From 4e87089af2ce50364a3e6fcfdf8a3c31e172d8aa Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sun, 5 Dec 2010 15:03:28 +0000 Subject: Apply new favorites outline view patch. --- Source/SPConnectionController.m | 1522 ++++++++++++++++++++++----------------- 1 file changed, 844 insertions(+), 678 deletions(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index aaff9687..8c5c4896 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -32,17 +32,32 @@ #import "SPAlertSheets.h" #import "SPKeychain.h" #import "SPSSHTunnel.h" -#import "SPFavoriteNode.h" #import "SPTableTextFieldCell.h" +#import "SPFavoritesController.h" +#import "SPFavoriteNode.h" + +// Constants +static const NSString *SPRemoveNode = @"RemoveNode"; +static const NSString *SPImportFavorites = @"ImportFavorites"; +static const NSString *SPExportFavorites = @"ExportFavorites"; +static const NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; @interface SPConnectionController (PrivateAPI) +- (BOOL)_checkHost; - (void)_sortFavorites; -- (void)_buildFavoritesTree; +- (void)_favoriteTypeDidChange; +- (void)_reloadFavoritesViewData; - (void)_restoreConnectionInterface; - (void)_mySQLConnectionEstablished; +- (void)_selectNode:(SPTreeNode *)node; - (void)_initiateMySQLConnectionInBackground; +- (NSNumber *)_createNewFavoriteID; +- (SPTreeNode *)_favoriteNodeForFavoriteID:(NSInteger)favoriteID; + +- (void)_updateFavoritePasswordsFromField:(NSControl *)control; + @end @implementation SPConnectionController @@ -83,124 +98,145 @@ * Initialise the connection controller, linking it to the * parent document and setting up the parent window. */ -- (id) initWithDocument:(SPDatabaseDocument *)theTableDocument +- (id)initWithDocument:(SPDatabaseDocument *)document { if (self = [super init]) { - tableDocument = theTableDocument; - databaseConnectionSuperview = [tableDocument databaseView]; - databaseConnectionView = [tableDocument valueForKey:@"contentViewSplitter"]; + + // Weak reference + dbDocument = document; + + databaseConnectionSuperview = [dbDocument databaseView]; + databaseConnectionView = [dbDocument valueForKey:@"contentViewSplitter"]; + + // Keychain references connectionKeychainItemName = nil; connectionKeychainItemAccount = nil; connectionSSHKeychainItemName = nil; connectionSSHKeychainItemAccount = nil; - mySQLConnection = nil; + sshTunnel = nil; - cancellingConnection = NO; + mySQLConnection = nil; isConnecting = NO; + cancellingConnection = NO; mySQLConnectionCancelled = NO; - favoritesPBoardType = @"FavoritesPBoardType"; - + + favoriteNameFieldWasTouched = YES; + // Load the connection nib, keeping references to the top-level objects for later release nibObjectsToRelease = [[NSMutableArray alloc] init]; NSArray *connectionViewTopLevelObjects = nil; NSNib *nibLoader = [[NSNib alloc] initWithNibNamed:@"ConnectionView" bundle:[NSBundle mainBundle]]; + [nibLoader instantiateNibWithOwner:self topLevelObjects:&connectionViewTopLevelObjects]; [nibObjectsToRelease addObjectsFromArray:connectionViewTopLevelObjects]; [nibLoader release]; - + // Hide the main view and position and display the connection view [databaseConnectionView setHidden:YES]; [connectionView setFrame:[databaseConnectionView frame]]; - [databaseConnectionSuperview addSubview:connectionView]; - [connectionSplitView setPosition:[[tableDocument valueForKey:@"dbTablesTableView"] frame].size.width-6 ofDividerAtIndex:0]; + [databaseConnectionSuperview addSubview:connectionView]; + [connectionSplitView setPosition:[[dbDocument valueForKey:@"dbTablesTableView"] frame].size.width-6 ofDividerAtIndex:0]; [connectionSplitViewButtonBar setSplitViewDelegate:self]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(scrollViewFrameChanged:) name:NSViewFrameDidChangeNotification object:nil]; - + + // Generic folder image for use in the outline view's groups + folderImage = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericFolderIcon)] retain]; + + [folderImage setSize:NSMakeSize(16, 16)]; + // Set up a keychain instance and preferences reference, and create the initial favorites list keychain = [[SPKeychain alloc] init]; prefs = [[NSUserDefaults standardUserDefaults] retain]; - - favorites = nil; - - // Load favorites - [self updateFavorites]; - - // Expand the favorites heading - [favoritesTable expandItem:[[favoritesRoot nodeChildren] objectAtIndex:0]]; - - // Register an observer for changes within the favorites - [prefs addObserver:self forKeyPath:SPFavorites options:NSKeyValueObservingOptionNew context:NULL]; - - // Set sort items - currentSortItem = [prefs integerForKey:SPFavoritesSortedBy]; - reverseFavoritesSort = [prefs boolForKey:SPFavoritesSortedInReverse]; - - // Register double click for the favorites view (double click favorite to connect) - [favoritesTable setTarget:self]; - [favoritesTable registerForDraggedTypes:[NSArray arrayWithObject:favoritesPBoardType]]; - [favoritesTable setDraggingSourceOperationMask:NSDragOperationMove forLocal:YES]; - + + // Create a reference to the favorites controller, forcing the data to be loaded from disk and the + // tree constructor. + favoritesController = [SPFavoritesController sharedFavoritesController]; + + // Tree reference + favoritesRoot = [favoritesController favoritesTree]; + + // Update the UI + [self _reloadFavoritesViewData]; + + // Set sort items + currentSortItem = [prefs integerForKey:SPFavoritesSortedBy]; + reverseFavoritesSort = [prefs boolForKey:SPFavoritesSortedInReverse]; + + // Register double click action for the favorites outline view (double click favorite to connect) + [favoritesOutlineView setTarget:self]; + [favoritesOutlineView setDoubleAction:@selector(nodeDoubleClicked:)]; + [favoritesOutlineView registerForDraggedTypes:[NSArray arrayWithObject:SPFavoritesPasteboardDragType]]; + [favoritesOutlineView setDraggingSourceOperationMask:NSDragOperationMove forLocal:YES]; + + // Registered to be notified of changes to connection information + [self addObserver:self forKeyPath:SPFavoriteNameKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; + [self addObserver:self forKeyPath:SPFavoriteHostKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; + [self addObserver:self forKeyPath:SPFavoriteUserKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; + [self addObserver:self forKeyPath:SPFavoriteDatabaseKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; + [self addObserver:self forKeyPath:SPFavoriteSocketKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; + [self addObserver:self forKeyPath:SPFavoritePortKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; + [self addObserver:self forKeyPath:SPFavoriteUseSSLKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; + [self addObserver:self forKeyPath:SPFavoriteSSHHostKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; + [self addObserver:self forKeyPath:SPFavoriteSSHUserKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; + [self addObserver:self forKeyPath:SPFavoriteSSHPortKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; + [self addObserver:self forKeyPath:SPFavoriteSSHKeyLocationEnabledKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; + [self addObserver:self forKeyPath:SPFavoriteSSHKeyLocationKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; + [self addObserver:self forKeyPath:SPFavoriteSSLKeyFileLocationEnabledKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; + [self addObserver:self forKeyPath:SPFavoriteSSLKeyFileLocationKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; + [self addObserver:self forKeyPath:SPFavoriteSSLCertificateFileLocationEnabledKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; + [self addObserver:self forKeyPath:SPFavoriteSSLCertificateFileLocationKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; + [self addObserver:self forKeyPath:SPFavoriteSSLCACertFileLocationEnabledKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; + [self addObserver:self forKeyPath:SPFavoriteSSLCACertFileLocationKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; + // Sort the favourites to match prefs and select the appropriate row - if a valid sort option is selected - if (currentSortItem > -1) [self _sortFavorites]; - - NSInteger tableRow = [prefs integerForKey:[prefs boolForKey:SPSelectLastFavoriteUsed] ? SPLastFavoriteIndex : SPDefaultFavorite]; - - if (tableRow < [favorites count]) { - previousType = [[[favorites objectAtIndex:tableRow] objectForKey:SPFavoriteTypeKey] integerValue]; - [favoritesTable selectRowIndexes:[NSIndexSet indexSetWithIndex:(tableRow + 1)] byExtendingSelection:NO]; - [self resizeTabViewToConnectionType:[[[favorites objectAtIndex:tableRow] objectForKey:SPFavoriteTypeKey] integerValue] animating:NO]; - [favoritesTable scrollRowToVisible:[favoritesTable selectedRow]]; - } + // TODO: Fix me, sorting currently does not work in the new outline view + //if (currentSortItem > -1) [self _sortFavorites]; + + SPTreeNode *favorite = [self _favoriteNodeForFavoriteID:[prefs integerForKey:([prefs boolForKey:SPSelectLastFavoriteUsed]) ? SPLastFavoriteID : SPDefaultFavorite]]; + + if (favorite && [favorite representedObject]) { + + NSNumber *typeNumber = [[[favorite representedObject] nodeFavorite] objectForKey:SPFavoriteTypeKey]; + + previousType = (typeNumber) ? [typeNumber integerValue] : SPTCPIPConnection; + + [self _selectNode:favorite]; + + [self resizeTabViewToConnectionType:[[[[favorite representedObject] nodeFavorite] objectForKey:SPFavoriteTypeKey] integerValue] animating:NO]; + + [favoritesOutlineView scrollRowToVisible:[favoritesOutlineView selectedRow]]; + } else { previousType = SPTCPIPConnection; + [self resizeTabViewToConnectionType:SPTCPIPConnection animating:NO]; } } - + return self; } -- (void) dealloc -{ - [prefs removeObserver:self forKeyPath:SPFavorites]; - [[NSNotificationCenter defaultCenter] removeObserver:self]; - [keychain release]; - [prefs release]; - - for (id retainedObject in nibObjectsToRelease) [retainedObject release]; - [nibObjectsToRelease release]; - - if (favorites) [favorites release]; - if (mySQLConnection) [mySQLConnection release]; - if (sshTunnel) [sshTunnel setConnectionStateChangeSelector:nil delegate:nil], [sshTunnel disconnect], [sshTunnel release]; - if (connectionKeychainItemName) [connectionKeychainItemName release]; - if (connectionKeychainItemAccount) [connectionKeychainItemAccount release]; - if (connectionSSHKeychainItemName) [connectionSSHKeychainItemName release]; - if (connectionSSHKeychainItemAccount) [connectionSSHKeychainItemAccount release]; - - [super dealloc]; -} - #pragma mark - #pragma mark Connection processes -/* +/** * Starts the connection process; invoked when user hits the connect button * or double-clicks on a favourite. * Error-checks fields as required, and triggers connection of MySQL or any * connection proxies in use. */ - (IBAction)initiateConnection:(id)sender -{ +{ // Ensure that host is not empty if this is a TCP/IP or SSH connection if (([self type] == SPTCPIPConnection || [self type] == SPSSHTunnelConnection) && ![[self host] length]) { - SPBeginAlertSheet(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocument parentWindow], self, nil, nil, NSLocalizedString(@"Insufficient details provided to establish a connection. Please enter at least the hostname.", @"insufficient details informative message")); + SPBeginAlertSheet(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], self, nil, nil, NSLocalizedString(@"Insufficient details provided to establish a connection. Please enter at least the hostname.", @"insufficient details informative message")); return; } - + // If SSH is enabled, ensure that the SSH host is not nil if ([self type] == SPSSHTunnelConnection && ![[self sshHost] length]) { - SPBeginAlertSheet(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocument parentWindow], self, nil, nil, NSLocalizedString(@"Insufficient details provided to establish a connection. Please enter the hostname for the SSH Tunnel, or disable the SSH Tunnel.", @"insufficient SSH tunnel details informative message")); + SPBeginAlertSheet(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], self, nil, nil, NSLocalizedString(@"Insufficient details provided to establish a connection. Please enter the hostname for the SSH Tunnel, or disable the SSH Tunnel.", @"insufficient SSH tunnel details informative message")); return; } @@ -208,13 +244,13 @@ if ([self type] == SPSSHTunnelConnection && sshKeyLocationEnabled && sshKeyLocation) { if (![[NSFileManager defaultManager] fileExistsAtPath:[sshKeyLocation stringByExpandingTildeInPath]]) { [self setSshKeyLocationEnabled:NSOffState]; - SPBeginAlertSheet(NSLocalizedString(@"SSH Key not found", @"SSH key check error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocument parentWindow], self, nil, nil, NSLocalizedString(@"A SSH key location was specified, but no file was found in the specified location. Please re-select the key and try again.", @"SSH key not found message")); + SPBeginAlertSheet(NSLocalizedString(@"SSH Key not found", @"SSH key check error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], self, nil, nil, NSLocalizedString(@"A SSH key location was specified, but no file was found in the specified location. Please re-select the key and try again.", @"SSH key not found message")); return; } } // Ensure that a socket connection is not inadvertently used - if (![self checkHost]) return; + if (![self _checkHost]) return; // If SSL keys have been supplied, verify they exist if (([self type] == SPTCPIPConnection || [self type] == SPSocketConnection) && [self useSSL]) { @@ -223,7 +259,7 @@ { [self setSslKeyFileLocationEnabled:NSOffState]; [self setSslKeyFileLocation:nil]; - SPBeginAlertSheet(NSLocalizedString(@"SSL Key File not found", @"SSL key file check error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocument parentWindow], self, nil, nil, NSLocalizedString(@"A SSL key file location was specified, but no file was found in the specified location. Please re-select the key file and try again.", @"SSL key file not found message")); + SPBeginAlertSheet(NSLocalizedString(@"SSL Key File not found", @"SSL key file check error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], self, nil, nil, NSLocalizedString(@"A SSL key file location was specified, but no file was found in the specified location. Please re-select the key file and try again.", @"SSL key file not found message")); return; } if (sslCertificateFileLocationEnabled && sslCertificateFileLocation @@ -231,7 +267,7 @@ { [self setSslCertificateFileLocationEnabled:NSOffState]; [self setSslCertificateFileLocation:nil]; - SPBeginAlertSheet(NSLocalizedString(@"SSL Certificate File not found", @"SSL certificate file check error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocument parentWindow], self, nil, nil, NSLocalizedString(@"A SSL certificate location was specified, but no file was found in the specified location. Please re-select the certificate and try again.", @"SSL certificate file not found message")); + SPBeginAlertSheet(NSLocalizedString(@"SSL Certificate File not found", @"SSL certificate file check error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], self, nil, nil, NSLocalizedString(@"A SSL certificate location was specified, but no file was found in the specified location. Please re-select the certificate and try again.", @"SSL certificate file not found message")); return; } if (sslCACertFileLocationEnabled && sslCACertFileLocation @@ -239,7 +275,7 @@ { [self setSslCACertFileLocationEnabled:NSOffState]; [self setSslCACertFileLocation:nil]; - SPBeginAlertSheet(NSLocalizedString(@"SSL Certificate Authority File not found", @"SSL certificate authority file check error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocument parentWindow], self, nil, nil, NSLocalizedString(@"A SSL Certificate Authority certificate location was specified, but no file was found in the specified location. Please re-select the Certificate Authority certificate and try again.", @"SSL CA certificate file not found message")); + SPBeginAlertSheet(NSLocalizedString(@"SSL Certificate Authority File not found", @"SSL certificate authority file check error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], self, nil, nil, NSLocalizedString(@"A SSL Certificate Authority certificate location was specified, but no file was found in the specified location. Please re-select the Certificate Authority certificate and try again.", @"SSL CA certificate file not found message")); return; } } @@ -247,7 +283,7 @@ // Basic details have validated - start the connection process animating isConnecting = YES; cancellingConnection = NO; - + [addToFavoritesButton setHidden:YES]; [addToFavoritesButton display]; [helpButton setHidden:YES]; @@ -258,9 +294,9 @@ [progressIndicator display]; [progressIndicatorText setHidden:NO]; [progressIndicatorText display]; - + // Start the current tab's progress indicator - [tableDocument setIsProcessing:YES]; + [dbDocument setIsProcessing:YES]; // If the password(s) are marked as having been originally sourced from a keychain, check whether they // have been changed or not; if not, leave the mark in place and remove the password from the field @@ -271,11 +307,13 @@ [[standardPasswordField undoManager] removeAllActionsWithTarget:standardPasswordField]; [[socketPasswordField undoManager] removeAllActionsWithTarget:socketPasswordField]; [[sshPasswordField undoManager] removeAllActionsWithTarget:sshPasswordField]; - } else { + } + else { [connectionKeychainItemName release], connectionKeychainItemName = nil; [connectionKeychainItemAccount release], connectionKeychainItemAccount = nil; } } + if (connectionSSHKeychainItemName) { if ([[keychain getPasswordForName:connectionSSHKeychainItemName account:connectionSSHKeychainItemAccount] isEqualToString:[self sshPassword]]) { [self setSshPassword:[[NSString string] stringByPaddingToLength:[[self sshPassword] length] withString:@"sp" startingAtIndex:0]]; @@ -285,22 +323,22 @@ [connectionSSHKeychainItemAccount release], connectionSSHKeychainItemAccount = nil; } } - + // Inform the delegate that we are starting the connection process if (delegate && [delegate respondsToSelector:@selector(connectionControllerInitiatingConnection:)]) { [delegate connectionControllerInitiatingConnection:self]; } - + // Trim whitespace and newlines from the host field before attempting to connect [self setHost:[[self host] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]]; - + // Initiate the SSH connection process for tunnels if ([self type] == SPSSHTunnelConnection) { [self performSelector:@selector(initiateSSHTunnelConnection) withObject:nil afterDelay:0.0]; return; } - - // ...or start the MySQL connection process directly + + // ...or start the MySQL connection process directly [self performSelector:@selector(initiateMySQLConnection) withObject:nil afterDelay:0.0]; } @@ -314,229 +352,39 @@ - (IBAction)cancelMySQLConnection:(id)sender { [connectButton setEnabled:NO]; - + [progressIndicatorText setStringValue:NSLocalizedString(@"Cancelling...", @"cancelling task status message")]; [progressIndicatorText display]; - + mySQLConnectionCancelled = YES; } -/* - * Initiate the SSH connection process. - * This should only be called as part of initiateConnection:, and will indirectly - * call initiateMySQLConnection if it's successful. - */ -- (void)initiateSSHTunnelConnection -{ - [progressIndicatorText setStringValue:NSLocalizedString(@"SSH connecting...", @"SSH connecting very short status message")]; - [progressIndicatorText display]; - - // Trim whitespace and newlines from the SSH host field before attempting to connect - [self setSshHost:[[self sshHost] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]]; - - // Set up the tunnel details - sshTunnel = [[SPSSHTunnel alloc] initToHost:[self sshHost] port:[[self sshPort] integerValue] login:[self sshUser] tunnellingToPort:([[self port] length]?[[self port] integerValue]:3306) onHost:[self host]]; - [sshTunnel setParentWindow:[tableDocument parentWindow]]; - - // Add keychain or plaintext password as appropriate - note the checks in initiateConnection. - if (connectionSSHKeychainItemName) { - [sshTunnel setPasswordKeychainName:connectionSSHKeychainItemName account:connectionSSHKeychainItemAccount]; - } else if (sshPassword) { - [sshTunnel setPassword:[self sshPassword]]; - } - - // Set the public key path if appropriate - if (sshKeyLocationEnabled && sshKeyLocation) { - [sshTunnel setKeyFilePath:sshKeyLocation]; - } - - // Set the callback function on the tunnel - [sshTunnel setConnectionStateChangeSelector:@selector(sshTunnelCallback:) delegate:self]; - - // Ask the tunnel to connect. This will call the callback below on success or failure, passing - // itself as an argument - retain count should be one at this point. - [sshTunnel connect]; -} - -/* - * Cancel connection. - * Currently only cleans up the SSH connection (MySQL connection isn't threaded) - */ -- (void)cancelConnection -{ - if (!sshTunnel) return; - - cancellingConnection = YES; - - [sshTunnel disconnect]; - [sshTunnel release]; - - sshTunnel = nil; -} - -/* - * A callback function for the SSH Tunnel setup process - will be called on a connection - * state change, allowing connection to fail or proceed as appropriate. If successful, - * will call initiateMySQLConnection. - */ -- (void)sshTunnelCallback:(SPSSHTunnel *)theTunnel -{ - if (cancellingConnection) return; - NSInteger newState = [theTunnel state]; - - if (newState == PROXY_STATE_IDLE) { - [tableDocument setTitlebarStatus:NSLocalizedString(@"SSH Disconnected", @"SSH disconnected titlebar marker")]; - [self failConnectionWithTitle:NSLocalizedString(@"SSH connection failed!", @"SSH connection failed title") errorMessage:[theTunnel lastError] detail:[sshTunnel debugMessages]]; - } else if (newState == PROXY_STATE_CONNECTED) { - [tableDocument setTitlebarStatus:NSLocalizedString(@"SSH Connected", @"SSH connected titlebar marker")]; - [self initiateMySQLConnection]; - } else { - [tableDocument setTitlebarStatus:NSLocalizedString(@"SSH Connecting…", @"SSH connecting titlebar marker")]; - } -} - -/* - * Set up the MySQL connection, either through a successful tunnel or directly in the background. - */ -- (void)initiateMySQLConnection -{ - // Disable the favorites table view to prevent further connections attempts - [favoritesTable setEnabled:NO]; - - if (sshTunnel) - [progressIndicatorText setStringValue:NSLocalizedString(@"MySQL connecting...", @"MySQL connecting very short status message")]; - else - [progressIndicatorText setStringValue:NSLocalizedString(@"Connecting...", @"Generic connecting very short status message")]; - - [progressIndicatorText display]; - - [connectButton setTitle:NSLocalizedString(@"Cancel", @"cancel button")]; - [connectButton setAction:@selector(cancelMySQLConnection:)]; - [connectButton setEnabled:YES]; - [connectButton display]; - - [NSThread detachNewThreadSelector:@selector(_initiateMySQLConnectionInBackground) toTarget:self withObject:nil]; -} - -/* - * Ends a connection attempt by stopping the connection animation and - * displaying a specified error message. - */ -- (void)failConnectionWithTitle:(NSString *)theTitle errorMessage:(NSString *)theErrorMessage detail:(NSString *)errorDetail -{ - BOOL isSSHTunnelBindError = NO; - - // Clean up the interface - [progressIndicator stopAnimation:self]; - [progressIndicator display]; - [progressIndicatorText setHidden:YES]; - [progressIndicatorText display]; - [addToFavoritesButton setHidden:NO]; - [addToFavoritesButton display]; - [connectButton setEnabled:YES]; - [tableDocument clearStatusIcon]; - - // Release as appropriate - if (sshTunnel) { - [sshTunnel disconnect], [sshTunnel release], sshTunnel = nil; - - // If the SSH tunnel connection failed because the port it was trying to bind to was already in use take note - // of it so we can give the user the option of connecting via standard connection and use the existing tunnel. - if ([theErrorMessage rangeOfString:@"bind"].location != NSNotFound) { - isSSHTunnelBindError = YES; - } - } - - if (errorDetail) [errorDetailText setString:errorDetail]; - - // Inform the delegate that the connection attempt failed - if (delegate && [delegate respondsToSelector:@selector(connectionControllerConnectAttemptFailed:)]) { - [delegate connectionControllerConnectAttemptFailed:self]; - } - - // Only display the connection error message if there is a window visible and the connection attempt - // wasn't cancelled even though it failed. - if ([[tableDocument parentWindow] isVisible] && (!mySQLConnectionCancelled)) { - SPBeginAlertSheet(theTitle, NSLocalizedString(@"OK", @"OK button"), (errorDetail) ? NSLocalizedString(@"Show Detail", @"Show detail button") : nil, (isSSHTunnelBindError) ? NSLocalizedString(@"Use Standard Connection", @"use standard connection button") : nil, [tableDocument parentWindow], self, @selector(connectionFailureSheetDidEnd:returnCode:contextInfo:), @"connect", theErrorMessage); - } -} - -/** - * Alert sheet callback method - invoked when an error sheet is closed. - */ -- (void)connectionFailureSheetDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo -{ - // Restore the passwords from keychain for editing if appropriate - if (connectionKeychainItemName) { - [self setPassword:[keychain getPasswordForName:connectionKeychainItemName account:connectionKeychainItemAccount]]; - } - if (connectionSSHKeychainItemName) { - [self setSshPassword:[keychain getPasswordForName:connectionSSHKeychainItemName account:connectionSSHKeychainItemAccount]]; - } - - if (returnCode == NSAlertAlternateReturn) { - [errorDetailText setFont:[NSFont userFontOfSize:12]]; - [errorDetailText setAlignment:NSLeftTextAlignment]; - [errorDetailWindow makeKeyAndOrderFront:self]; - } - // Currently only SSH port bind errors offer a 3rd option in the error dialog, but if this ever changes - // this will definitely need to be updated. - else if (returnCode == NSAlertOtherReturn) { - // Extract the local port number that SSH attempted to bind to from the debug output - NSString *tunnelPort = [[[errorDetailText string] componentsMatchedByRegex:@"LOCALHOST:([0-9]+)" capture:1L] lastObject]; - - // Change the connection type to standard TCP/IP - [self setType:SPTCPIPConnection]; - - // Change connection details - [self setPort:tunnelPort]; - [self setHost:@"127.0.0.1"]; - - // Change to standard TCP/IP connection view - [self resizeTabViewToConnectionType:SPTCPIPConnection animating:YES]; - - // Initiate the connection after half a second to give the connection view a chance to resize - [self performSelector:@selector(initiateConnection:) withObject:self afterDelay:0.5]; - } -} +#pragma mark - +#pragma mark Interface interaction /** - * Add the connection to the parent document and restore the - * interface, allowing the application to run as normal. + * Registered in initWithDocument: to be the double click action of the favorites outline view. */ -- (void)addConnectionToDocument +- (IBAction)nodeDoubleClicked:(id)sender { - // Hide the connection view and restore the main view - [connectionView removeFromSuperviewWithoutNeedingDisplay]; - [databaseConnectionView setHidden:NO]; - - // Restore the toolbar icons - NSArray *toolbarItems = [[[tableDocument parentWindow] toolbar] items]; - - for (NSInteger i = 0; i < [toolbarItems count]; i++) [[toolbarItems objectAtIndex:i] setEnabled:YES]; - - // Set keychain id for saving SPF files - if ([self valueForKeyPath:@"selectedFavorite.id"]) { - [tableDocument setKeychainID:[[self valueForKeyPath:@"selectedFavorite.id"] stringValue]]; + SPTreeNode *node = [self selectedFavoriteNode]; + + // Only proceed to initiate a connection if a leaf node (i.e. a favorite and not a group) was double clicked. + if (![node isGroup]) { + [self initiateConnection:self]; } + // Otherwise start editing the group item's name else { - [tableDocument setKeychainID:@""]; + [favoritesOutlineView editColumn:0 row:[favoritesOutlineView selectedRow] withEvent:nil select:YES]; } - - // Pass the connection to the table document, allowing it to set - // up the other classes and the rest of the interface. - [tableDocument setConnection:mySQLConnection]; } -#pragma mark - -#pragma mark Interface interaction - /** * Opens the SSH/SSL key selection window, ready to select a key file. */ - (IBAction)chooseKeyLocation:(id)sender { - [favoritesTable deselectAll:self]; + [favoritesOutlineView deselectAll:self]; NSString *directoryPath = nil; NSString *filePath = nil; NSArray *permittedFileTypes = nil; @@ -569,7 +417,7 @@ } permittedFileTypes = [NSArray arrayWithObjects:@"pem", @"key", @"", nil]; [openPanel setAccessoryView:sslKeyFileLocationHelp]; - + // SSL certificate file location: } else if (sender == standardSSLCertificateButton || sender == socketSSLCertificateButton) { if ([sender state] == NSOffState) { @@ -578,7 +426,7 @@ } permittedFileTypes = [NSArray arrayWithObjects:@"pem", @"cert", @"", nil]; [openPanel setAccessoryView:sslCertificateLocationHelp]; - + // SSL CA certificate file location: } else if (sender == standardSSLCACertButton || sender == socketSSLCACertButton) { if ([sender state] == NSOffState) { @@ -592,72 +440,14 @@ [openPanel beginSheetForDirectory:directoryPath file:filePath types:permittedFileTypes - modalForWindow:[tableDocument parentWindow] + modalForWindow:[dbDocument parentWindow] modalDelegate:self didEndSelector:@selector(chooseKeyLocationSheetDidEnd:returnCode:contextInfo:) contextInfo:sender]; } /** - * Called after closing the SSH/SSL key selection sheet. - */ -- (void)chooseKeyLocationSheetDidEnd:(NSOpenPanel *)openPanel returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo -{ - NSString *abbreviatedFileName = [[openPanel filename] stringByAbbreviatingWithTildeInPath]; - - // SSH key file selection - if (contextInfo == sshSSHKeyButton) { - if (returnCode == NSCancelButton) { - [self setSshKeyLocationEnabled:NSOffState]; - return; - } - [self setSshKeyLocation:abbreviatedFileName]; - - // SSL key file selection - } else if (contextInfo == standardSSLKeyFileButton || contextInfo == socketSSLKeyFileButton) { - if (returnCode == NSCancelButton) { - [self setSslKeyFileLocationEnabled:NSOffState]; - [self setSslKeyFileLocation:nil]; - return; - } - [self setSslKeyFileLocation:abbreviatedFileName]; - - // SSL certificate file selection - } else if (contextInfo == standardSSLCertificateButton || contextInfo == socketSSLCertificateButton) { - if (returnCode == NSCancelButton) { - [self setSslCertificateFileLocationEnabled:NSOffState]; - [self setSslCertificateFileLocation:nil]; - return; - } - [self setSslCertificateFileLocation:abbreviatedFileName]; - - // SSL CA certificate file selection - } else if (contextInfo == standardSSLCACertButton || contextInfo == socketSSLCACertButton) { - if (returnCode == NSCancelButton) { - [self setSslCACertFileLocationEnabled:NSOffState]; - [self setSslCACertFileLocation:nil]; - return; - } - [self setSslCACertFileLocation:abbreviatedFileName]; - } -} - -/** - * Opens the preferences window, or brings it to the front, and switch to the favorites tab. - * If a favorite is selected in the connection sheet, it is also select in the prefs window. - */ -- (IBAction)editFavorites:(id)sender -{ - SPPreferenceController *prefsController = [[NSApp delegate] preferenceController]; - - [prefsController showWindow:self]; - [prefsController displayFavoritePreferences:self]; - - if ([favoritesTable numberOfSelectedRows]) [[prefsController favoritesPreferencePane] selectFavorites:[NSArray arrayWithObject:[self valueForKeyPath:@"selectedFavorite"]]]; -} - -/** - * Show connection help. + * Show connection help webpage. */ - (IBAction)showHelp:(id)sender { @@ -675,83 +465,6 @@ #pragma mark - #pragma mark Connection details interaction and display -/** - * Trigger a resize action whenever the tab view changes. The connection - * detail forms are held within container views, which are of a fixed width; - * the tabview and buttons are contained within a resizable view which - * is set to dimensions based on the container views, allowing the view - * to be sized according to the detail type. - */ -- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem -{ - NSInteger selectedTabView = [tabView indexOfTabViewItem:tabViewItem]; - - // Deselect any selected favorite for manual changes - if (!automaticFavoriteSelection) [favoritesTable deselectAll:self]; - automaticFavoriteSelection = NO; - - if (selectedTabView == previousType) return; - - [self resizeTabViewToConnectionType:selectedTabView animating:YES]; - - // Update the host as appropriate - if ((selectedTabView != SPSocketConnection) && [[self host] isEqualToString:@"localhost"]) { - [self setHost:@""]; - } - - previousType = selectedTabView; -} - -/** - * As the scrollview resizes, keep the details centered within it if - * the detail frame is larger than the scrollview size; otherwise, pin - * the detail frame to the top of the scrollview. - */ -- (void)scrollViewFrameChanged:(NSNotification *)aNotification -{ - NSRect scrollViewFrame = [connectionDetailsScrollView frame]; - NSRect scrollDocumentFrame = [[connectionDetailsScrollView documentView] frame]; - NSRect connectionDetailsFrame = [connectionResizeContainer frame]; - - // Scroll view is smaller than contents - keep positioned at top. - if (scrollViewFrame.size.height < connectionDetailsFrame.size.height + 10) { - if (connectionDetailsFrame.origin.y != 0) { - connectionDetailsFrame.origin.y = 0; - [connectionResizeContainer setFrame:connectionDetailsFrame]; - scrollDocumentFrame.size.height = connectionDetailsFrame.size.height + 10; - [[connectionDetailsScrollView documentView] setFrame:scrollDocumentFrame]; - } - - // Otherwise, center. - } else { - connectionDetailsFrame.origin.y = (scrollViewFrame.size.height - connectionDetailsFrame.size.height)/3; - [connectionResizeContainer setFrame:connectionDetailsFrame]; - scrollDocumentFrame.size.height = scrollViewFrame.size.height; - [[connectionDetailsScrollView documentView] setFrame:scrollDocumentFrame]; - } -} - -/** - * When a favorite is selected, and the connection details are edited, deselect the favorite; - * this is clearer and also prevents a failed connection from being repopulated with the - * favorite's details instead of the last used details. - */ -- (void)controlTextDidChange:(NSNotification *)aNotification -{ - [favoritesTable deselectAll:self]; -} - -/** - * When a host field finishes editing, ensure that it hasn't been set to "localhost" - * to ensure that socket connections don't inadvertently occur. - */ -- (void)controlTextDidEndEditing:(NSNotification *)notification -{ - if ([notification object] == standardSQLHostField || [notification object] == sshSQLHostField) { - [self checkHost]; - } -} - /** * Control tab view resizing based on the supplied connection type, * with an option defining whether it should be animated or not. @@ -775,49 +488,15 @@ case SPSSHTunnelConnection: targetResizeRect = [sshConnectionFormContainer frame]; break; - } + } frameRect.size.height = targetResizeRect.size.height + additionalFormHeight; if (animate) { [[connectionResizeContainer animator] setFrame:frameRect]; - } else { - [connectionResizeContainer setFrame:frameRect]; - } -} - -/** - * Check the host field and ensure it isn't set to "localhost" for - * non-socket connections. - */ -- (BOOL)checkHost -{ - if ([self type] != SPSocketConnection && [[self host] isEqualToString:@"localhost"]) { - SPBeginAlertSheet(NSLocalizedString(@"You have entered 'localhost' for a non-socket connection", @"title of error when using 'localhost' for a network connection"), - NSLocalizedString(@"Use 127.0.0.1", @"Use 127.0.0.1 button"), // Main button - NSLocalizedString(@"Connect via socket", @"Connect via socket button"), // Alternate button - nil, // Other button - [tableDocument parentWindow], // Window to attach to - self, // Modal delegate - @selector(localhostErrorSheetDidEnd:returnCode:contextInfo:), // Did end selector - nil, // Contextual info for selectors - NSLocalizedString(@"To MySQL, 'localhost' is a special host and means that a socket connection should be used.\n\nDid you mean to use a socket connection, or to connect to the local machine via a port? If you meant to connect via a port, '127.0.0.1' should be used instead of 'localhost'.", @"message of error when using 'localhost' for a network connection")); - return NO; - } - - return YES; -} - -/** - * Alert sheet callback method - invoked when the error sheet is closed. - */ -- (void)localhostErrorSheetDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo -{ - if (returnCode == NSAlertAlternateReturn) { - [self setType:SPSocketConnection]; - [self setHost:@""]; - } else { - [self setHost:@"127.0.0.1"]; + } + else { + [connectionResizeContainer setFrame:frameRect]; } } @@ -828,17 +507,17 @@ * Sorts the favorites table view based on the selected sort by item. */ - (void)sortFavorites:(id)sender -{ +{ previousSortItem = currentSortItem; currentSortItem = [[sender menu] indexOfItem:sender]; - + [prefs setInteger:currentSortItem forKey:SPFavoritesSortedBy]; - + // Perform sorting [self _sortFavorites]; - + if (previousSortItem > -1) [[[sender menu] itemAtIndex:previousSortItem] setState:NSOffState]; - + [[[sender menu] itemAtIndex:currentSortItem] setState:NSOnState]; } @@ -848,46 +527,20 @@ - (void)reverseSortFavorites:(id)sender { reverseFavoritesSort = (![sender state]); - + [prefs setBool:reverseFavoritesSort forKey:SPFavoritesSortedInReverse]; - + // Perform re-sorting [self _sortFavorites]; - - [sender setState:reverseFavoritesSort]; + + [sender setState:reverseFavoritesSort]; } /** - * Updates the local favorites array from the user defaults - */ -- (void)updateFavorites -{ - [favoritesTable deselectAll:self]; - - if (favorites) [favorites release]; - - if ([prefs objectForKey:SPFavorites]) { - favorites = [[NSMutableArray alloc] initWithArray:[prefs objectForKey:SPFavorites]]; - } - else { - favorites = [[NSMutableArray alloc] init]; - } - - [self _buildFavoritesTree]; - - [favoritesTable reloadData]; - - [favoritesTable expandItem:[[favoritesRoot nodeChildren] objectAtIndex:0]]; -} - -/** - * Sets fields for the chosen favorite. + * Sets fields for the chosen favorite. */ - (void)updateFavoriteSelection:(id)sender { - // If nothing is selected, return without updating the interface - if (![self selectedFavorite]) return; - automaticFavoriteSelection = YES; // Clear the keychain referral items as appropriate @@ -895,10 +548,19 @@ if (connectionKeychainItemAccount) [connectionKeychainItemAccount release], connectionKeychainItemAccount = nil; if (connectionSSHKeychainItemName) [connectionSSHKeychainItemName release], connectionSSHKeychainItemName = nil; if (connectionSSHKeychainItemAccount) [connectionSSHKeychainItemAccount release], connectionSSHKeychainItemAccount = nil; - + + SPTreeNode *node = [self selectedFavoriteNode]; + // Update key-value properties from the selected favourite, using empty strings where not found - NSDictionary *fav = [self selectedFavorite]; - + NSDictionary *fav = [[node representedObject] nodeFavorite]; + + // Keep a copy of the favorite as it currently stands + if (currentFavorite) [currentFavorite release], currentFavorite = nil; + + currentFavorite = [[node representedObject] copy]; + + [connectionResizeContainer setHidden:NO]; + // Standard details [self setType:([fav objectForKey:SPFavoriteTypeKey] ? [[fav objectForKey:SPFavoriteTypeKey] integerValue] : SPTCPIPConnection)]; [self setName:([fav objectForKey:SPFavoriteNameKey] ? [fav objectForKey:SPFavoriteNameKey] : @"")]; @@ -907,7 +569,7 @@ [self setUser:([fav objectForKey:SPFavoriteUserKey] ? [fav objectForKey:SPFavoriteUserKey] : @"")]; [self setPort:([fav objectForKey:SPFavoritePortKey] ? [fav objectForKey:SPFavoritePortKey] : @"")]; [self setDatabase:([fav objectForKey:SPFavoriteDatabaseKey] ? [fav objectForKey:SPFavoriteDatabaseKey] : @"")]; - + // SSL details [self setUseSSL:([fav objectForKey:SPFavoriteUseSSLKey] ? [[fav objectForKey:SPFavoriteUseSSLKey] intValue] : NSOffState)]; [self setSslKeyFileLocationEnabled:([fav objectForKey:SPFavoriteSSLKeyFileLocationEnabledKey] ? [[fav objectForKey:SPFavoriteSSLKeyFileLocationEnabledKey] intValue] : NSOffState)]; @@ -916,89 +578,173 @@ [self setSslCertificateFileLocation:([fav objectForKey:SPFavoriteSSLCertificateFileLocationKey] ? [fav objectForKey:SPFavoriteSSLCertificateFileLocationKey] : @"")]; [self setSslCACertFileLocationEnabled:([fav objectForKey:SPFavoriteSSLCACertFileLocationEnabledKey] ? [[fav objectForKey:SPFavoriteSSLCACertFileLocationEnabledKey] intValue] : NSOffState)]; [self setSslCACertFileLocation:([fav objectForKey:SPFavoriteSSLCACertFileLocationKey] ? [fav objectForKey:SPFavoriteSSLCACertFileLocationKey] : @"")]; - + // SSH details [self setSshHost:([fav objectForKey:SPFavoriteSSHHostKey] ? [fav objectForKey:SPFavoriteSSHHostKey] : @"")]; [self setSshUser:([fav objectForKey:SPFavoriteSSHUserKey] ? [fav objectForKey:SPFavoriteSSHUserKey] : @"")]; [self setSshKeyLocationEnabled:([fav objectForKey:SPFavoriteSSHKeyLocationEnabledKey] ? [[fav objectForKey:SPFavoriteSSHKeyLocationEnabledKey] intValue] : NSOffState)]; [self setSshKeyLocation:([fav objectForKey:SPFavoriteSSHKeyLocationKey] ? [fav objectForKey:SPFavoriteSSHKeyLocationKey] : @"")]; [self setSshPort:([fav objectForKey:SPFavoriteSSHPortKey] ? [fav objectForKey:SPFavoriteSSHPortKey] : @"")]; - + // Trigger an interface update [self resizeTabViewToConnectionType:[self type] animating:YES]; - + // Check whether the password exists in the keychain, and if so add it; also record the // keychain details so we can pass around only those details if the password doesn't change connectionKeychainItemName = [[keychain nameForFavoriteName:[self valueForKeyPath:@"selectedFavorite.name"] id:[self valueForKeyPath:@"selectedFavorite.id"]] retain]; connectionKeychainItemAccount = [[keychain accountForUser:[self valueForKeyPath:@"selectedFavorite.user"] host:(([self type] == SPSocketConnection)?@"localhost":[self valueForKeyPath:@"selectedFavorite.host"]) database:[self valueForKeyPath:@"selectedFavorite.database"]] retain]; - + [self setPassword:[keychain getPasswordForName:connectionKeychainItemName account:connectionKeychainItemAccount]]; - + if (![[self password] length]) { [self setPassword:nil]; [connectionKeychainItemName release], connectionKeychainItemName = nil; [connectionKeychainItemAccount release], connectionKeychainItemAccount = nil; } - + // And the same for the SSH password connectionSSHKeychainItemName = [[keychain nameForSSHForFavoriteName:[self valueForKeyPath:@"selectedFavorite.name"] id:[self valueForKeyPath:@"selectedFavorite.id"]] retain]; connectionSSHKeychainItemAccount = [[keychain accountForSSHUser:[self valueForKeyPath:@"selectedFavorite.sshUser"] sshHost:[self valueForKeyPath:@"selectedFavorite.sshHost"]] retain]; - + [self setSshPassword:[keychain getPasswordForName:connectionSSHKeychainItemName account:connectionSSHKeychainItemAccount]]; - + if (![[self sshPassword] length]) { [self setSshPassword:nil]; [connectionSSHKeychainItemName release], connectionSSHKeychainItemName = nil; [connectionSSHKeychainItemAccount release], connectionSSHKeychainItemAccount = nil; } - - [prefs setInteger:([favoritesTable selectedRow] - 1) forKey:SPLastFavoriteIndex]; - - // Set next KeyView to password field if it is empty - switch ([self type]) + + [prefs setInteger:([favoritesOutlineView selectedRow] - 1) forKey:SPLastFavoriteID]; + + // Set first responder to password field if it is empty + switch ([self type]) { case SPTCPIPConnection: - if (![[standardPasswordField stringValue] length]) [favoritesTable setNextKeyView:standardPasswordField]; + if (![[standardPasswordField stringValue] length]) [[dbDocument parentWindow] makeFirstResponder:standardPasswordField]; break; case SPSocketConnection: - if (![[socketPasswordField stringValue] length]) [favoritesTable setNextKeyView:socketPasswordField]; + if (![[socketPasswordField stringValue] length]) [[dbDocument parentWindow] makeFirstResponder:socketPasswordField]; break; case SPSSHTunnelConnection: - if (![[sshPasswordField stringValue] length]) [favoritesTable setNextKeyView:sshPasswordField]; + if (![[sshPasswordField stringValue] length]) [[dbDocument parentWindow] makeFirstResponder:sshPasswordField]; break; } } /** - * Returns a KVC-compliant proxy to the currently selected favorite, or nil if nothing selected. + * Returns the selected favorite data dictionary or nil if nothing is selected. + */ +- (NSMutableDictionary *)selectedFavorite +{ + SPTreeNode *node = [self selectedFavoriteNode]; + + return (![node isGroup]) ? [[node representedObject] nodeFavorite] : nil; +} + +/** + * Returns the selected favorite node or nil if nothing is selected. + */ +- (SPTreeNode *)selectedFavoriteNode +{ + NSArray *nodes = [self selectedFavoriteNodes]; + + return ([nodes count]) ? (SPTreeNode *)[[self selectedFavoriteNodes] objectAtIndex:0] : nil; +} + +/** + * Returns an array of selected favorite nodes. */ -- (id)selectedFavorite +- (NSArray *)selectedFavoriteNodes { - if ([favoritesTable selectedRow] == -1) return nil; + NSMutableArray *nodes = [NSMutableArray array]; + NSIndexSet *indexes = [favoritesOutlineView selectedRowIndexes]; - return [favorites objectAtIndex:([favoritesTable selectedRow] - 1)]; + NSUInteger currentIndex = [indexes firstIndex]; + + while (currentIndex != NSNotFound) + { + [nodes addObject:[favoritesOutlineView itemAtRow:currentIndex]]; + + currentIndex = [indexes indexGreaterThanIndex:currentIndex]; + } + + return nodes; } /** - * Adds the current details as a new favorite, select it, and scroll the selected - * row to visible. + * Adds a new connection favorite. */ - (IBAction)addFavorite:(id)sender { - NSString *thePassword, *theSSHPassword; - NSNumber *favoriteid = [NSNumber numberWithInteger:[[NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]] hash]]; - NSString *favoriteName = [[self name] length] ? [self name]:[NSString stringWithFormat:@"%@@%@", ([self user] && [[self user] length])?[self user] : @"anonymous", (([self type] == SPSocketConnection) ? @"localhost" : [self host])]; + NSNumber *favoriteID = [self _createNewFavoriteID]; + + NSArray *objects = [NSArray arrayWithObjects:NSLocalizedString(@"New Favorite", @"new favorite name"), + [NSNumber numberWithInteger:0], @"", @"", @"", @"", + [NSNumber numberWithInt:NSOffState], + [NSNumber numberWithInt:NSOffState], + [NSNumber numberWithInt:NSOffState], + [NSNumber numberWithInt:NSOffState], @"", @"", @"", + [NSNumber numberWithInt:NSOffState], @"", @"", favoriteID, nil]; + + NSArray *keys = [NSArray arrayWithObjects: + SPFavoriteNameKey, + SPFavoriteTypeKey, + SPFavoriteHostKey, + SPFavoriteSocketKey, + SPFavoriteUserKey, + SPFavoritePortKey, + SPFavoriteUseSSLKey, + SPFavoriteSSLKeyFileLocationEnabledKey, + SPFavoriteSSLCertificateFileLocationEnabledKey, + SPFavoriteSSLCACertFileLocationEnabledKey, + SPFavoriteDatabaseKey, + SPFavoriteSSHHostKey, + SPFavoriteSSHUserKey, + SPFavoriteSSHKeyLocationEnabledKey, + SPFavoriteSSHKeyLocationKey, + SPFavoriteSSHPortKey, + SPFavoriteIDKey, + nil]; + + // Create default favorite + NSMutableDictionary *favorite = [NSMutableDictionary dictionaryWithObjects:objects forKeys:keys]; + + SPTreeNode *selectedNode = [self selectedFavoriteNode]; + + SPTreeNode *parent = ([selectedNode isGroup]) ? selectedNode : [selectedNode parentNode]; + + SPTreeNode *node = [favoritesController addFavoriteNodeWithData:favorite asChildOfNode:parent]; + + [self _reloadFavoritesViewData]; + [self _selectNode:node]; + + [[[[NSApp delegate] preferenceController] generalPreferencePane] updateDefaultFavoritePopup]; + + favoriteNameFieldWasTouched = NO; + + [favoritesOutlineView editColumn:0 row:[favoritesOutlineView selectedRow] withEvent:nil select:YES]; +} +/** + * Adds the current details as a new connection favorite, selects it, and scrolls the selected + * row to be visible. + */ +- (IBAction)addFavoriteUsingCurrentDetails:(id)sender +{ + NSString *thePassword, *theSSHPassword; + NSNumber *favoriteid = [self _createNewFavoriteID]; + NSString *favoriteName = [[self name] length] ? [self name] : [NSString stringWithFormat:@"%@@%@", ([self user] && [[self user] length])?[self user] : @"anonymous", (([self type] == SPSocketConnection) ? @"localhost" : [self host])]; + if (![[self name] length] && [self database] && ![[self database] isEqualToString:@""]) { favoriteName = [NSString stringWithFormat:@"%@ %@", [self database], favoriteName]; } - + // Ensure that host is not empty if this is a TCP/IP or SSH connection if (([self type] == SPTCPIPConnection || [self type] == SPSSHTunnelConnection) && ![[self host] length]) { NSRunAlertPanel(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), NSLocalizedString(@"Insufficient details provided to establish a connection. Please provide at least a host.", @"insufficient details informative message"), NSLocalizedString(@"OK", @"OK button"), nil, nil); return; } - + // If SSH is enabled, ensure that the SSH host is not nil if ([self type] == SPSSHTunnelConnection && ![[self sshHost] length]) { NSRunAlertPanel(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), NSLocalizedString(@"Please enter the hostname for the SSH Tunnel, or disable the SSH Tunnel.", @"message of panel when ssh details are incomplete"), NSLocalizedString(@"OK", @"OK button"), nil, nil); @@ -1006,22 +752,22 @@ } // Ensure that a socket connection is not inadvertently used - if (![self checkHost]) return; - + if (![self _checkHost]) return; + // Construct the favorite details - cannot use only dictionaryWithObjectsAndKeys for possible nil values. NSMutableDictionary *newFavorite = [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInteger:[self type]], SPFavoriteTypeKey, favoriteName, SPFavoriteNameKey, favoriteid, SPFavoriteIDKey, nil]; - + // Standard details if ([self host]) [newFavorite setObject:[self host] forKey:SPFavoriteHostKey]; if ([self socket]) [newFavorite setObject:[self socket] forKey:SPFavoriteSocketKey]; if ([self user]) [newFavorite setObject:[self user] forKey:SPFavoriteUserKey]; if ([self port]) [newFavorite setObject:[self port] forKey:SPFavoritePortKey]; if ([self database]) [newFavorite setObject:[self database] forKey:SPFavoriteDatabaseKey]; - + // SSL details if ([self useSSL]) [newFavorite setObject:[NSNumber numberWithInt:[self useSSL]] forKey:SPFavoriteUseSSLKey]; [newFavorite setObject:[NSNumber numberWithInt:[self sslKeyFileLocationEnabled]] forKey:SPFavoriteSSLKeyFileLocationEnabledKey]; @@ -1030,7 +776,7 @@ if ([self sslCertificateFileLocation]) [newFavorite setObject:[self sslCertificateFileLocation] forKey:SPFavoriteSSLCertificateFileLocationKey]; [newFavorite setObject:[NSNumber numberWithInt:[self sslCACertFileLocationEnabled]] forKey:SPFavoriteSSLCACertFileLocationEnabledKey]; if ([self sslCACertFileLocation]) [newFavorite setObject:[self sslCACertFileLocation] forKey:SPFavoriteSSLCACertFileLocationKey]; - + // SSH details if ([self sshHost]) [newFavorite setObject:[self sshHost] forKey:SPFavoriteSSHHostKey]; if ([self sshUser]) [newFavorite setObject:[self sshUser] forKey:SPFavoriteSSHUserKey]; @@ -1038,22 +784,13 @@ [newFavorite setObject:[NSNumber numberWithInt:[self sshKeyLocationEnabled]] forKey:SPFavoriteSSHKeyLocationEnabledKey]; if ([self sshKeyLocation]) [newFavorite setObject:[self sshKeyLocation] forKey:SPFavoriteSSHKeyLocationKey]; - // Add the new favorite to the user defaults array - NSMutableArray *currentFavorites = ([prefs objectForKey:SPFavorites]) ? [[NSMutableArray alloc] initWithArray:[prefs objectForKey:SPFavorites]] : [[NSMutableArray alloc] init]; - - [currentFavorites addObject:newFavorite]; - - [prefs setObject:[NSArray arrayWithArray:currentFavorites] forKey:SPFavorites]; - - [currentFavorites release]; - // Add the password to keychain as appropriate thePassword = [self password]; - + if (mySQLConnection && connectionKeychainItemName) { thePassword = [keychain getPasswordForName:connectionKeychainItemName account:connectionKeychainItemAccount]; } - + if (thePassword && (![thePassword isEqualToString:@""])) { [keychain addPassword:thePassword forName:[keychain nameForFavoriteName:favoriteName id:[NSString stringWithFormat:@"%lld", [favoriteid longLongValue]]] @@ -1062,71 +799,367 @@ // Add the SSH password to keychain as appropriate theSSHPassword = [self sshPassword]; - + if (mySQLConnection && connectionSSHKeychainItemName) { theSSHPassword = [keychain getPasswordForName:connectionSSHKeychainItemName account:connectionSSHKeychainItemAccount]; } - + if (theSSHPassword && (![theSSHPassword isEqualToString:@""])) { [keychain addPassword:theSSHPassword forName:[keychain nameForSSHForFavoriteName:favoriteName id:[NSString stringWithFormat:@"%lld", [favoriteid longLongValue]]] account:[keychain accountForSSHUser:[self sshUser] sshHost:[self sshHost]]]; } - - // Update the favorites list and selection - [self updateFavorites]; - - [favoritesTable selectRowIndexes:[NSIndexSet indexSetWithIndex:[favorites count]-1] byExtendingSelection:NO]; - [favoritesTable scrollRowToVisible:[favoritesTable selectedRow]]; + + SPTreeNode *node = [favoritesController addFavoriteNodeWithData:newFavorite asChildOfNode:nil]; + + [self _reloadFavoritesViewData]; + [self _selectNode:node]; // Update the favorites popup button in the preferences [[[[NSApp delegate] preferenceController] generalPreferencePane] updateDefaultFavoritePopup]; } /** - * If the favorites list in the preferences change, trigger a reload of - * the favorites table data. + * Adds a new group node to the favorites tree with a default name. Once added it is selected for editing. */ -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +- (IBAction)addGroup:(id)sender +{ + SPTreeNode *selectedNode = [self selectedFavoriteNode]; + + SPTreeNode *parent = ([selectedNode isGroup]) ? selectedNode : [selectedNode parentNode]; + + SPTreeNode *node = [favoritesController addGroupNodeWithName:NSLocalizedString(@"New Folder", @"new folder placeholder name") asChildOfNode:parent]; + + [self _reloadFavoritesViewData]; + [self _selectNode:node]; + + [favoritesOutlineView editColumn:0 row:[favoritesOutlineView selectedRow] withEvent:nil select:YES]; +} + +/** + * Removes the selected node. + */ +- (IBAction)removeNode:(id)sender { - if ([keyPath isEqualToString:SPFavorites]) { - [self updateFavorites]; + if ([favoritesOutlineView numberOfSelectedRows] == 1) { + + SPTreeNode *node = [self selectedFavoriteNode]; + + NSString *message = @""; + NSString *informativeMessage = @""; + + if (![node isGroup]) { + message = [NSString stringWithFormat:NSLocalizedString(@"Delete favorite '%@'?", @"delete database message"), [[[node representedObject] nodeFavorite] objectForKey:SPFavoriteNameKey]]; + informativeMessage = [NSString stringWithFormat:NSLocalizedString(@"Are you sure you want to delete the favorite '%@'? This operation cannot be undone.", @"delete database informative message"), [[[node representedObject] nodeFavorite] objectForKey:SPFavoriteNameKey]]; + } + else { + message = [NSString stringWithFormat:NSLocalizedString(@"Delete group '%@'?", @"delete database message"), [[node representedObject] nodeName]]; + informativeMessage = [NSString stringWithFormat:NSLocalizedString(@"Are you sure you want to delete the group '%@'? All favorites within this group will also be deleted. This operation cannot be undone.", @"delete database informative message"), [[node representedObject] nodeName]]; + } + + NSAlert *alert = [NSAlert alertWithMessageText:message + defaultButton:NSLocalizedString(@"Delete", @"delete button") + alternateButton:NSLocalizedString(@"Cancel", @"cancel button") + otherButton:nil + informativeTextWithFormat:informativeMessage]; + + NSArray *buttons = [alert buttons]; + + // Change the alert's cancel button to have the key equivalent of return + [[buttons objectAtIndex:0] setKeyEquivalent:@"d"]; + [[buttons objectAtIndex:0] setKeyEquivalentModifierMask:NSCommandKeyMask]; + [[buttons objectAtIndex:1] setKeyEquivalent:@"\r"]; + + [alert setAlertStyle:NSCriticalAlertStyle]; + + [alert beginSheetModalForWindow:[dbDocument parentWindow] + modalDelegate:self + didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) + contextInfo:SPRemoveNode]; + } +} + +/** + * Duplicates the selected connection favorite. + */ +- (IBAction)duplicateFavorite:(id)sender +{ + if ([favoritesOutlineView numberOfSelectedRows] == 1) { + + NSMutableDictionary *favorite = [NSMutableDictionary dictionaryWithDictionary:[self selectedFavorite]]; + + NSNumber *favoriteID = [self _createNewFavoriteID]; + + NSInteger duplicatedFavoriteType = [[favorite objectForKey:SPFavoriteTypeKey] integerValue]; + + // Select the keychain passwords for duplication + NSString *keychainName = [keychain nameForFavoriteName:[favorite objectForKey:SPFavoriteNameKey] id:[favorite objectForKey:SPFavoriteIDKey]]; + NSString *keychainAccount = [keychain accountForUser:[favorite objectForKey:SPFavoriteUserKey] host:((duplicatedFavoriteType == SPSocketConnection) ? @"localhost" : [favorite objectForKey:SPFavoriteHostKey]) database:[favorite objectForKey:SPFavoriteDatabaseKey]]; + NSString *favoritePassword = [keychain getPasswordForName:keychainName account:keychainAccount]; + NSString *keychainSSHName = [keychain nameForSSHForFavoriteName:[favorite objectForKey:SPFavoriteNameKey] id:[favorite objectForKey:SPFavoriteIDKey]]; + NSString *keychainSSHAccount = [keychain accountForSSHUser:[favorite objectForKey:SPFavoriteSSHUserKey] sshHost:[favorite objectForKey:SPFavoriteSSHHostKey]]; + NSString *favoriteSSHPassword = [keychain getPasswordForName:keychainSSHName account:keychainSSHAccount]; + + // Update the unique ID + [favorite setObject:favoriteID forKey:SPFavoriteIDKey]; + + // Alter the name for clarity + [favorite setObject:[NSString stringWithFormat:NSLocalizedString(@"%@ Copy", @"Initial favourite name after duplicating a previous favourite"), [favorite objectForKey:SPFavoriteNameKey]] forKey:SPFavoriteNameKey]; + + // Create new keychain items if appropriate + if (password && [password length]) { + keychainName = [keychain nameForFavoriteName:[favorite objectForKey:SPFavoriteNameKey] id:[favorite objectForKey:SPFavoriteIDKey]]; + [keychain addPassword:favoritePassword forName:keychainName account:keychainAccount]; + } + + if (sshPassword && [sshPassword length]) { + keychainSSHName = [keychain nameForSSHForFavoriteName:[favorite objectForKey:SPFavoriteNameKey] id:[favorite objectForKey:SPFavoriteIDKey]]; + [keychain addPassword:favoriteSSHPassword forName:keychainSSHName account:keychainSSHAccount]; + } + + favoritePassword = nil, favoriteSSHPassword = nil; + + SPTreeNode *selectedNode = [self selectedFavoriteNode]; + + SPTreeNode *parent = ([selectedNode isGroup]) ? selectedNode : [selectedNode parentNode]; + + SPTreeNode *node = [favoritesController addFavoriteNodeWithData:favorite asChildOfNode:parent]; + + [self _reloadFavoritesViewData]; + [self _selectNode:node]; + + [[(SPPreferenceController *)[[NSApp delegate] preferenceController] generalPreferencePane] updateDefaultFavoritePopup]; } } +/** + * Switches the selected favorite/group to editing mode so it can be renamed. + */ +- (IBAction)renameFavorite:(id)sender +{ + if ([favoritesOutlineView numberOfSelectedRows] == 1) { + [favoritesOutlineView editColumn:0 row:[favoritesOutlineView selectedRow] withEvent:nil select:YES]; + } +} + +/** + * Marks the selected favorite as the default. + */ +- (IBAction)makeSelectedFavoriteDefault:(id)sender +{ + NSInteger favoriteID = [[[self selectedFavorite] objectForKey:SPFavoriteIDKey] integerValue]; + + [prefs setInteger:favoriteID forKey:SPDefaultFavorite]; +} + #pragma mark - -#pragma mark Menu Validation +#pragma mark Import/export favorites --(BOOL)validateMenuItem:(NSMenuItem *)menuItem +/** + * Displays an open panel, allowing the user to import their favorites. + */ +- (IBAction)importFavorites:(id)sender { - SEL action = [menuItem action]; - if ((action == @selector(sortFavorites:)) || (action == @selector(reverseSortFavorites:))) { + NSOpenPanel *openPanel = [NSOpenPanel openPanel]; + + [openPanel beginSheetForDirectory:nil + file:nil + types:nil + modalForWindow:[dbDocument parentWindow] + modalDelegate:self + didEndSelector:@selector(importExportFavoritesSheetDidEnd:returnCode:contextInfo:) + contextInfo:SPImportFavorites]; +} - // Loop all the items in the sort by menu only checking the currently selected one - for (NSMenuItem *item in [[menuItem menu] itemArray]) - { - [item setState:([[menuItem menu] indexOfItem:item] == currentSortItem) ? NSOnState : NSOffState]; +/** + * Displays a save panel, allowing the user to export their favorites. + */ +- (IBAction)exportFavorites:(id)sender +{ + NSSavePanel *savePanel = [NSSavePanel savePanel]; + + [savePanel beginSheetForDirectory:nil + file:SPExportFavoritesFilename + modalForWindow:[dbDocument parentWindow] + modalDelegate:self + didEndSelector:@selector(importExportFavoritesSheetDidEnd:returnCode:contextInfo:) + contextInfo:SPExportFavorites]; +} + +#pragma mark - +#pragma mark Key Value Observing + +/** + * This method is called as part of Key Value Observing. + */ +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + id oldObject = [change objectForKey:NSKeyValueChangeOldKey]; + id newObject = [change objectForKey:NSKeyValueChangeNewKey]; + + if (oldObject != newObject) { + NSMutableDictionary *favorite = [self selectedFavorite]; + + [favorite setObject:(newObject) ? newObject : @"" forKey:keyPath]; + + // Save the new data to disk + [favoritesController saveFavorites]; + + [self _reloadFavoritesViewData]; + } +} + +#pragma mark - +#pragma mark Sheet methods + +/** + * Called when the user dismisses the remove node sheet. + */ +- (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo +{ + // Remove the current favorite/group + if ([contextInfo isEqualToString:SPRemoveNode]) { + if (returnCode == NSAlertDefaultReturn) { + + NSDictionary *favorite = [self selectedFavorite]; + + // Get selected favorite's details + NSString *favoriteName = [favorite objectForKey:SPFavoriteNameKey]; + NSString *favoriteUser = [favorite objectForKey:SPFavoriteUserKey]; + NSString *favoriteHost = [favorite objectForKey:SPFavoriteHostKey]; + NSString *favoriteDatabase = [favorite objectForKey:SPFavoriteDatabaseKey]; + NSString *favoriteSSHUser = [favorite objectForKey:SPFavoriteSSHUserKey]; + NSString *favoriteSSHHost = [favorite objectForKey:SPFavoriteSSHHostKey]; + NSString *favoriteID = [favorite objectForKey:SPFavoriteIDKey]; + + NSInteger favoriteType = [[favorite objectForKey:SPFavoriteTypeKey] integerValue]; + + // Remove passwords from the Keychain + [keychain deletePasswordForName:[keychain nameForFavoriteName:favoriteName id:favoriteID] + account:[keychain accountForUser:favoriteUser host:((type == SPSocketConnection) ? @"localhost" : favoriteHost) database:favoriteDatabase]]; + [keychain deletePasswordForName:[keychain nameForSSHForFavoriteName:favoriteName id:favoriteID] + account:[keychain accountForSSHUser:favoriteSSHUser sshHost:favoriteSSHHost]]; + + // Reset last used favorite + if ([[favorite objectForKey:SPFavoriteIDKey] integerValue] == [prefs integerForKey:SPLastFavoriteID]) { + [prefs setInteger:0 forKey:SPLastFavoriteID]; + } + + // Reset default favorite + if ([[favorite objectForKey:SPFavoriteIDKey] integerValue] == [prefs integerForKey:SPDefaultFavorite]) { + [prefs setInteger:[prefs integerForKey:SPLastFavoriteID] forKey:SPDefaultFavorite]; + } + + [favoritesController removeFavoriteNode:[self selectedFavoriteNode]]; + + [self _reloadFavoritesViewData]; + + [[(SPPreferenceController *)[[NSApp delegate] preferenceController] generalPreferencePane] updateDefaultFavoritePopup]; } + } +} - // Check or uncheck the reverse sort item - if (action == @selector(reverseSortFavorites:)) { - [menuItem setState:reverseFavoritesSort]; +/** + * Called after closing the SSH/SSL key selection sheet. + */ +- (void)chooseKeyLocationSheetDidEnd:(NSOpenPanel *)openPanel returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo +{ + NSString *abbreviatedFileName = [[openPanel filename] stringByAbbreviatingWithTildeInPath]; + + // SSH key file selection + if (contextInfo == sshSSHKeyButton) { + if (returnCode == NSCancelButton) { + [self setSshKeyLocationEnabled:NSOffState]; + return; + } + + [self setSshKeyLocation:abbreviatedFileName]; + } + // SSL key file selection + else if (contextInfo == standardSSLKeyFileButton || contextInfo == socketSSLKeyFileButton) { + if (returnCode == NSCancelButton) { + [self setSslKeyFileLocationEnabled:NSOffState]; + [self setSslKeyFileLocation:nil]; + return; + } + + [self setSslKeyFileLocation:abbreviatedFileName]; + } + // SSL certificate file selection + else if (contextInfo == standardSSLCertificateButton || contextInfo == socketSSLCertificateButton) { + if (returnCode == NSCancelButton) { + [self setSslCertificateFileLocationEnabled:NSOffState]; + [self setSslCertificateFileLocation:nil]; + return; + } + + [self setSslCertificateFileLocation:abbreviatedFileName]; + } + // SSL CA certificate file selection + else if (contextInfo == standardSSLCACertButton || contextInfo == socketSSLCACertButton) { + if (returnCode == NSCancelButton) { + [self setSslCACertFileLocationEnabled:NSOffState]; + [self setSslCACertFileLocation:nil]; + return; } - } - return YES; + + [self setSslCACertFileLocation:abbreviatedFileName]; + } +} +/** + * Called when the user dismisses either the import of export favorites panels. + */ +- (void)importExportFavoritesSheetDidEnd:(NSOpenPanel *)openPanel returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo +{ + +} + +/** + * Alert sheet callback method - invoked when the error sheet is closed. + */ +- (void)localhostErrorSheetDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo +{ + if (returnCode == NSAlertAlternateReturn) { + [self setType:SPSocketConnection]; + [self setHost:@""]; + } + else { + [self setHost:@"127.0.0.1"]; + } } #pragma mark - #pragma mark Private API +/** + * Check the host field and ensure it isn't set to 'localhost' for non-socket connections. + */ +- (BOOL)_checkHost +{ + if ([self type] != SPSocketConnection && [[self host] isEqualToString:@"localhost"]) { + SPBeginAlertSheet(NSLocalizedString(@"You have entered 'localhost' for a non-socket connection", @"title of error when using 'localhost' for a network connection"), + NSLocalizedString(@"Use 127.0.0.1", @"Use 127.0.0.1 button"), // Main button + NSLocalizedString(@"Connect via socket", @"Connect via socket button"), // Alternate button + nil, // Other button + [dbDocument parentWindow], // Window to attach to + self, // Modal delegate + @selector(localhostErrorSheetDidEnd:returnCode:contextInfo:), // Did end selector + nil, // Contextual info for selectors + NSLocalizedString(@"To MySQL, 'localhost' is a special host and means that a socket connection should be used.\n\nDid you mean to use a socket connection, or to connect to the local machine via a port? If you meant to connect via a port, '127.0.0.1' should be used instead of 'localhost'.", @"message of error when using 'localhost' for a network connection")); + return NO; + } + + return YES; +} + /** * Sorts the connection favorites based on the selected criteria. */ - (void)_sortFavorites { NSString *sortKey = SPFavoriteNameKey; - + switch (currentSortItem) { case SPFavoritesSortNameItem: @@ -1139,61 +1172,57 @@ sortKey = SPFavoriteTypeKey; break; } - + NSSortDescriptor *sortDescriptor = nil; - + if (currentSortItem == SPFavoritesSortTypeItem) { sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:sortKey ascending:(!reverseFavoritesSort)] autorelease]; } else { sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:sortKey ascending:(!reverseFavoritesSort) selector:@selector(caseInsensitiveCompare:)] autorelease]; } - - NSDictionary *first = [[favorites objectAtIndex:0] retain]; - - [favorites removeObjectAtIndex:0]; - [favorites sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]]; - [favorites insertObject:first atIndex:0]; - - // Rebuild the favorites tree - [self _buildFavoritesTree]; - - [favoritesTable reloadData]; - - [favoritesTable expandItem:[[favoritesRoot nodeChildren] objectAtIndex:0]]; - - [first release]; + + // TODO: Perform actual sorting here + + [self _reloadFavoritesViewData]; } /** - * Builds a tree structure from the user's connection favorties by wrapping them in SPFavoriteNode instances. + * Updates the favorite's host when the type changes. */ -- (void)_buildFavoritesTree +- (void)_favoriteTypeDidChange { - if (favoritesRoot) [favoritesRoot release], favoritesRoot = nil; - - favoritesRoot = [[SPFavoriteNode alloc] init]; - - // Add a dummy item to represent the favorites heading - SPFavoriteNode *favoritesNode = [[SPFavoriteNode alloc] init]; - - [favoritesNode setNodeIsGroup:YES]; - [favoritesNode setNodeName:NSLocalizedString(@"FAVORITES", @"Favorites title at the top of the sidebar")]; - - for (NSDictionary *favorite in favorites) - { - SPFavoriteNode *node2 = [[SPFavoriteNode alloc] init]; - - [node2 setNodeFavorite:favorite]; - - [[favoritesNode nodeChildren] addObject:node2]; - - [node2 release]; + // TODO: Handle changing favorite connection types + + /*NSDictionary *favorite = [[[self selectedFavoriteNode] representedObject] nodeFavorite]; + + // If either socket or host is localhost, clear. + if ((selectedTabView != SPSocketConnection) && [[favorite objectForKey:SPFavoriteHostKey] isEqualToString:@"localhost"]) { + [self setHost:@""]; } + + // Update the name for newly added favorites if not already touched by the user, by trigger a KVO update + if (!favoriteNameFieldWasTouched) { + [self setName:[NSString stringWithFormat:@"%@@%@", + ([favorite objectForKey:SPFavoriteUserKey]) ? [favorite objectForKey:SPFavoriteUserKey] : @"", + ((previousType == SPSocketConnection) ? @"localhost" : + (([favorite objectForKey:SPFavoriteHostKey]) ? [favorite valueForKeyPath:SPFavoriteHostKey] : @"")) + ]]; + } + + // Request a password refresh to keep keychain references in synch with the favorites + [self _updateFavoritePasswordsFromField:nil];*/ +} - [[favoritesRoot nodeChildren] addObject:favoritesNode]; - - [favoritesNode release]; +/** + * Convenience method for rebuilding the connection favorites tree, reloading the outline view, expanding the + * items and scrolling to the selected item. + */ +- (void)_reloadFavoritesViewData +{ + [favoritesOutlineView reloadData]; + [favoritesOutlineView expandItem:[[favoritesRoot childNodes] objectAtIndex:0] expandChildren:YES]; + [favoritesOutlineView scrollRowToVisible:[favoritesOutlineView selectedRow]]; } /** @@ -1203,13 +1232,13 @@ { // Must be performed on the main thread if (![NSThread isMainThread]) return [[self onMainThread] _restoreConnectionInterface]; - + // Reset the window title - [[tableDocument parentWindow] setTitle:[tableDocument displayName]]; - + [[dbDocument parentWindow] setTitle:[dbDocument displayName]]; + // Stop the current tab's progress indicator - [tableDocument setIsProcessing:NO]; - + [dbDocument setIsProcessing:NO]; + // Reset the UI [addToFavoritesButton setHidden:NO]; [addToFavoritesButton display]; @@ -1222,47 +1251,56 @@ [progressIndicator display]; [progressIndicatorText setHidden:YES]; [progressIndicatorText display]; - + // Re-enable favorites table view - [favoritesTable setEnabled:YES]; - [favoritesTable display]; - + [favoritesOutlineView setEnabled:YES]; + [favoritesOutlineView display]; + mySQLConnectionCancelled = NO; - + // Revert the connect button back to its original selector [connectButton setAction:@selector(initiateConnection:)]; } +/** + * Selected the supplied node in the favorites outline view. + */ +- (void)_selectNode:(SPTreeNode *)node +{ + [favoritesOutlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:[favoritesOutlineView rowForItem:node]] byExtendingSelection:NO]; + [favoritesOutlineView scrollRowToVisible:[favoritesOutlineView selectedRow]]; +} + /** * Called on the main thread once the MySQL connection is established on the background thread. Either the - * connection was cancelled or it was successful. + * connection was cancelled or it was successful. */ - (void)_mySQLConnectionEstablished -{ +{ isConnecting = NO; - - // If the user hit cancel during the connection attempt, kill the connection once + + // If the user hit cancel during the connection attempt, kill the connection once // established and reset the UI. - if (mySQLConnectionCancelled) { + if (mySQLConnectionCancelled) { if ([mySQLConnection isConnected]) { [mySQLConnection disconnect]; [mySQLConnection release], mySQLConnection = nil; } - + // Kill the SSH connection if present [self cancelConnection]; - + [self _restoreConnectionInterface]; - + return; } - + [progressIndicatorText setStringValue:NSLocalizedString(@"Connected", @"connection established message")]; [progressIndicatorText display]; - + // Stop the current tab's progress indicator - [tableDocument setIsProcessing:NO]; - + [dbDocument setIsProcessing:NO]; + // Successful connection! [connectButton setEnabled:NO]; [connectButton display]; @@ -1273,19 +1311,20 @@ // If SSL was enabled, check it was established correctly if (useSSL && ([self type] == SPTCPIPConnection || [self type] == SPSocketConnection)) { if (![mySQLConnection isConnectedViaSSL]) { - SPBeginAlertSheet(NSLocalizedString(@"SSL connection not established", @"SSL requested but not used title"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocument parentWindow], nil, nil, nil, NSLocalizedString(@"You requested that the connection should be established using SSL, but MySQL made the connection without SSL.\n\nThis may be because the server does not support SSL connections, or has SSL disabled; or insufficient details were supplied to establish an SSL connection.\n\nThis connection is not encrypted.", @"SSL connection requested but not established error detail")); - } else { - [tableDocument setStatusIconToImageWithName:@"titlebarlock"]; + SPBeginAlertSheet(NSLocalizedString(@"SSL connection not established", @"SSL requested but not used title"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], nil, nil, nil, NSLocalizedString(@"You requested that the connection should be established using SSL, but MySQL made the connection without SSL.\n\nThis may be because the server does not support SSL connections, or has SSL disabled; or insufficient details were supplied to establish an SSL connection.\n\nThis connection is not encrypted.", @"SSL connection requested but not established error detail")); + } + else { + [dbDocument setStatusIconToImageWithName:@"titlebarlock"]; } } // Re-enable favorites table view - [favoritesTable setEnabled:YES]; - [favoritesTable display]; - + [favoritesOutlineView setEnabled:YES]; + [favoritesOutlineView display]; + // Release the tunnel if set - will now be retained by the connection if (sshTunnel) [sshTunnel release], sshTunnel = nil; - + // Pass the connection to the document and clean up the interface [self addConnectionToDocument]; } @@ -1296,11 +1335,11 @@ - (void)_initiateMySQLConnectionInBackground { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - + // Initialise to socket if appropriate. if ([self type] == SPSocketConnection) { mySQLConnection = [[MCPConnection alloc] initToSocket:[self socket] withLogin:[self user]]; - + // Otherwise, initialise to host, using tunnel if appropriate } else { if ([self type] == SPSSHTunnelConnection) { @@ -1311,11 +1350,11 @@ } else { mySQLConnection = [[MCPConnection alloc] initToHost:[self host] withLogin:[self user] - usingPort:([[self port] length]?[[self port] integerValue]:3306)]; + usingPort:([[self port] length] ? [[self port] integerValue] : 3306)]; } } - - // Only set the password if there is no Keychain item set. The connection will ask the delegate for passwords in the Keychain. + + // Only set the password if there is no Keychain item set. The connection will ask the delegate for passwords in the Keychain. if (!connectionKeychainItemName && [self password]) { [mySQLConnection setPassword:[self password]]; } @@ -1327,28 +1366,28 @@ certificatePath:[self sslCertificateFileLocationEnabled] ? [self sslCertificateFileLocation] : nil certificateAuthorityCertificatePath:[self sslCACertFileLocationEnabled] ? [self sslCACertFileLocation] : nil]; } - + // Connection delegate must be set before actual connection attempt is made - [mySQLConnection setDelegate:tableDocument]; + [mySQLConnection setDelegate:dbDocument]; // Set whether or not we should enable delegate logging according to the prefs [mySQLConnection setDelegateQueryLogging:[prefs boolForKey:SPConsoleEnableLogging]]; - + // Set options from preferences [mySQLConnection setConnectionTimeout:[[prefs objectForKey:SPConnectionTimeoutValue] integerValue]]; [mySQLConnection setUseKeepAlive:[[prefs objectForKey:SPUseKeepAlive] boolValue]]; [mySQLConnection setKeepAliveInterval:[[prefs objectForKey:SPKeepAliveInterval] doubleValue]]; - + // Connect [mySQLConnection connect]; - + if (![mySQLConnection isConnected]) { if (sshTunnel) { - + // If an SSH tunnel is running, temporarily block to allow the tunnel to register changes in state [[NSRunLoop currentRunLoop] runMode:NSModalPanelRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.2]]; - + // If the state is connection refused, attempt the MySQL connection again with the host using the hostfield value. if ([sshTunnel state] == PROXY_STATE_FORWARDING_FAILED) { if ([sshTunnel localPortFallback]) { @@ -1360,7 +1399,7 @@ } } } - + if (![mySQLConnection isConnected]) { NSString *errorMessage = @""; if (sshTunnel && [sshTunnel state] == PROXY_STATE_FORWARDING_FAILED) { @@ -1379,56 +1418,183 @@ errorMessage = [NSString stringWithFormat:NSLocalizedString(@"Unable to connect to host %@, or the request timed out.\n\nBe sure that the address is correct and that you have the necessary privileges, or try increasing the connection timeout (currently %ld seconds).\n\nMySQL said: %@", @"message of panel when connection to host failed"), [self host], (long)[[prefs objectForKey:SPConnectionTimeoutValue] integerValue], [mySQLConnection getLastErrorMessage]]; [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Connection failed!", @"connection failed title") errorMessage:errorMessage detail:nil]; } - + // Tidy up isConnecting = NO; - + if (sshTunnel) [sshTunnel release], sshTunnel = nil; - + [mySQLConnection release], mySQLConnection = nil; [self _restoreConnectionInterface]; [pool release]; - + return; } } - + if ([self database] && ![[self database] isEqualToString:@""]) { if (![mySQLConnection selectDB:[self database]]) { [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Could not select database", @"message when database selection failed") errorMessage:[NSString stringWithFormat:NSLocalizedString(@"Connected to host, but unable to connect to database %@.\n\nBe sure that the database exists and that you have the necessary privileges.\n\nMySQL said: %@", @"message of panel when connection to db failed"), [self database], [mySQLConnection getLastErrorMessage]] detail:nil]; - + // Tidy up isConnecting = NO; - + if (sshTunnel) [sshTunnel release], sshTunnel = nil; - + [mySQLConnection release], mySQLConnection = nil; [self _restoreConnectionInterface]; [pool release]; - + return; } } - + // Connection established [self performSelectorOnMainThread:@selector(_mySQLConnectionEstablished) withObject:nil waitUntilDone:NO]; - + [pool release]; } -@end +/** + * Creates a new favorite ID based on the UNIX epoch time. + */ +- (NSNumber *)_createNewFavoriteID +{ + return [NSNumber numberWithInteger:[[NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]] hash]]; +} -#pragma mark - -#pragma mark NSView subclass - flipped view for simpler drawing +/** + * Returns the favorite node for the conection favorite with the supplied ID. + */ +- (SPTreeNode *)_favoriteNodeForFavoriteID:(NSInteger)favoriteID +{ + SPTreeNode *node = nil; + + if (!favoritesRoot) return; + + for (node in [favoritesRoot allChildLeafs]) + { + if ([[[[node representedObject] nodeFavorite] objectForKey:SPFavoriteIDKey] integerValue] == favoriteID) { + return node; + } + } +} /** - * Add an implementation of a flipped view to simplify drawing. + * Check all fields used in the keychain names against the old values for that + * favorite, and update the keychain names to match if necessary. + * If an (optional) recognised password field is supplied, that field is assumed + * to have changed and is used to supply the new value. */ -@implementation SPFlippedView: NSView +- (void)_updateFavoritePasswordsFromField:(NSControl *)control +{ + if (!currentFavorite) return; + + NSDictionary *oldFavorite = [currentFavorite nodeFavorite]; + NSDictionary *newFavorite = [[[self selectedFavoriteNode] representedObject] nodeFavorite]; + + NSString *passwordValue; + NSString *oldKeychainName, *newKeychainName; + NSString *oldKeychainAccount, *newKeychainAccount; + NSString *oldHostnameForPassword = ([[oldFavorite objectForKey:SPFavoriteTypeKey] integerValue] == SPSocketConnection) ? @"localhost" : [oldFavorite objectForKey:SPFavoriteHostKey]; + NSString *newHostnameForPassword = ([[newFavorite objectForKey:SPFavoriteTypeKey] integerValue] == SPSocketConnection) ? @"localhost" : [newFavorite objectForKey:SPFavoriteHostKey]; + + // SQL passwords are indexed by name, host, user and database. If any of these + // have changed, or a standard password field has, alter the keychain item to match. + if (![[oldFavorite objectForKey:SPFavoriteNameKey] isEqualToString:[newFavorite objectForKey:SPFavoriteNameKey]] || + ![oldHostnameForPassword isEqualToString:newHostnameForPassword] || + ![[oldFavorite objectForKey:SPFavoriteUserKey] isEqualToString:[newFavorite objectForKey:SPFavoriteUserKey]] || + ![[oldFavorite objectForKey:SPFavoriteDatabaseKey] isEqualToString:[newFavorite objectForKey:SPFavoriteDatabaseKey]] || + control == standardPasswordField || control == socketPasswordField || control == sshPasswordField) + { + // Determine the correct password field to read the password from, defaulting to standard + if (control == socketPasswordField) { + passwordValue = [socketPasswordField stringValue]; + } + else if (control == sshPasswordField) { + passwordValue = [sshPasswordField stringValue]; + } + else { + passwordValue = [standardPasswordField stringValue]; + } + + // Get the old keychain name and account strings + oldKeychainName = [keychain nameForFavoriteName:[oldFavorite objectForKey:SPFavoriteNameKey] id:[newFavorite objectForKey:SPFavoriteIDKey]]; + oldKeychainAccount = [keychain accountForUser:[oldFavorite objectForKey:SPFavoriteUserKey] host:oldHostnameForPassword database:[oldFavorite objectForKey:SPFavoriteDatabaseKey]]; + + // Delete the old keychain item + [keychain deletePasswordForName:oldKeychainName account:oldKeychainAccount]; + + // Set up the new keychain name and account strings + newKeychainName = [keychain nameForFavoriteName:[newFavorite objectForKey:SPFavoriteNameKey] id:[newFavorite objectForKey:SPFavoriteIDKey]]; + newKeychainAccount = [keychain accountForUser:[newFavorite objectForKey:SPFavoriteUserKey] host:newHostnameForPassword database:[newFavorite objectForKey:SPFavoriteDatabaseKey]]; + + // Add the new keychain item if the password field has a value + if ([passwordValue length]) { + [keychain addPassword:passwordValue forName:newKeychainName account:newKeychainAccount]; + } + + // Synch password changes + [standardPasswordField setStringValue:passwordValue]; + [socketPasswordField setStringValue:passwordValue]; + [sshPasswordField setStringValue:passwordValue]; + + passwordValue = @""; + } + + // If SSH account/password details have changed, update the keychain to match + if (![[oldFavorite objectForKey:SPFavoriteNameKey] isEqualToString:[newFavorite objectForKey:SPFavoriteNameKey]] || + ![[oldFavorite objectForKey:SPFavoriteSSHHostKey] isEqualToString:[newFavorite objectForKey:SPFavoriteSSHHostKey]] || + ![[oldFavorite objectForKey:SPFavoriteSSHUserKey] isEqualToString:[newFavorite objectForKey:SPFavoriteSSHUserKey]] || + control == sshSSHPasswordField) + { + // Get the old keychain name and account strings + oldKeychainName = [keychain nameForSSHForFavoriteName:[oldFavorite objectForKey:SPFavoriteNameKey] id:[newFavorite objectForKey:SPFavoriteIDKey]]; + oldKeychainAccount = [keychain accountForSSHUser:[oldFavorite objectForKey:SPFavoriteSSHUserKey] sshHost:[oldFavorite objectForKey:SPFavoriteSSHHostKey]]; + + // Delete the old keychain item + [keychain deletePasswordForName:oldKeychainName account:oldKeychainAccount]; + + // Set up the new keychain name and account strings + newKeychainName = [keychain nameForSSHForFavoriteName:[newFavorite objectForKey:SPFavoriteNameKey] id:[newFavorite objectForKey:SPFavoriteIDKey]]; + newKeychainAccount = [keychain accountForSSHUser:[newFavorite objectForKey:SPFavoriteSSHUserKey] sshHost:[newFavorite objectForKey:SPFavoriteSSHHostKey]]; + + // Add the new keychain item if the password field has a value + if ([[sshPasswordField stringValue] length]) { + [keychain addPassword:[sshPasswordField stringValue] forName:newKeychainName account:newKeychainAccount]; + } + } + + // Update the current favorite + if (currentFavorite) [currentFavorite release], currentFavorite = nil; + + if ([[favoritesOutlineView selectedRowIndexes] count]) { + currentFavorite = [[[self selectedFavoriteNode] representedObject] copy]; + } +} -- (BOOL)isFlipped +#pragma mark - + +- (void)dealloc { - return YES; + [keychain release]; + [prefs release]; + + [folderImage release], folderImage = nil; + + for (id retainedObject in nibObjectsToRelease) [retainedObject release]; + + [nibObjectsToRelease release]; + + if (mySQLConnection) [mySQLConnection release]; + if (sshTunnel) [sshTunnel setConnectionStateChangeSelector:nil delegate:nil], [sshTunnel disconnect], [sshTunnel release]; + if (connectionKeychainItemName) [connectionKeychainItemName release]; + if (connectionKeychainItemAccount) [connectionKeychainItemAccount release]; + if (connectionSSHKeychainItemName) [connectionSSHKeychainItemName release]; + if (connectionSSHKeychainItemAccount) [connectionSSHKeychainItemAccount release]; + if (currentFavorite) [currentFavorite release], currentFavorite = nil; + + [super dealloc]; } @end -- cgit v1.2.3 From c91dd638605c4e4cee9343dce11abdf498e20532 Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sun, 5 Dec 2010 15:57:15 +0000 Subject: Only access Keychain items when necessary. --- Source/SPConnectionController.m | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 8c5c4896..4938c09a 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -892,14 +892,6 @@ static const NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; NSInteger duplicatedFavoriteType = [[favorite objectForKey:SPFavoriteTypeKey] integerValue]; - // Select the keychain passwords for duplication - NSString *keychainName = [keychain nameForFavoriteName:[favorite objectForKey:SPFavoriteNameKey] id:[favorite objectForKey:SPFavoriteIDKey]]; - NSString *keychainAccount = [keychain accountForUser:[favorite objectForKey:SPFavoriteUserKey] host:((duplicatedFavoriteType == SPSocketConnection) ? @"localhost" : [favorite objectForKey:SPFavoriteHostKey]) database:[favorite objectForKey:SPFavoriteDatabaseKey]]; - NSString *favoritePassword = [keychain getPasswordForName:keychainName account:keychainAccount]; - NSString *keychainSSHName = [keychain nameForSSHForFavoriteName:[favorite objectForKey:SPFavoriteNameKey] id:[favorite objectForKey:SPFavoriteIDKey]]; - NSString *keychainSSHAccount = [keychain accountForSSHUser:[favorite objectForKey:SPFavoriteSSHUserKey] sshHost:[favorite objectForKey:SPFavoriteSSHHostKey]]; - NSString *favoriteSSHPassword = [keychain getPasswordForName:keychainSSHName account:keychainSSHAccount]; - // Update the unique ID [favorite setObject:favoriteID forKey:SPFavoriteIDKey]; @@ -908,16 +900,28 @@ static const NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; // Create new keychain items if appropriate if (password && [password length]) { + NSString *keychainName = [keychain nameForFavoriteName:[favorite objectForKey:SPFavoriteNameKey] id:[favorite objectForKey:SPFavoriteIDKey]]; + NSString *keychainAccount = [keychain accountForUser:[favorite objectForKey:SPFavoriteUserKey] host:((duplicatedFavoriteType == SPSocketConnection) ? @"localhost" : [favorite objectForKey:SPFavoriteHostKey]) database:[favorite objectForKey:SPFavoriteDatabaseKey]]; + NSString *favoritePassword = [keychain getPasswordForName:keychainName account:keychainAccount]; + keychainName = [keychain nameForFavoriteName:[favorite objectForKey:SPFavoriteNameKey] id:[favorite objectForKey:SPFavoriteIDKey]]; + [keychain addPassword:favoritePassword forName:keychainName account:keychainAccount]; + + favoritePassword = nil; } if (sshPassword && [sshPassword length]) { + NSString *keychainSSHName = [keychain nameForSSHForFavoriteName:[favorite objectForKey:SPFavoriteNameKey] id:[favorite objectForKey:SPFavoriteIDKey]]; + NSString *keychainSSHAccount = [keychain accountForSSHUser:[favorite objectForKey:SPFavoriteSSHUserKey] sshHost:[favorite objectForKey:SPFavoriteSSHHostKey]]; + NSString *favoriteSSHPassword = [keychain getPasswordForName:keychainSSHName account:keychainSSHAccount]; + keychainSSHName = [keychain nameForSSHForFavoriteName:[favorite objectForKey:SPFavoriteNameKey] id:[favorite objectForKey:SPFavoriteIDKey]]; + [keychain addPassword:favoriteSSHPassword forName:keychainSSHName account:keychainSSHAccount]; - } - favoritePassword = nil, favoriteSSHPassword = nil; + favoriteSSHPassword = nil; + } SPTreeNode *selectedNode = [self selectedFavoriteNode]; -- cgit v1.2.3 From 71524d822dd449c131f6e09b2a0be0c3c0101eb1 Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Mon, 6 Dec 2010 22:18:39 +0000 Subject: Fix crash when deleting a group node. --- Source/SPConnectionController.m | 58 ++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 27 deletions(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 979f59bb..f7a09471 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -1025,36 +1025,40 @@ static const NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; if ([contextInfo isEqualToString:SPRemoveNode]) { if (returnCode == NSAlertDefaultReturn) { - NSDictionary *favorite = [self selectedFavorite]; + SPTreeNode *node = [self selectedFavoriteNode]; - // Get selected favorite's details - NSString *favoriteName = [favorite objectForKey:SPFavoriteNameKey]; - NSString *favoriteUser = [favorite objectForKey:SPFavoriteUserKey]; - NSString *favoriteHost = [favorite objectForKey:SPFavoriteHostKey]; - NSString *favoriteDatabase = [favorite objectForKey:SPFavoriteDatabaseKey]; - NSString *favoriteSSHUser = [favorite objectForKey:SPFavoriteSSHUserKey]; - NSString *favoriteSSHHost = [favorite objectForKey:SPFavoriteSSHHostKey]; - NSString *favoriteID = [favorite objectForKey:SPFavoriteIDKey]; - - NSInteger favoriteType = [[favorite objectForKey:SPFavoriteTypeKey] integerValue]; - - // Remove passwords from the Keychain - [keychain deletePasswordForName:[keychain nameForFavoriteName:favoriteName id:favoriteID] - account:[keychain accountForUser:favoriteUser host:((type == SPSocketConnection) ? @"localhost" : favoriteHost) database:favoriteDatabase]]; - [keychain deletePasswordForName:[keychain nameForSSHForFavoriteName:favoriteName id:favoriteID] - account:[keychain accountForSSHUser:favoriteSSHUser sshHost:favoriteSSHHost]]; - - // Reset last used favorite - if ([[favorite objectForKey:SPFavoriteIDKey] integerValue] == [prefs integerForKey:SPLastFavoriteID]) { - [prefs setInteger:0 forKey:SPLastFavoriteID]; - } - - // Reset default favorite - if ([[favorite objectForKey:SPFavoriteIDKey] integerValue] == [prefs integerForKey:SPDefaultFavorite]) { - [prefs setInteger:[prefs integerForKey:SPLastFavoriteID] forKey:SPDefaultFavorite]; + if (![node isGroup]) { + NSDictionary *favorite = [[node representedObject] nodeFavorite]; + + // Get selected favorite's details + NSString *favoriteName = [favorite objectForKey:SPFavoriteNameKey]; + NSString *favoriteUser = [favorite objectForKey:SPFavoriteUserKey]; + NSString *favoriteHost = [favorite objectForKey:SPFavoriteHostKey]; + NSString *favoriteDatabase = [favorite objectForKey:SPFavoriteDatabaseKey]; + NSString *favoriteSSHUser = [favorite objectForKey:SPFavoriteSSHUserKey]; + NSString *favoriteSSHHost = [favorite objectForKey:SPFavoriteSSHHostKey]; + NSString *favoriteID = [favorite objectForKey:SPFavoriteIDKey]; + + NSInteger favoriteType = [[favorite objectForKey:SPFavoriteTypeKey] integerValue]; + + // Remove passwords from the Keychain + [keychain deletePasswordForName:[keychain nameForFavoriteName:favoriteName id:favoriteID] + account:[keychain accountForUser:favoriteUser host:((type == SPSocketConnection) ? @"localhost" : favoriteHost) database:favoriteDatabase]]; + [keychain deletePasswordForName:[keychain nameForSSHForFavoriteName:favoriteName id:favoriteID] + account:[keychain accountForSSHUser:favoriteSSHUser sshHost:favoriteSSHHost]]; + + // Reset last used favorite + if ([[favorite objectForKey:SPFavoriteIDKey] integerValue] == [prefs integerForKey:SPLastFavoriteID]) { + [prefs setInteger:0 forKey:SPLastFavoriteID]; + } + + // Reset default favorite + if ([[favorite objectForKey:SPFavoriteIDKey] integerValue] == [prefs integerForKey:SPDefaultFavorite]) { + [prefs setInteger:[prefs integerForKey:SPLastFavoriteID] forKey:SPDefaultFavorite]; + } } - [favoritesController removeFavoriteNode:[self selectedFavoriteNode]]; + [favoritesController removeFavoriteNode:node]; [self _reloadFavoritesViewData]; -- cgit v1.2.3 From cecdee329489a304d94630662a24a6d0145eaa1a Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Wed, 8 Dec 2010 20:58:57 +0000 Subject: Update a connection favorite's name accordingly if it's type is changed. --- Source/SPConnectionController.m | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index f7a09471..f48cdc06 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -1199,13 +1199,11 @@ static const NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; * Updates the favorite's host when the type changes. */ - (void)_favoriteTypeDidChange -{ - // TODO: Handle changing favorite connection types - - /*NSDictionary *favorite = [[[self selectedFavoriteNode] representedObject] nodeFavorite]; +{ + NSDictionary *favorite = [[[self selectedFavoriteNode] representedObject] nodeFavorite]; // If either socket or host is localhost, clear. - if ((selectedTabView != SPSocketConnection) && [[favorite objectForKey:SPFavoriteHostKey] isEqualToString:@"localhost"]) { + if ((previousType != SPSocketConnection) && [[favorite objectForKey:SPFavoriteHostKey] isEqualToString:@"localhost"]) { [self setHost:@""]; } @@ -1217,9 +1215,6 @@ static const NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; (([favorite objectForKey:SPFavoriteHostKey]) ? [favorite valueForKeyPath:SPFavoriteHostKey] : @"")) ]]; } - - // Request a password refresh to keep keychain references in synch with the favorites - [self _updateFavoritePasswordsFromField:nil];*/ } /** -- cgit v1.2.3 From 66f0041a06b8710cd3ed3b5ff2c8cbb54004efba Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sat, 11 Dec 2010 21:48:37 +0000 Subject: Tidy up. --- Source/SPConnectionController.m | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index f48cdc06..89dc6405 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -188,10 +188,6 @@ static const NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; [self addObserver:self forKeyPath:SPFavoriteSSLCertificateFileLocationKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; [self addObserver:self forKeyPath:SPFavoriteSSLCACertFileLocationEnabledKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; [self addObserver:self forKeyPath:SPFavoriteSSLCACertFileLocationKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; - - // Sort the favourites to match prefs and select the appropriate row - if a valid sort option is selected - // TODO: Fix me, sorting currently does not work in the new outline view - //if (currentSortItem > -1) [self _sortFavorites]; SPTreeNode *favorite = [self _favoriteNodeForFavoriteID:[prefs integerForKey:([prefs boolForKey:SPSelectLastFavoriteUsed]) ? SPLastFavoriteID : SPDefaultFavorite]]; @@ -1180,18 +1176,18 @@ static const NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; sortKey = SPFavoriteTypeKey; break; } - - NSSortDescriptor *sortDescriptor = nil; - - if (currentSortItem == SPFavoritesSortTypeItem) { - sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:sortKey ascending:(!reverseFavoritesSort)] autorelease]; - } - else { - sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:sortKey ascending:(!reverseFavoritesSort) selector:@selector(caseInsensitiveCompare:)] autorelease]; + + // First sort the contents of all groups + for (SPTreeNode *node in [[[favoritesRoot childNodes] objectAtIndex:0] groupChildren]) + { + [[node mutableChildNodes] sortUsingSelector:@selector(compare:)]; } - - // TODO: Perform actual sorting here + // Secondly sort the root's leaf nodes + [[[[favoritesRoot childNodes] objectAtIndex:0] childLeafs] sortUsingSelector:@selector(compare:)]; + + [favoritesController saveFavorites]; + [self _reloadFavoritesViewData]; } @@ -1218,8 +1214,7 @@ static const NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; } /** - * Convenience method for rebuilding the connection favorites tree, reloading the outline view, expanding the - * items and scrolling to the selected item. + * Convenience method for reloading the outline view, expanding the root item and scrolling to the selected item. */ - (void)_reloadFavoritesViewData { -- cgit v1.2.3 From bb7fbb3c5ba1ccbf879ac7a54d30b6b7d2082c9a Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Mon, 13 Dec 2010 21:37:09 +0000 Subject: Shift some connection methods around. --- Source/SPConnectionController.m | 186 ---------------------------------------- 1 file changed, 186 deletions(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index b7f42654..adc7a893 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -49,9 +49,7 @@ static const NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; - (void)_favoriteTypeDidChange; - (void)_reloadFavoritesViewData; - (void)_restoreConnectionInterface; -- (void)_mySQLConnectionEstablished; - (void)_selectNode:(SPTreeNode *)node; -- (void)_initiateMySQLConnectionInBackground; - (NSNumber *)_createNewFavoriteID; - (SPTreeNode *)_favoriteNodeForFavoriteID:(NSInteger)favoriteID; @@ -1269,190 +1267,6 @@ static const NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; [favoritesOutlineView scrollRowToVisible:[favoritesOutlineView selectedRow]]; } -/** - * Called on the main thread once the MySQL connection is established on the background thread. Either the - * connection was cancelled or it was successful. - */ -- (void)_mySQLConnectionEstablished -{ - isConnecting = NO; - - // If the user hit cancel during the connection attempt, kill the connection once - // established and reset the UI. - if (mySQLConnectionCancelled) { - if ([mySQLConnection isConnected]) { - [mySQLConnection disconnect]; - [mySQLConnection release], mySQLConnection = nil; - } - - // Kill the SSH connection if present - [self cancelConnection]; - - [self _restoreConnectionInterface]; - - return; - } - - [progressIndicatorText setStringValue:NSLocalizedString(@"Connected", @"connection established message")]; - [progressIndicatorText display]; - - // Stop the current tab's progress indicator - [dbDocument setIsProcessing:NO]; - - // Successful connection! - [connectButton setEnabled:NO]; - [connectButton display]; - [progressIndicator stopAnimation:self]; - [progressIndicatorText setHidden:YES]; - [addToFavoritesButton setHidden:NO]; - - // If SSL was enabled, check it was established correctly - if (useSSL && ([self type] == SPTCPIPConnection || [self type] == SPSocketConnection)) { - if (![mySQLConnection isConnectedViaSSL]) { - SPBeginAlertSheet(NSLocalizedString(@"SSL connection not established", @"SSL requested but not used title"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], nil, nil, nil, NSLocalizedString(@"You requested that the connection should be established using SSL, but MySQL made the connection without SSL.\n\nThis may be because the server does not support SSL connections, or has SSL disabled; or insufficient details were supplied to establish an SSL connection.\n\nThis connection is not encrypted.", @"SSL connection requested but not established error detail")); - } - else { - [dbDocument setStatusIconToImageWithName:@"titlebarlock"]; - } - } - - // Re-enable favorites table view - [favoritesOutlineView setEnabled:YES]; - [favoritesOutlineView display]; - - // Release the tunnel if set - will now be retained by the connection - if (sshTunnel) [sshTunnel release], sshTunnel = nil; - - // Pass the connection to the document and clean up the interface - [self addConnectionToDocument]; -} - -/** - * Initiates the core of the MySQL connection process on a background thread. - */ -- (void)_initiateMySQLConnectionInBackground -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - // Initialise to socket if appropriate. - if ([self type] == SPSocketConnection) { - mySQLConnection = [[MCPConnection alloc] initToSocket:[self socket] withLogin:[self user]]; - - // Otherwise, initialise to host, using tunnel if appropriate - } else { - if ([self type] == SPSSHTunnelConnection) { - mySQLConnection = [[MCPConnection alloc] initToHost:@"127.0.0.1" - withLogin:[self user] - usingPort:[sshTunnel localPort]]; - [mySQLConnection setConnectionProxy:sshTunnel]; - } else { - mySQLConnection = [[MCPConnection alloc] initToHost:[self host] - withLogin:[self user] - usingPort:([[self port] length] ? [[self port] integerValue] : 3306)]; - } - } - - // Only set the password if there is no Keychain item set. The connection will ask the delegate for passwords in the Keychain. - if (!connectionKeychainItemName && [self password]) { - [mySQLConnection setPassword:[self password]]; - } - - // Enable SSL if set - if ([self useSSL]) { - [mySQLConnection setSSL:YES - usingKeyFilePath:[self sslKeyFileLocationEnabled] ? [self sslKeyFileLocation] : nil - certificatePath:[self sslCertificateFileLocationEnabled] ? [self sslCertificateFileLocation] : nil - certificateAuthorityCertificatePath:[self sslCACertFileLocationEnabled] ? [self sslCACertFileLocation] : nil]; - } - - - // Connection delegate must be set before actual connection attempt is made - [mySQLConnection setDelegate:dbDocument]; - - // Set whether or not we should enable delegate logging according to the prefs - [mySQLConnection setDelegateQueryLogging:[prefs boolForKey:SPConsoleEnableLogging]]; - - // Set options from preferences - [mySQLConnection setConnectionTimeout:[[prefs objectForKey:SPConnectionTimeoutValue] integerValue]]; - [mySQLConnection setUseKeepAlive:[[prefs objectForKey:SPUseKeepAlive] boolValue]]; - [mySQLConnection setKeepAliveInterval:[[prefs objectForKey:SPKeepAliveInterval] doubleValue]]; - - // Connect - [mySQLConnection connect]; - - if (![mySQLConnection isConnected]) { - if (sshTunnel) { - - // If an SSH tunnel is running, temporarily block to allow the tunnel to register changes in state - [[NSRunLoop currentRunLoop] runMode:NSModalPanelRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.2]]; - - // If the state is connection refused, attempt the MySQL connection again with the host using the hostfield value. - if ([sshTunnel state] == PROXY_STATE_FORWARDING_FAILED) { - if ([sshTunnel localPortFallback]) { - [mySQLConnection setPort:[sshTunnel localPortFallback]]; - [mySQLConnection connect]; - if (![mySQLConnection isConnected]) { - [[NSRunLoop currentRunLoop] runMode:NSModalPanelRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.2]]; - } - } - } - } - - if (![mySQLConnection isConnected]) { - NSString *errorMessage = @""; - if (sshTunnel && [sshTunnel state] == PROXY_STATE_FORWARDING_FAILED) { - errorMessage = [NSString stringWithFormat:NSLocalizedString(@"Unable to connect to host %@ because the port connection via SSH was refused.\n\nPlease ensure that your MySQL host is set up to allow TCP/IP connections (no --skip-networking) and is configured to allow connections from the host you are tunnelling via.\n\nYou may also want to check the port is correct and that you have the necessary privileges.\n\nChecking the error detail will show the SSH debug log which may provide more details.\n\nMySQL said: %@", @"message of panel when SSH port forwarding failed"), [self host], [mySQLConnection getLastErrorMessage]]; - [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"SSH port forwarding failed", @"title when ssh tunnel port forwarding failed") errorMessage:errorMessage detail:[sshTunnel debugMessages]]; - } else if ([mySQLConnection getLastErrorID] == 1045) { // "Access denied" error - errorMessage = [NSString stringWithFormat:NSLocalizedString(@"Unable to connect to host %@ because access was denied.\n\nDouble-check your username and password and ensure that access from your current location is permitted.\n\nMySQL said: %@", @"message of panel when connection to host failed due to access denied error"), [self host], [mySQLConnection getLastErrorMessage]]; - [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Access denied!", @"connection failed due to access denied title") errorMessage:errorMessage detail:nil]; - } else if ([self type] == SPSocketConnection && (![self socket] || ![[self socket] length]) && ![mySQLConnection findSocketPath]) { - errorMessage = [NSString stringWithFormat:NSLocalizedString(@"The socket file could not be found in any common location. Please supply the correct socket location.\n\nMySQL said: %@", @"message of panel when connection to socket failed because optional socket could not be found"), [mySQLConnection getLastErrorMessage]]; - [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Socket not found!", @"socket not found title") errorMessage:errorMessage detail:nil]; - } else if ([self type] == SPSocketConnection) { - errorMessage = [NSString stringWithFormat:NSLocalizedString(@"Unable to connect via the socket, or the request timed out.\n\nDouble-check that the socket path is correct and that you have the necessary privileges, and that the server is running.\n\nMySQL said: %@", @"message of panel when connection to host failed"), [mySQLConnection getLastErrorMessage]]; - [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Socket connection failed!", @"socket connection failed title") errorMessage:errorMessage detail:nil]; - } else { - errorMessage = [NSString stringWithFormat:NSLocalizedString(@"Unable to connect to host %@, or the request timed out.\n\nBe sure that the address is correct and that you have the necessary privileges, or try increasing the connection timeout (currently %ld seconds).\n\nMySQL said: %@", @"message of panel when connection to host failed"), [self host], (long)[[prefs objectForKey:SPConnectionTimeoutValue] integerValue], [mySQLConnection getLastErrorMessage]]; - [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Connection failed!", @"connection failed title") errorMessage:errorMessage detail:nil]; - } - - // Tidy up - isConnecting = NO; - - if (sshTunnel) [sshTunnel release], sshTunnel = nil; - - [mySQLConnection release], mySQLConnection = nil; - [self _restoreConnectionInterface]; - [pool release]; - - return; - } - } - - if ([self database] && ![[self database] isEqualToString:@""]) { - if (![mySQLConnection selectDB:[self database]]) { - [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Could not select database", @"message when database selection failed") errorMessage:[NSString stringWithFormat:NSLocalizedString(@"Connected to host, but unable to connect to database %@.\n\nBe sure that the database exists and that you have the necessary privileges.\n\nMySQL said: %@", @"message of panel when connection to db failed"), [self database], [mySQLConnection getLastErrorMessage]] detail:nil]; - - // Tidy up - isConnecting = NO; - - if (sshTunnel) [sshTunnel release], sshTunnel = nil; - - [mySQLConnection release], mySQLConnection = nil; - [self _restoreConnectionInterface]; - [pool release]; - - return; - } - } - - // Connection established - [self performSelectorOnMainThread:@selector(_mySQLConnectionEstablished) withObject:nil waitUntilDone:NO]; - - [pool release]; -} - /** * Creates a new favorite ID based on the UNIX epoch time. */ -- cgit v1.2.3 From 9859a8f8b20ebf038967a09b40bba9b4eb2f223b Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Mon, 13 Dec 2010 22:02:17 +0000 Subject: Use connection favorite constants. --- Source/SPConnectionController.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index adc7a893..4bafafbf 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -585,8 +585,8 @@ static const NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; // Check whether the password exists in the keychain, and if so add it; also record the // keychain details so we can pass around only those details if the password doesn't change - connectionKeychainItemName = [[keychain nameForFavoriteName:[self valueForKeyPath:@"selectedFavorite.name"] id:[self valueForKeyPath:@"selectedFavorite.id"]] retain]; - connectionKeychainItemAccount = [[keychain accountForUser:[self valueForKeyPath:@"selectedFavorite.user"] host:(([self type] == SPSocketConnection)?@"localhost":[self valueForKeyPath:@"selectedFavorite.host"]) database:[self valueForKeyPath:@"selectedFavorite.database"]] retain]; + connectionKeychainItemName = [[keychain nameForFavoriteName:[fav objectForKey:SPFavoriteNameKey] id:[fav objectForKey:SPFavoriteIDKey]] retain]; + connectionKeychainItemAccount = [[keychain accountForUser:[fav objectForKey:SPFavoriteUserKey] host:(([self type] == SPSocketConnection) ? @"localhost" : [fav objectForKey:SPFavoriteHostKey]) database:[fav objectForKey:SPFavoriteDatabaseKey]] retain]; [self setPassword:[keychain getPasswordForName:connectionKeychainItemName account:connectionKeychainItemAccount]]; @@ -597,8 +597,8 @@ static const NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; } // And the same for the SSH password - connectionSSHKeychainItemName = [[keychain nameForSSHForFavoriteName:[self valueForKeyPath:@"selectedFavorite.name"] id:[self valueForKeyPath:@"selectedFavorite.id"]] retain]; - connectionSSHKeychainItemAccount = [[keychain accountForSSHUser:[self valueForKeyPath:@"selectedFavorite.sshUser"] sshHost:[self valueForKeyPath:@"selectedFavorite.sshHost"]] retain]; + connectionSSHKeychainItemName = [[keychain nameForSSHForFavoriteName:[fav objectForKey:SPFavoriteNameKey] id:[fav objectForKey:SPFavoriteIDKey]] retain]; + connectionSSHKeychainItemAccount = [[keychain accountForSSHUser:[fav objectForKey:SPFavoriteSSHUserKey] sshHost:[fav objectForKey:SPFavoriteSSHHostKey]] retain]; [self setSshPassword:[keychain getPasswordForName:connectionSSHKeychainItemName account:connectionSSHKeychainItemAccount]]; -- cgit v1.2.3 From fa7cff57548edc51420693e6909fe2adb3c18951 Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Wed, 26 Jan 2011 21:44:29 +0000 Subject: Fix and enable drag and drop in the connection favorites outline view. --- Source/SPConnectionController.m | 67 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 8 deletions(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 2bad361f..c12f1ac4 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -46,6 +46,7 @@ static const NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; - (BOOL)_checkHost; - (void)_sortFavorites; +- (void)_sortTreeNode:(SPTreeNode *)node usingKey:(NSString *)key; - (void)_favoriteTypeDidChange; - (void)_reloadFavoritesViewData; - (void)_restoreConnectionInterface; @@ -56,6 +57,8 @@ static const NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; - (void)_updateFavoritePasswordsFromField:(NSControl *)control; +static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, void *key); + @end @implementation SPConnectionController @@ -1177,19 +1180,67 @@ static const NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; sortKey = SPFavoriteTypeKey; break; } + + [self _sortTreeNode:[[favoritesRoot childNodes] objectAtIndex:0] usingKey:sortKey]; + + [favoritesController saveFavorites]; - // First sort the contents of all groups - for (SPTreeNode *node in [[[favoritesRoot childNodes] objectAtIndex:0] groupChildren]) + [self _reloadFavoritesViewData]; +} + +/** + * Sorts the supplied tree node using the supplied sort key. + * + * @param node The tree node to sort + * @param key The sort key to sort by + */ +- (void)_sortTreeNode:(SPTreeNode *)node usingKey:(NSString *)key +{ + NSMutableArray *nodes = [[node mutableChildNodes] mutableCopy]; + + for (SPTreeNode *node in nodes) { - [[node mutableChildNodes] sortUsingSelector:@selector(compare:)]; + if ([node isGroup]) { + [self _sortTreeNode:node usingKey:key]; + } } - // Secondly sort the root's leaf nodes - [[[[favoritesRoot childNodes] objectAtIndex:0] childLeafs] sortUsingSelector:@selector(compare:)]; + NSMutableArray *groupNodes = [[NSMutableArray alloc] init]; - [favoritesController saveFavorites]; - - [self _reloadFavoritesViewData]; + for (node in nodes) + { + if ([node isGroup]) { + [groupNodes addObject:node]; + [nodes removeObject:node]; + } + } + + [nodes sortUsingFunction:compareFavoritesUsingKey context:key]; + + [nodes addObjectsFromArray:groupNodes]; + + [[node mutableChildNodes] setArray:nodes]; + + [groupNodes release]; +} + +/** + * Sort function used by NSMutableArray's sortUsingFunction: + * + * @param favorite1 The first of the favorites to compare (and determine sort order) + * @param favorite2 The second of the favorites to compare + * @param key The sort key to perform the comparison by + * + * @return An integer (NSComparisonResult) indicating the order of the comparison + */ +static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, void *key) +{ + NSString *dictKey = (NSString *)key; + + id value1 = [[(SPFavoriteNode *)[(SPTreeNode *)favorite1 representedObject] nodeFavorite] objectForKey:dictKey]; + id value2 = [[(SPFavoriteNode *)[(SPTreeNode *)favorite2 representedObject] nodeFavorite] objectForKey:dictKey]; + + return [value1 compare:value2]; } /** -- cgit v1.2.3 From c9338aee82e9b42a07e93625c3235977c3dbf1b4 Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Wed, 2 Feb 2011 23:10:06 +0000 Subject: Fix favorites sorting. --- Source/SPConnectionController.m | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 9f542cec..8424b427 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -1193,6 +1193,9 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v { NSMutableArray *nodes = [[node mutableChildNodes] mutableCopy]; + // If this node only has one child and it's not another group node, don't bother proceeding + if (([nodes count] == 1) && (![[nodes objectAtIndex:0] isGroup])) return; + for (SPTreeNode *node in nodes) { if ([node isGroup]) { @@ -1200,20 +1203,34 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v } } + NSMutableIndexSet *indexes = [[NSMutableIndexSet alloc] init]; NSMutableArray *groupNodes = [[NSMutableArray alloc] init]; - for (node in nodes) + for (SPTreeNode *innerNode in nodes) { - if ([node isGroup]) { - [groupNodes addObject:node]; - [nodes removeObject:node]; + if ([innerNode isGroup]) { + [groupNodes addObject:innerNode]; + [indexes addIndex:[nodes indexOfObject:innerNode]]; } } + NSUInteger i = [indexes firstIndex]; + + while (i != NSNotFound) + { + [nodes removeObjectAtIndex:i]; + + i = [indexes indexGreaterThanIndex:i]; + } + + [indexes release]; + [nodes sortUsingFunction:compareFavoritesUsingKey context:key]; [nodes addObjectsFromArray:groupNodes]; + if (reverseFavoritesSort) [nodes reverse]; + [[node mutableChildNodes] setArray:nodes]; [groupNodes release]; -- cgit v1.2.3 From 32b7c4df28e2eb3a4a8bcb71b3faae4f173f016c Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sat, 5 Mar 2011 00:14:43 +0000 Subject: Add support for auto saving expanded items. --- Source/SPConnectionController.m | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 8424b427..49dcb542 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -167,9 +167,14 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v // Register double click action for the favorites outline view (double click favorite to connect) [favoritesOutlineView setTarget:self]; [favoritesOutlineView setDoubleAction:@selector(nodeDoubleClicked:)]; + + // Register drag types for the favorites outline view [favoritesOutlineView registerForDraggedTypes:[NSArray arrayWithObject:SPFavoritesPasteboardDragType]]; [favoritesOutlineView setDraggingSourceOperationMask:NSDragOperationMove forLocal:YES]; + // Preserve expanded group nodes + [favoritesOutlineView setAutosaveExpandedItems:YES]; + // Registered to be notified of changes to connection information [self addObserver:self forKeyPath:SPFavoriteNameKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; [self addObserver:self forKeyPath:SPFavoriteHostKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; @@ -1283,7 +1288,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v - (void)_reloadFavoritesViewData { [favoritesOutlineView reloadData]; - [favoritesOutlineView expandItem:[[favoritesRoot childNodes] objectAtIndex:0] expandChildren:YES]; + [favoritesOutlineView expandItem:[[favoritesRoot childNodes] objectAtIndex:0] expandChildren:NO]; [favoritesOutlineView scrollRowToVisible:[favoritesOutlineView selectedRow]]; } -- cgit v1.2.3 From 57f129d6d503dc68aecc0ed458771d91f062290b Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sat, 5 Mar 2011 15:21:36 +0000 Subject: Fix bug that was incorrectly saving the wrong SSH password to the Keychain. --- Source/SPConnectionController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 49dcb542..258d442a 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -1444,7 +1444,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v // Add the new keychain item if the password field has a value if ([[sshPasswordField stringValue] length]) { - [keychain addPassword:[sshPasswordField stringValue] forName:newKeychainName account:newKeychainAccount]; + [keychain addPassword:[sshSSHPasswordField stringValue] forName:newKeychainName account:newKeychainAccount]; } } -- cgit v1.2.3 From 915a3831525bf3a9350648d82f86dd54ae366292 Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sun, 6 Mar 2011 15:37:06 +0000 Subject: Comments. --- Source/SPConnectionController.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 258d442a..d3c75e16 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -1021,7 +1021,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v */ - (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo { - // Remove the current favorite/group + // Remove the current favorite/group node if ([contextInfo isEqualToString:SPRemoveNode]) { if (returnCode == NSAlertDefaultReturn) { @@ -1052,7 +1052,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v [prefs setInteger:0 forKey:SPLastFavoriteID]; } - // Reset default favorite + // If required, reset the default favorite if ([[favorite objectForKey:SPFavoriteIDKey] integerValue] == [prefs integerForKey:SPDefaultFavorite]) { [prefs setInteger:[prefs integerForKey:SPLastFavoriteID] forKey:SPDefaultFavorite]; } @@ -1272,7 +1272,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v [self setHost:@""]; } - // Update the name for newly added favorites if not already touched by the user, by trigger a KVO update + // Update the name for newly added favorites if not already touched by the user, by triggering a KVO update if (!favoriteNameFieldWasTouched) { [self setName:[NSString stringWithFormat:@"%@@%@", ([favorite objectForKey:SPFavoriteUserKey]) ? [favorite objectForKey:SPFavoriteUserKey] : @"", -- cgit v1.2.3 From 1e5288e9840e201a00bcb5ca3035d7aa807f1f8d Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Mon, 7 Mar 2011 20:12:52 +0000 Subject: Bring outline view branch up to date with trunk (r3203:r3224). --- Source/SPConnectionController.m | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index d3c75e16..b5ab8010 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -35,6 +35,9 @@ #import "SPTableTextFieldCell.h" #import "SPFavoritesController.h" #import "SPFavoriteNode.h" +#import "SPGeneralPreferencePane.h" +#import "SPDatabaseViewController.h" +#import "SPTreeNode.h" // Constants static const NSString *SPRemoveNode = @"RemoveNode"; @@ -101,7 +104,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v */ - (id)initWithDocument:(SPDatabaseDocument *)document { - if (self = [super init]) { + if ((self = [super init])) { // Weak reference dbDocument = document; @@ -533,7 +536,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v // Perform re-sorting [self _sortFavorites]; - [sender setState:reverseFavoritesSort]; + [(NSMenuItem *)sender setState:reverseFavoritesSort]; } /** @@ -1039,8 +1042,6 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v NSString *favoriteSSHHost = [favorite objectForKey:SPFavoriteSSHHostKey]; NSString *favoriteID = [favorite objectForKey:SPFavoriteIDKey]; - NSInteger favoriteType = [[favorite objectForKey:SPFavoriteTypeKey] integerValue]; - // Remove passwords from the Keychain [keychain deletePasswordForName:[keychain nameForFavoriteName:favoriteName id:favoriteID] account:[keychain accountForUser:favoriteUser host:((type == SPSocketConnection) ? @"localhost" : favoriteHost) database:favoriteDatabase]]; @@ -1351,16 +1352,18 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v */ - (SPTreeNode *)_favoriteNodeForFavoriteID:(NSInteger)favoriteID { - SPTreeNode *node = nil; + SPTreeNode *favoriteNode = nil; - if (!favoritesRoot) return; + if (!favoritesRoot) return favoriteNode; - for (node in [favoritesRoot allChildLeafs]) + for (SPTreeNode *node in [favoritesRoot allChildLeafs]) { if ([[[[node representedObject] nodeFavorite] objectForKey:SPFavoriteIDKey] integerValue] == favoriteID) { - return node; + favoriteNode = node; } } + + return favoriteNode; } /** -- cgit v1.2.3 From 25065735e36fcec54c5eb034b701999722fb99a1 Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sat, 12 Mar 2011 19:37:21 +0000 Subject: Fix last used favorite not being remembered. --- Source/SPConnectionController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index b5ab8010..5734f3de 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -617,7 +617,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v [connectionSSHKeychainItemAccount release], connectionSSHKeychainItemAccount = nil; } - [prefs setInteger:([favoritesOutlineView selectedRow] - 1) forKey:SPLastFavoriteID]; + [prefs setInteger:[[fav objectForKey:SPFavoriteIDKey] integerValue] forKey:SPLastFavoriteID]; // Set first responder to password field if it is empty switch ([self type]) -- cgit v1.2.3 From c2cf0e39c3c59c3548bf39f0835ac552cdcd23f4 Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sun, 13 Mar 2011 17:54:48 +0000 Subject: Warnings. --- Source/SPConnectionController.m | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 5734f3de..da828cda 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -40,10 +40,10 @@ #import "SPTreeNode.h" // Constants -static const NSString *SPRemoveNode = @"RemoveNode"; -static const NSString *SPImportFavorites = @"ImportFavorites"; -static const NSString *SPExportFavorites = @"ExportFavorites"; -static const NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; +static NSString *SPRemoveNode = @"RemoveNode"; +static NSString *SPImportFavorites = @"ImportFavorites"; +static NSString *SPExportFavorites = @"ExportFavorites"; +static NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; @interface SPConnectionController (PrivateAPI) @@ -637,7 +637,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v /** * Returns the selected favorite data dictionary or nil if nothing is selected. */ -- (NSMutableDictionary *)selectedFavorite +- (NSDictionary *)selectedFavorite { SPTreeNode *node = [self selectedFavoriteNode]; @@ -1202,10 +1202,10 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v // If this node only has one child and it's not another group node, don't bother proceeding if (([nodes count] == 1) && (![[nodes objectAtIndex:0] isGroup])) return; - for (SPTreeNode *node in nodes) + for (SPTreeNode *treeNode in nodes) { - if ([node isGroup]) { - [self _sortTreeNode:node usingKey:key]; + if ([treeNode isGroup]) { + [self _sortTreeNode:treeNode usingKey:key]; } } -- cgit v1.2.3 From 1632c45fdd9d0b8fe1e0aef3e891e8bd5704df29 Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sun, 13 Mar 2011 18:50:40 +0000 Subject: Fix issue related to dragging a node to a group node that is being renamed as well as fix all warnings related to the connection outlinew view. --- Source/SPConnectionController.m | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index da828cda..0362b9ae 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -118,9 +118,11 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v connectionSSHKeychainItemName = nil; connectionSSHKeychainItemAccount = nil; + isEditing = NO; + isConnecting = NO; + sshTunnel = nil; mySQLConnection = nil; - isConnecting = NO; cancellingConnection = NO; mySQLConnectionCancelled = NO; @@ -519,7 +521,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v // Perform sorting [self _sortFavorites]; - if (previousSortItem > -1) [[[sender menu] itemAtIndex:previousSortItem] setState:NSOffState]; + if ((NSInteger)previousSortItem > -1) [[[sender menu] itemAtIndex:previousSortItem] setState:NSOffState]; [[[sender menu] itemAtIndex:currentSortItem] setState:NSOnState]; } @@ -637,7 +639,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v /** * Returns the selected favorite data dictionary or nil if nothing is selected. */ -- (NSDictionary *)selectedFavorite +- (NSMutableDictionary *)selectedFavorite { SPTreeNode *node = [self selectedFavoriteNode]; @@ -714,7 +716,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v SPTreeNode *selectedNode = [self selectedFavoriteNode]; - SPTreeNode *parent = ([selectedNode isGroup]) ? selectedNode : [selectedNode parentNode]; + SPTreeNode *parent = ([selectedNode isGroup]) ? selectedNode : (SPTreeNode *)[selectedNode parentNode]; SPTreeNode *node = [favoritesController addFavoriteNodeWithData:favorite asChildOfNode:parent]; @@ -722,7 +724,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v [self _selectNode:node]; [[[[NSApp delegate] preferenceController] generalPreferencePane] updateDefaultFavoritePopup]; - + favoriteNameFieldWasTouched = NO; [favoritesOutlineView editColumn:0 row:[favoritesOutlineView selectedRow] withEvent:nil select:YES]; @@ -829,13 +831,15 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v { SPTreeNode *selectedNode = [self selectedFavoriteNode]; - SPTreeNode *parent = ([selectedNode isGroup]) ? selectedNode : [selectedNode parentNode]; + SPTreeNode *parent = ([selectedNode isGroup]) ? selectedNode : (SPTreeNode *)[selectedNode parentNode]; SPTreeNode *node = [favoritesController addGroupNodeWithName:NSLocalizedString(@"New Folder", @"new folder placeholder name") asChildOfNode:parent]; [self _reloadFavoritesViewData]; [self _selectNode:node]; + isEditing = YES; + [favoritesOutlineView editColumn:0 row:[favoritesOutlineView selectedRow] withEvent:nil select:YES]; } @@ -928,7 +932,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v SPTreeNode *selectedNode = [self selectedFavoriteNode]; - SPTreeNode *parent = ([selectedNode isGroup]) ? selectedNode : [selectedNode parentNode]; + SPTreeNode *parent = ([selectedNode isGroup]) ? selectedNode : (SPTreeNode *)[selectedNode parentNode]; SPTreeNode *node = [favoritesController addFavoriteNodeWithData:favorite asChildOfNode:parent]; @@ -1322,7 +1326,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v // Re-enable favorites table view [favoritesOutlineView setEnabled:YES]; - [favoritesOutlineView display]; + [(NSView *)favoritesOutlineView display]; mySQLConnectionCancelled = NO; -- cgit v1.2.3 From 62dcc003c4a94fb6b21fd248bc7920969391f46b Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sat, 19 Mar 2011 18:56:16 +0000 Subject: Change categories to class extensions to enable compile time checking of method implementation. --- Source/SPConnectionController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 0362b9ae..5bc81be8 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -45,7 +45,7 @@ static NSString *SPImportFavorites = @"ImportFavorites"; static NSString *SPExportFavorites = @"ExportFavorites"; static NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; -@interface SPConnectionController (PrivateAPI) +@interface SPConnectionController () - (BOOL)_checkHost; - (void)_sortFavorites; -- cgit v1.2.3 From 3e799650da5898cf3e59acecba27a74b95e17ebc Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sat, 19 Mar 2011 23:28:29 +0000 Subject: Expand all tree children to allow the default to be selected. --- Source/SPConnectionController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 5bc81be8..0a75c4d2 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -1293,7 +1293,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v - (void)_reloadFavoritesViewData { [favoritesOutlineView reloadData]; - [favoritesOutlineView expandItem:[[favoritesRoot childNodes] objectAtIndex:0] expandChildren:NO]; + [favoritesOutlineView expandItem:[[favoritesRoot childNodes] objectAtIndex:0] expandChildren:YES]; [favoritesOutlineView scrollRowToVisible:[favoritesOutlineView selectedRow]]; } -- cgit v1.2.3 From 02f1dc76e83c25120395cf59c79712fa41292507 Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sat, 26 Mar 2011 12:29:01 +0000 Subject: Remove references to deleted image. --- Source/SPConnectionController.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 0a75c4d2..a06637e5 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -521,7 +521,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v // Perform sorting [self _sortFavorites]; - if ((NSInteger)previousSortItem > -1) [[[sender menu] itemAtIndex:previousSortItem] setState:NSOffState]; + if (previousSortItem > SPFavoritesSortUnsorted) [[[sender menu] itemAtIndex:previousSortItem] setState:NSOffState]; [[[sender menu] itemAtIndex:currentSortItem] setState:NSOnState]; } @@ -1184,6 +1184,8 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v case SPFavoritesSortTypeItem: sortKey = SPFavoriteTypeKey; break; + case SPFavoritesSortUnsorted: + break; } [self _sortTreeNode:[[favoritesRoot childNodes] objectAtIndex:0] usingKey:sortKey]; -- cgit v1.2.3 From c1de88ac62381469ad01975c0f28e5847a65edb9 Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sat, 14 May 2011 18:43:05 +0000 Subject: Fix exceptions being thrown upon launch. --- Source/SPConnectionController.m | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index a06637e5..0f12303f 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -997,6 +997,14 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v contextInfo:SPExportFavorites]; } +#pragma mark - +#pragma mark Accessors + +- (SPFavoritesOutlineView *)favoritesOutlineView +{ + return favoritesOutlineView; +} + #pragma mark - #pragma mark Key Value Observing -- cgit v1.2.3 From f3e65efba3f76cb2149fd8427af9463032d3c45d Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sun, 12 Jun 2011 13:56:11 +0000 Subject: Add the ability to export favorites. --- Source/SPConnectionController.m | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 0f12303f..6ed6e392 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -38,6 +38,8 @@ #import "SPGeneralPreferencePane.h" #import "SPDatabaseViewController.h" #import "SPTreeNode.h" +#import "SPFavoritesExporter.h" +#import "SPFavoritesImporter.h" // Constants static NSString *SPRemoveNode = @"RemoveNode"; @@ -989,6 +991,8 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v { NSSavePanel *savePanel = [NSSavePanel savePanel]; + [savePanel setAccessoryView:exportPanelAccessoryView]; + [savePanel beginSheetForDirectory:nil file:SPExportFavoritesFilename modalForWindow:[dbDocument parentWindow] @@ -1131,9 +1135,20 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v /** * Called when the user dismisses either the import of export favorites panels. */ -- (void)importExportFavoritesSheetDidEnd:(NSOpenPanel *)openPanel returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo +- (void)importExportFavoritesSheetDidEnd:(NSOpenPanel *)panel returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo { + NSError *error = nil; + if (contextInfo == SPExportFavorites) { + SPFavoritesExporter *exporter = [[[SPFavoritesExporter alloc] init] autorelease]; + + [exporter setDelegate:self]; + + [exporter writeFavorites:[self selectedFavoriteNodes] toFile:[panel filename] error:&error]; + } + else if (contextInfo == SPImportFavorites) { + //SPFavoritesImporter *importer = [[SPFavoritesImporter alloc] init]; + } } /** -- cgit v1.2.3 From e70486eb1503cad882a0d5e452fef3b2fecf6629 Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Mon, 1 Aug 2011 21:54:27 +0000 Subject: Work on importing favorites. --- Source/SPConnectionController.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 6ed6e392..a5021206 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -1136,18 +1136,18 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v * Called when the user dismisses either the import of export favorites panels. */ - (void)importExportFavoritesSheetDidEnd:(NSOpenPanel *)panel returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo -{ - NSError *error = nil; - +{ if (contextInfo == SPExportFavorites) { SPFavoritesExporter *exporter = [[[SPFavoritesExporter alloc] init] autorelease]; [exporter setDelegate:self]; - [exporter writeFavorites:[self selectedFavoriteNodes] toFile:[panel filename] error:&error]; + [exporter writeFavorites:[self selectedFavoriteNodes] toFile:[panel filename]]; } else if (contextInfo == SPImportFavorites) { - //SPFavoritesImporter *importer = [[SPFavoritesImporter alloc] init]; + SPFavoritesImporter *importer = [[SPFavoritesImporter alloc] init]; + + [importer setDelegate:self]; } } -- cgit v1.2.3 From d01474532b11014019d4d5a1197a8f8f7d683c0b Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sun, 7 Aug 2011 18:42:51 +0000 Subject: Don't allow newlines in the connection view input fields. Fixes issue #1008. --- Source/SPConnectionController.m | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index a5021206..937cb521 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -59,6 +59,7 @@ static NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; - (NSNumber *)_createNewFavoriteID; - (SPTreeNode *)_favoriteNodeForFavoriteID:(NSInteger)favoriteID; +- (NSString *)_stripInvalidCharactersFromString:(NSString *)subject; - (void)_updateFavoritePasswordsFromField:(NSControl *)control; @@ -1395,6 +1396,17 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v return favoriteNode; } +/** + * Strips any invalid characters form the supplied string. Invalid is defined as any characters that should + * not be allowed to be enetered on the connection screen. + */ +- (NSString *)_stripInvalidCharactersFromString:(NSString *)subject +{ + NSString *result = [subject stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + + return [result stringByReplacingOccurrencesOfString:@"\n" withString:@""]; +} + /** * Check all fields used in the keychain names against the old values for that * favorite, and update the keychain names to match if necessary. -- cgit v1.2.3 From e23ba5155a53c43a106ac9646f51321ccc7d86f4 Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sun, 2 Oct 2011 11:25:26 +0000 Subject: Favorites import progress. --- Source/SPConnectionController.m | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 937cb521..f5d02a61 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -1138,17 +1138,21 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v */ - (void)importExportFavoritesSheetDidEnd:(NSOpenPanel *)panel returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo { - if (contextInfo == SPExportFavorites) { - SPFavoritesExporter *exporter = [[[SPFavoritesExporter alloc] init] autorelease]; - - [exporter setDelegate:self]; - - [exporter writeFavorites:[self selectedFavoriteNodes] toFile:[panel filename]]; - } - else if (contextInfo == SPImportFavorites) { - SPFavoritesImporter *importer = [[SPFavoritesImporter alloc] init]; - - [importer setDelegate:self]; + if (returnCode == NSOKButton) { + if (contextInfo == SPExportFavorites) { + SPFavoritesExporter *exporter = [[[SPFavoritesExporter alloc] init] autorelease]; + + [exporter setDelegate:self]; + + [exporter writeFavorites:[self selectedFavoriteNodes] toFile:[panel filename]]; + } + else if (contextInfo == SPImportFavorites) { + SPFavoritesImporter *importer = [[SPFavoritesImporter alloc] init]; + + [importer setDelegate:self]; + + [importer importFavoritesFromFileAtPath:[panel filename]]; + } } } -- cgit v1.2.3 From 1d7ed99d602bf9c7aa4ea40a9a2ab6458864e51f Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sun, 22 Jan 2012 12:19:21 +0000 Subject: Bring outlinew view branch up to date with trunk (r3375:3468). --- Source/SPConnectionController.m | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index f5d02a61..2e6790f6 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -47,6 +47,10 @@ static NSString *SPImportFavorites = @"ImportFavorites"; static NSString *SPExportFavorites = @"ExportFavorites"; static NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; +@interface NSSavePanel (NSSavePanel_unpublishedUntilSnowLeopardAPI) +- (void)setShowsHiddenFiles:(BOOL)flag; +@end + @interface SPConnectionController () - (BOOL)_checkHost; @@ -237,7 +241,13 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v * connection proxies in use. */ - (IBAction)initiateConnection:(id)sender -{ +{ + // If this action was triggered via a double-click on the favorites outline view, + // ensure that one of the connections was double-clicked, not the area above or below +#ifndef SP_REFACTOR + if (sender == favoritesOutlineView && [favoritesOutlineView clickedRow] <= 0) return; +#endif + // Ensure that host is not empty if this is a TCP/IP or SSH connection if (([self type] == SPTCPIPConnection || [self type] == SPSSHTunnelConnection) && ![[self host] length]) { SPBeginAlertSheet(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], self, nil, nil, NSLocalizedString(@"Insufficient details provided to establish a connection. Please enter at least the hostname.", @"insufficient details informative message")); @@ -396,7 +406,8 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v NSString *directoryPath = nil; NSString *filePath = nil; NSArray *permittedFileTypes = nil; - NSOpenPanel *openPanel = [NSOpenPanel openPanel]; + keySelectionPanel = [NSOpenPanel openPanel]; + [keySelectionPanel setShowsHiddenFiles:[prefs boolForKey:SPHiddenKeyFileVisibilityKey]]; // Switch details by sender. // First, SSH keys: @@ -415,7 +426,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v } permittedFileTypes = [NSArray arrayWithObjects:@"pem", @"", nil]; - [openPanel setAccessoryView:sshKeyLocationHelp]; + [keySelectionPanel setAccessoryView:sshKeyLocationHelp]; // SSL key file location: } else if (sender == standardSSLKeyFileButton || sender == socketSSLKeyFileButton) { @@ -424,7 +435,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v return; } permittedFileTypes = [NSArray arrayWithObjects:@"pem", @"key", @"", nil]; - [openPanel setAccessoryView:sslKeyFileLocationHelp]; + [keySelectionPanel setAccessoryView:sslKeyFileLocationHelp]; // SSL certificate file location: } else if (sender == standardSSLCertificateButton || sender == socketSSLCertificateButton) { @@ -433,7 +444,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v return; } permittedFileTypes = [NSArray arrayWithObjects:@"pem", @"cert", @"crt", @"", nil]; - [openPanel setAccessoryView:sslCertificateLocationHelp]; + [keySelectionPanel setAccessoryView:sslCertificateLocationHelp]; // SSL CA certificate file location: } else if (sender == standardSSLCACertButton || sender == socketSSLCACertButton) { @@ -442,10 +453,10 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v return; } permittedFileTypes = [NSArray arrayWithObjects:@"pem", @"cert", @"crt", @"", nil]; - [openPanel setAccessoryView:sslCACertLocationHelp]; + [keySelectionPanel setAccessoryView:sslCACertLocationHelp]; } - [openPanel beginSheetForDirectory:directoryPath + [keySelectionPanel beginSheetForDirectory:directoryPath file:filePath types:permittedFileTypes modalForWindow:[dbDocument parentWindow] @@ -470,6 +481,14 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v [self resizeTabViewToConnectionType:[self type] animating:YES]; } +/** + * Toggle hidden file visiblity in response to accessory view changes + */ +- (IBAction)updateKeyLocationFileVisibility:(id)sender +{ + [keySelectionPanel setShowsHiddenFiles:[prefs boolForKey:SPHiddenKeyFileVisibilityKey]]; +} + #pragma mark - #pragma mark Connection details interaction and display -- cgit v1.2.3 From b31bb2c4981d0a44119c43eac7525233a7c3b0e3 Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sun, 22 Jan 2012 22:40:05 +0000 Subject: Split out connection controller initialization. --- Source/SPConnectionController.m | 136 ++-------------------------------------- 1 file changed, 5 insertions(+), 131 deletions(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 2e6790f6..c5119bb7 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -48,7 +48,9 @@ static NSString *SPExportFavorites = @"ExportFavorites"; static NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; @interface NSSavePanel (NSSavePanel_unpublishedUntilSnowLeopardAPI) + - (void)setShowsHiddenFiles:(BOOL)flag; + @end @interface SPConnectionController () @@ -67,7 +69,7 @@ static NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; - (void)_updateFavoritePasswordsFromField:(NSControl *)control; -static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, void *key); +static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, void *key); @end @@ -103,134 +105,6 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v @synthesize isConnecting; -#pragma mark - - -/** - * Initialise the connection controller, linking it to the - * parent document and setting up the parent window. - */ -- (id)initWithDocument:(SPDatabaseDocument *)document -{ - if ((self = [super init])) { - - // Weak reference - dbDocument = document; - - databaseConnectionSuperview = [dbDocument databaseView]; - databaseConnectionView = [dbDocument valueForKey:@"contentViewSplitter"]; - - // Keychain references - connectionKeychainItemName = nil; - connectionKeychainItemAccount = nil; - connectionSSHKeychainItemName = nil; - connectionSSHKeychainItemAccount = nil; - - isEditing = NO; - isConnecting = NO; - - sshTunnel = nil; - mySQLConnection = nil; - cancellingConnection = NO; - mySQLConnectionCancelled = NO; - - favoriteNameFieldWasTouched = YES; - - // Load the connection nib, keeping references to the top-level objects for later release - nibObjectsToRelease = [[NSMutableArray alloc] init]; - NSArray *connectionViewTopLevelObjects = nil; - NSNib *nibLoader = [[NSNib alloc] initWithNibNamed:@"ConnectionView" bundle:[NSBundle mainBundle]]; - - [nibLoader instantiateNibWithOwner:self topLevelObjects:&connectionViewTopLevelObjects]; - [nibObjectsToRelease addObjectsFromArray:connectionViewTopLevelObjects]; - [nibLoader release]; - - // Hide the main view and position and display the connection view - [databaseConnectionView setHidden:YES]; - [connectionView setFrame:[databaseConnectionView frame]]; - [databaseConnectionSuperview addSubview:connectionView]; - [connectionSplitView setPosition:[[dbDocument valueForKey:@"dbTablesTableView"] frame].size.width ofDividerAtIndex:0]; - [connectionSplitView setDelegate:self]; - - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(scrollViewFrameChanged:) name:NSViewFrameDidChangeNotification object:nil]; - - // Generic folder image for use in the outline view's groups - folderImage = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericFolderIcon)] retain]; - - [folderImage setSize:NSMakeSize(16, 16)]; - - // Set up a keychain instance and preferences reference, and create the initial favorites list - keychain = [[SPKeychain alloc] init]; - prefs = [[NSUserDefaults standardUserDefaults] retain]; - - // Create a reference to the favorites controller, forcing the data to be loaded from disk and the - // tree constructor. - favoritesController = [SPFavoritesController sharedFavoritesController]; - - // Tree reference - favoritesRoot = [favoritesController favoritesTree]; - - // Update the UI - [self _reloadFavoritesViewData]; - - // Set sort items - currentSortItem = [prefs integerForKey:SPFavoritesSortedBy]; - reverseFavoritesSort = [prefs boolForKey:SPFavoritesSortedInReverse]; - - // Register double click action for the favorites outline view (double click favorite to connect) - [favoritesOutlineView setTarget:self]; - [favoritesOutlineView setDoubleAction:@selector(nodeDoubleClicked:)]; - - // Register drag types for the favorites outline view - [favoritesOutlineView registerForDraggedTypes:[NSArray arrayWithObject:SPFavoritesPasteboardDragType]]; - [favoritesOutlineView setDraggingSourceOperationMask:NSDragOperationMove forLocal:YES]; - - // Preserve expanded group nodes - [favoritesOutlineView setAutosaveExpandedItems:YES]; - - // Registered to be notified of changes to connection information - [self addObserver:self forKeyPath:SPFavoriteNameKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; - [self addObserver:self forKeyPath:SPFavoriteHostKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; - [self addObserver:self forKeyPath:SPFavoriteUserKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; - [self addObserver:self forKeyPath:SPFavoriteDatabaseKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; - [self addObserver:self forKeyPath:SPFavoriteSocketKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; - [self addObserver:self forKeyPath:SPFavoritePortKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; - [self addObserver:self forKeyPath:SPFavoriteUseSSLKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; - [self addObserver:self forKeyPath:SPFavoriteSSHHostKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; - [self addObserver:self forKeyPath:SPFavoriteSSHUserKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; - [self addObserver:self forKeyPath:SPFavoriteSSHPortKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; - [self addObserver:self forKeyPath:SPFavoriteSSHKeyLocationEnabledKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; - [self addObserver:self forKeyPath:SPFavoriteSSHKeyLocationKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; - [self addObserver:self forKeyPath:SPFavoriteSSLKeyFileLocationEnabledKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; - [self addObserver:self forKeyPath:SPFavoriteSSLKeyFileLocationKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; - [self addObserver:self forKeyPath:SPFavoriteSSLCertificateFileLocationEnabledKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; - [self addObserver:self forKeyPath:SPFavoriteSSLCertificateFileLocationKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; - [self addObserver:self forKeyPath:SPFavoriteSSLCACertFileLocationEnabledKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; - [self addObserver:self forKeyPath:SPFavoriteSSLCACertFileLocationKey options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; - - SPTreeNode *favorite = [self _favoriteNodeForFavoriteID:[prefs integerForKey:([prefs boolForKey:SPSelectLastFavoriteUsed]) ? SPLastFavoriteID : SPDefaultFavorite]]; - - if (favorite) { - - NSNumber *typeNumber = [[[favorite representedObject] nodeFavorite] objectForKey:SPFavoriteTypeKey]; - - previousType = (typeNumber) ? [typeNumber integerValue] : SPTCPIPConnection; - - [self _selectNode:favorite]; - - [self resizeTabViewToConnectionType:[[[[favorite representedObject] nodeFavorite] objectForKey:SPFavoriteTypeKey] integerValue] animating:NO]; - - [favoritesOutlineView scrollRowToVisible:[favoritesOutlineView selectedRow]]; - } - else { - previousType = SPTCPIPConnection; - - [self resizeTabViewToConnectionType:SPTCPIPConnection animating:NO]; - } - } - - return self; -} - #pragma mark - #pragma mark Connection processes @@ -1284,7 +1158,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v [indexes release]; - [nodes sortUsingFunction:compareFavoritesUsingKey context:key]; + [nodes sortUsingFunction:_compareFavoritesUsingKey context:key]; [nodes addObjectsFromArray:groupNodes]; @@ -1304,7 +1178,7 @@ static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, v * * @return An integer (NSComparisonResult) indicating the order of the comparison */ -static NSComparisonResult compareFavoritesUsingKey(id favorite1, id favorite2, void *key) +static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, void *key) { NSString *dictKey = (NSString *)key; -- cgit v1.2.3 From 92c07b88860b19bd78d2a1bec98e6b17bc458c03 Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Mon, 23 Jan 2012 22:58:29 +0000 Subject: When exporting a single favorite, make the filename the name of the favorite. --- Source/SPConnectionController.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index c5119bb7..f0bfecfe 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -885,10 +885,12 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, { NSSavePanel *savePanel = [NSSavePanel savePanel]; + NSString *fileName = [[self selectedFavoriteNodes] count] > 1 ? SPExportFavoritesFilename : [[[self selectedFavorite] objectForKey:SPFavoriteNameKey] stringByAppendingPathExtension:@"plist"]; + [savePanel setAccessoryView:exportPanelAccessoryView]; [savePanel beginSheetForDirectory:nil - file:SPExportFavoritesFilename + file:fileName modalForWindow:[dbDocument parentWindow] modalDelegate:self didEndSelector:@selector(importExportFavoritesSheetDidEnd:returnCode:contextInfo:) -- cgit v1.2.3 From 91c69ffc2010a95bd18a6d21cc8665d810ed4128 Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Mon, 23 Jan 2012 23:11:14 +0000 Subject: Tidy up. --- Source/SPConnectionController.m | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index f0bfecfe..7f67edcc 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -148,28 +148,37 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, // If SSL keys have been supplied, verify they exist if (([self type] == SPTCPIPConnection || [self type] == SPSocketConnection) && [self useSSL]) { - if (sslKeyFileLocationEnabled && sslKeyFileLocation - && ![[NSFileManager defaultManager] fileExistsAtPath:[sslKeyFileLocation stringByExpandingTildeInPath]]) + + if (sslKeyFileLocationEnabled && sslKeyFileLocation && + ![[NSFileManager defaultManager] fileExistsAtPath:[sslKeyFileLocation stringByExpandingTildeInPath]]) { [self setSslKeyFileLocationEnabled:NSOffState]; [self setSslKeyFileLocation:nil]; + SPBeginAlertSheet(NSLocalizedString(@"SSL Key File not found", @"SSL key file check error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], self, nil, nil, NSLocalizedString(@"A SSL key file location was specified, but no file was found in the specified location. Please re-select the key file and try again.", @"SSL key file not found message")); + return; } - if (sslCertificateFileLocationEnabled && sslCertificateFileLocation - && ![[NSFileManager defaultManager] fileExistsAtPath:[sslCertificateFileLocation stringByExpandingTildeInPath]]) + + if (sslCertificateFileLocationEnabled && sslCertificateFileLocation && + ![[NSFileManager defaultManager] fileExistsAtPath:[sslCertificateFileLocation stringByExpandingTildeInPath]]) { [self setSslCertificateFileLocationEnabled:NSOffState]; [self setSslCertificateFileLocation:nil]; + SPBeginAlertSheet(NSLocalizedString(@"SSL Certificate File not found", @"SSL certificate file check error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], self, nil, nil, NSLocalizedString(@"A SSL certificate location was specified, but no file was found in the specified location. Please re-select the certificate and try again.", @"SSL certificate file not found message")); + return; } - if (sslCACertFileLocationEnabled && sslCACertFileLocation - && ![[NSFileManager defaultManager] fileExistsAtPath:[sslCACertFileLocation stringByExpandingTildeInPath]]) + + if (sslCACertFileLocationEnabled && sslCACertFileLocation && + ![[NSFileManager defaultManager] fileExistsAtPath:[sslCACertFileLocation stringByExpandingTildeInPath]]) { [self setSslCACertFileLocationEnabled:NSOffState]; [self setSslCACertFileLocation:nil]; + SPBeginAlertSheet(NSLocalizedString(@"SSL Certificate Authority File not found", @"SSL certificate authority file check error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], self, nil, nil, NSLocalizedString(@"A SSL Certificate Authority certificate location was specified, but no file was found in the specified location. Please re-select the Certificate Authority certificate and try again.", @"SSL CA certificate file not found message")); + return; } } @@ -196,6 +205,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, if (connectionKeychainItemName) { if ([[keychain getPasswordForName:connectionKeychainItemName account:connectionKeychainItemAccount] isEqualToString:[self password]]) { [self setPassword:[[NSString string] stringByPaddingToLength:[[self password] length] withString:@"sp" startingAtIndex:0]]; + [[standardPasswordField undoManager] removeAllActionsWithTarget:standardPasswordField]; [[socketPasswordField undoManager] removeAllActionsWithTarget:socketPasswordField]; [[sshPasswordField undoManager] removeAllActionsWithTarget:sshPasswordField]; @@ -210,7 +220,8 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, if ([[keychain getPasswordForName:connectionSSHKeychainItemName account:connectionSSHKeychainItemAccount] isEqualToString:[self sshPassword]]) { [self setSshPassword:[[NSString string] stringByPaddingToLength:[[self sshPassword] length] withString:@"sp" startingAtIndex:0]]; [[sshSSHPasswordField undoManager] removeAllActionsWithTarget:sshSSHPasswordField]; - } else { + } + else { [connectionSSHKeychainItemName release], connectionSSHKeychainItemName = nil; [connectionSSHKeychainItemAccount release], connectionSSHKeychainItemAccount = nil; } @@ -227,6 +238,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, // Initiate the SSH connection process for tunnels if ([self type] == SPSSHTunnelConnection) { [self performSelector:@selector(initiateSSHTunnelConnection) withObject:nil afterDelay:0.0]; + return; } @@ -900,6 +912,9 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, #pragma mark - #pragma mark Accessors +/** + * Returns the main outline view instance. + */ - (SPFavoritesOutlineView *)favoritesOutlineView { return favoritesOutlineView; -- cgit v1.2.3 From 816c870b93ad7bd3078d8b7a700e24354adfe4af Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Tue, 24 Jan 2012 23:19:17 +0000 Subject: Tidy up. --- Source/SPConnectionController.m | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 7f67edcc..62767fe8 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -312,33 +312,40 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, } permittedFileTypes = [NSArray arrayWithObjects:@"pem", @"", nil]; + [keySelectionPanel setAccessoryView:sshKeyLocationHelp]; - + } // SSL key file location: - } else if (sender == standardSSLKeyFileButton || sender == socketSSLKeyFileButton) { + else if (sender == standardSSLKeyFileButton || sender == socketSSLKeyFileButton) { if ([sender state] == NSOffState) { [self setSslKeyFileLocation:nil]; return; } + permittedFileTypes = [NSArray arrayWithObjects:@"pem", @"key", @"", nil]; - [keySelectionPanel setAccessoryView:sslKeyFileLocationHelp]; + [keySelectionPanel setAccessoryView:sslKeyFileLocationHelp]; + } // SSL certificate file location: - } else if (sender == standardSSLCertificateButton || sender == socketSSLCertificateButton) { + else if (sender == standardSSLCertificateButton || sender == socketSSLCertificateButton) { if ([sender state] == NSOffState) { [self setSslCertificateFileLocation:nil]; return; } + permittedFileTypes = [NSArray arrayWithObjects:@"pem", @"cert", @"crt", @"", nil]; - [keySelectionPanel setAccessoryView:sslCertificateLocationHelp]; + [keySelectionPanel setAccessoryView:sslCertificateLocationHelp]; + } // SSL CA certificate file location: - } else if (sender == standardSSLCACertButton || sender == socketSSLCACertButton) { + else if (sender == standardSSLCACertButton || sender == socketSSLCACertButton) { if ([sender state] == NSOffState) { [self setSslCACertFileLocation:nil]; return; } + permittedFileTypes = [NSArray arrayWithObjects:@"pem", @"cert", @"crt", @"", nil]; + [keySelectionPanel setAccessoryView:sslCACertLocationHelp]; } -- cgit v1.2.3 From dcff7d1299bd0887736f7464698b61ef18a94416 Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sat, 28 Jan 2012 17:48:40 +0000 Subject: Tidy up. --- Source/SPConnectionController.m | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 62767fe8..4b7db791 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -939,9 +939,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, id newObject = [change objectForKey:NSKeyValueChangeNewKey]; if (oldObject != newObject) { - NSMutableDictionary *favorite = [self selectedFavorite]; - - [favorite setObject:(newObject) ? newObject : @"" forKey:keyPath]; + [[self selectedFavorite] setObject:(newObject) ? newObject : @"" forKey:keyPath]; // Save the new data to disk [favoritesController saveFavorites]; -- cgit v1.2.3 From 524e8c356b4074f5be5933b0551374a130a8f6d1 Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sun, 18 Mar 2012 20:05:36 +0000 Subject: Bring outline view branch up to date with trunk (r3471:r3517). --- Source/SPConnectionController.m | 1 + 1 file changed, 1 insertion(+) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 4b7db791..75752c1d 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -40,6 +40,7 @@ #import "SPTreeNode.h" #import "SPFavoritesExporter.h" #import "SPFavoritesImporter.h" +#import "SPMySQL.h" // Constants static NSString *SPRemoveNode = @"RemoveNode"; -- cgit v1.2.3 From 4cad6f0e6e4fb497b480256c2abe3de34ebf225c Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Mon, 16 Apr 2012 20:16:52 +0000 Subject: Bring outline view branch up to date with trunk. --- Source/SPConnectionController.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 75752c1d..1a7668fd 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -40,7 +40,8 @@ #import "SPTreeNode.h" #import "SPFavoritesExporter.h" #import "SPFavoritesImporter.h" -#import "SPMySQL.h" + +#import // Constants static NSString *SPRemoveNode = @"RemoveNode"; @@ -1440,6 +1441,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, if (connectionSSHKeychainItemName) [connectionSSHKeychainItemName release]; if (connectionSSHKeychainItemAccount) [connectionSSHKeychainItemAccount release]; if (currentFavorite) [currentFavorite release], currentFavorite = nil; + if (favoritesRoot) [favoritesRoot release], favoritesRoot = nil; [super dealloc]; } -- cgit v1.2.3 From 765a45a7837fb3d51eac13cd1359ab767c536478 Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sun, 29 Apr 2012 10:33:34 +0000 Subject: Restrict favorites import to only plists. --- Source/SPConnectionController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Source/SPConnectionController.m') diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 1a7668fd..af46f564 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -892,7 +892,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, [openPanel beginSheetForDirectory:nil file:nil - types:nil + types:[NSArray arrayWithObject:@"plist"] modalForWindow:[dbDocument parentWindow] modalDelegate:self didEndSelector:@selector(importExportFavoritesSheetDidEnd:returnCode:contextInfo:) -- cgit v1.2.3