diff options
author | rowanbeentje <rowan@beent.je> | 2012-10-06 11:48:15 +0000 |
---|---|---|
committer | rowanbeentje <rowan@beent.je> | 2012-10-06 11:48:15 +0000 |
commit | ecb5c70566d1303288e4faf170bda40672a799e1 (patch) | |
tree | 2882fa5fd6f25eed9c754e810785f5834225b95f /Source/SPConnectionController.m | |
parent | ada181f6fe5b010a5ab56030d16b35e92e58af10 (diff) | |
download | sequelpro-ecb5c70566d1303288e4faf170bda40672a799e1.tar.gz sequelpro-ecb5c70566d1303288e4faf170bda40672a799e1.tar.bz2 sequelpro-ecb5c70566d1303288e4faf170bda40672a799e1.zip |
Change the connection screen interface, particularly relating to favourite editing on the connection screen (Issue #1339, Issue #1440):
- No longer save changes made to connection favourites as soon as the interface is updated
- Alter the interfaace if favourites are editing, offering to save the changes either to the old connection favourite or to a new favourite
- Add a "Test connection" button to verify changes before saving
- Add a "Quick Connect" entry to the top of the connection view's favourites list so a blank form is always available
- Use a custom highlight when editing favourites to show the favourite has changed but is still linked
- Reduce the margin on the left-hand side of the connection favourites list to increase the available space
- Alter favourite name generation, making it less aggressive when generating names from partial details (eg creating names of just "@") and removing the user
- Alter key icon usage to correctly update the button appearance if an SSL or SSH key is selected
Diffstat (limited to 'Source/SPConnectionController.m')
-rw-r--r-- | Source/SPConnectionController.m | 581 |
1 files changed, 355 insertions, 226 deletions
diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index d6078f1d..a6ca5f75 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -31,6 +31,7 @@ // More info at <http://code.google.com/p/sequel-pro/> #import "SPConnectionController.h" +#import "SPConnectionHandler.h" #import "SPDatabaseDocument.h" #ifndef SP_REFACTOR /* headers */ @@ -67,6 +68,10 @@ static NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; @interface SPConnectionController () +// Privately redeclare as read/write to get the synthesized setter +@property (readwrite, assign) BOOL isEditingConnection; + +- (void)_saveCurrentDetailsCreatingNewFavorite:(BOOL)createNewFavorite; - (BOOL)_checkHost; #ifndef SP_REFACTOR - (void)_sortFavorites; @@ -83,13 +88,22 @@ static NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; - (SPTreeNode *)_favoriteNodeForFavoriteID:(NSInteger)favoriteID; - (NSString *)_stripInvalidCharactersFromString:(NSString *)subject; -- (void)_updateFavoritePasswordsFromField:(NSControl *)control; +- (NSString *)_generateNameForConnection; + +- (void)_startEditingConnection; static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, void *key); #endif @end +@interface SPConnectionController (SPConnectionControllerDelegate) + +- (void)_stopEditingConnection; + +@end + + @implementation SPConnectionController @synthesize delegate; @@ -125,6 +139,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, @synthesize connectionSSHKeychainItemAccount; @synthesize isConnecting; +@synthesize isEditingConnection; #pragma mark - #pragma mark Connection processes @@ -143,6 +158,9 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, if (sender == favoritesOutlineView && [favoritesOutlineView clickedRow] <= 0) return; #endif + // If triggered via the "Test Connection" button, set the state - otherwise clear it + isTestingConnection = (sender == testConnectButton); + // 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")); @@ -212,9 +230,9 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, // Disable the favorites outline view to prevent further connections attempts [favoritesOutlineView setEnabled:NO]; - [addToFavoritesButton setHidden:YES]; [helpButton setHidden:YES]; [connectButton setEnabled:NO]; + [testConnectButton setEnabled:NO]; [progressIndicator startAnimation:self]; [progressIndicatorText setHidden:NO]; #endif @@ -226,7 +244,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, // have been changed or not; if not, leave the mark in place and remove the password from the field // for increased security. #ifndef SP_REFACTOR - if (connectionKeychainItemName) { + if (connectionKeychainItemName && !isTestingConnection) { if ([[keychain getPasswordForName:connectionKeychainItemName account:connectionKeychainItemAccount] isEqualToString:[self password]]) { [self setPassword:[[NSString string] stringByPaddingToLength:[[self password] length] withString:@"sp" startingAtIndex:0]]; @@ -240,7 +258,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, } } - if (connectionSSHKeychainItemName) { + if (connectionSSHKeychainItemName && !isTestingConnection) { 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]; @@ -302,10 +320,15 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, SPTreeNode *node = [self selectedFavoriteNode]; if (node) { + if (node == quickConnectItem) { + return; + } + // 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 node's name else { [favoritesOutlineView editColumn:0 row:[favoritesOutlineView selectedRow] withEvent:nil select:YES]; @@ -325,6 +348,11 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, keySelectionPanel = [NSOpenPanel openPanel]; [keySelectionPanel setShowsHiddenFiles:[prefs boolForKey:SPHiddenKeyFileVisibilityKey]]; + // If the button was toggled off, ensure editing is ended + if ([sender state] == NSOffState) { + [self _startEditingConnection]; + } + // Switch details by sender. // First, SSH keys: if (sender == sshSSHKeyButton) { @@ -402,6 +430,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, - (IBAction)updateSSLInterface:(id)sender { [self resizeTabViewToConnectionType:[self type] animating:YES]; + [self _startEditingConnection]; } /** @@ -436,7 +465,10 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, - (void)resizeTabViewToConnectionType:(NSUInteger)theType animating:(BOOL)animate { NSRect frameRect, targetResizeRect; - NSInteger additionalFormHeight = 55; + + // Use a magic number which needs to be added to the form when calculating resizes - + // including the height of the button areas below. + NSInteger additionalFormHeight = 92; frameRect = [connectionResizeContainer frame]; @@ -529,6 +561,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, currentFavorite = [fav copy]; [connectionResizeContainer setHidden:NO]; + [self _stopEditingConnection]; // Set up the type, also storing it in the previous type store to prevent type "changes" triggering actions NSUInteger connectionType = ([fav objectForKey:SPFavoriteTypeKey] ? [[fav objectForKey:SPFavoriteTypeKey] integerValue] : SPTCPIPConnection); @@ -639,6 +672,14 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, } /** + * Saves the current connection favorite. + */ +- (IBAction)saveFavorite:(id)sender +{ + [self _saveCurrentDetailsCreatingNewFavorite:NO]; +} + +/** * Adds a new connection favorite. */ - (IBAction)addFavorite:(id)sender @@ -687,7 +728,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, [[[[NSApp delegate] preferenceController] generalPreferencePane] updateDefaultFavoritePopup]; - favoriteNameFieldWasTouched = NO; + favoriteNameFieldWasAutogenerated = YES; [favoritesOutlineView editColumn:0 row:[favoritesOutlineView selectedRow] withEvent:nil select:YES]; } @@ -698,98 +739,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, */ - (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]) { - SPBeginAlertSheet(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), - NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], nil, nil, nil, - NSLocalizedString(@"Insufficient details provided to establish a connection. Please provide at least a host.", @"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, [dbDocument parentWindow], nil, nil, nil, - NSLocalizedString(@"Please enter the hostname for the SSH Tunnel, or disable the SSH Tunnel.", @"message of panel when ssh details are incomplete")); - return; - } - - // Ensure that a socket connection is not inadvertently used - 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 numberWithInteger:[self useSSL]] forKey:SPFavoriteUseSSLKey]; - [newFavorite setObject:[NSNumber numberWithInteger:[self sslKeyFileLocationEnabled]] forKey:SPFavoriteSSLKeyFileLocationEnabledKey]; - if ([self sslKeyFileLocation]) [newFavorite setObject:[self sslKeyFileLocation] forKey:SPFavoriteSSLKeyFileLocationKey]; - [newFavorite setObject:[NSNumber numberWithInteger:[self sslCertificateFileLocationEnabled]] forKey:SPFavoriteSSLCertificateFileLocationEnabledKey]; - if ([self sslCertificateFileLocation]) [newFavorite setObject:[self sslCertificateFileLocation] forKey:SPFavoriteSSLCertificateFileLocationKey]; - [newFavorite setObject:[NSNumber numberWithInteger:[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]; - if ([self sshPort]) [newFavorite setObject:[self sshPort] forKey:SPFavoriteSSHPortKey]; - [newFavorite setObject:[NSNumber numberWithInteger:[self sshKeyLocationEnabled]] forKey:SPFavoriteSSHKeyLocationEnabledKey]; - if ([self sshKeyLocation]) [newFavorite setObject:[self sshKeyLocation] forKey:SPFavoriteSSHKeyLocationKey]; - - // 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]]] - account:[keychain accountForUser:[self user] host:(([self type] == SPSocketConnection) ? @"localhost" : [self host]) database:[self database]]]; - } - - // 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]]]; - } - - SPTreeNode *selectedNode = [self selectedFavoriteNode]; - - SPTreeNode *node = [favoritesController addFavoriteNodeWithData:newFavorite asChildOfNode:[selectedNode isGroup] ? selectedNode : nil]; - - [self _reloadFavoritesViewData]; - [self _selectNode:node]; - - // Update the favorites popup button in the preferences - [[[[NSApp delegate] preferenceController] generalPreferencePane] updateDefaultFavoritePopup]; + [self _saveCurrentDetailsCreatingNewFavorite:YES]; } /** @@ -805,9 +755,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, [self _reloadFavoritesViewData]; [self _selectNode:node]; - - isEditing = YES; - + [favoritesOutlineView editColumn:0 row:[favoritesOutlineView selectedRow] withEvent:nil select:YES]; } @@ -999,26 +947,9 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, */ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - NSMutableDictionary *selectedFavorite = [self selectedFavorite]; - if (!selectedFavorite) return; - - id oldObject = [change objectForKey:NSKeyValueChangeOldKey]; - id newObject = [change objectForKey:NSKeyValueChangeNewKey]; - - if (oldObject != newObject) { - [selectedFavorite setObject:![newObject isNSNull] ? newObject : @"" forKey:keyPath]; - - // Save the new data to disk - [favoritesController saveFavorites]; - - [self _reloadFavoritesViewData]; - - if ([keyPath isEqualToString:SPFavoriteNameKey]) { - [[NSNotificationCenter defaultCenter] postNotificationName:SPConnectionFavoritesChangedNotification object:self]; - } - } } + #pragma mark - #pragma mark Sheet methods @@ -1082,6 +1013,8 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, [self setSslCACertFileLocation:abbreviatedFileName]; } + + [self _startEditingConnection]; } /** @@ -1119,15 +1052,9 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, if (returnCode == NSAlertAlternateReturn) { [self setType:SPSocketConnection]; [self setHost:@""]; -#ifndef SP_REFACTOR - [self _updateFavoritePasswordsFromField:standardSQLHostField]; -#endif } else { [self setHost:@"127.0.0.1"]; -#ifndef SP_REFACTOR - [self _updateFavoritePasswordsFromField:standardSQLHostField]; -#endif } } @@ -1135,6 +1062,245 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, #pragma mark Private API /** + * Take the current details and either save them to the currently selected + * favourite, or create a new connection favourite using them. + * If creating a new favourite, also select it and ensure the selected + * favourite is visible. + */ +- (void)_saveCurrentDetailsCreatingNewFavorite:(BOOL)createNewFavorite +{ + + // Complete any active editing + if ([[connectionView window] firstResponder]) { + [[connectionView window] endEditingFor:[[connectionView window] firstResponder]]; + } + + + // 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], nil, nil, nil, + NSLocalizedString(@"Insufficient details provided to establish a connection. Please provide at least a host.", @"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, [dbDocument parentWindow], nil, nil, nil, + NSLocalizedString(@"Please enter the hostname for the SSH Tunnel, or disable the SSH Tunnel.", @"message of panel when ssh details are incomplete")); + return; + } + + // Ensure that a socket connection is not inadvertently used + if (![self _checkHost]) return; + + + // Set up the favourite, or get the mutable dictionary for the current favourite. + NSMutableDictionary *theFavorite; + if (createNewFavorite) { + theFavorite = [NSMutableDictionary dictionary]; + [theFavorite setObject:[self _createNewFavoriteID] forKey:SPFavoriteIDKey]; + } else { + if (!currentFavorite) { + [NSException raise:NSInternalInconsistencyException format:@"Tried to save a current favourite with no currentFavorite"]; + } + theFavorite = [self selectedFavorite]; + } + + // Set the name - either taking the provided name, or generating one. + if ([[self name] length]) { + [theFavorite setObject:[self name] forKey:SPFavoriteNameKey]; + } else { + NSString *favoriteName = [self _generateNameForConnection]; + if (!favoriteName) { + favoriteName = NSLocalizedString(@"Untitled", @"Name for an untitled connection"); + } + [theFavorite setObject:favoriteName forKey:SPFavoriteNameKey]; + } + + // Set standard details for the connection + [theFavorite setObject:[NSNumber numberWithInteger:[self type]] forKey:SPFavoriteTypeKey]; + if ([self host]) { + [theFavorite setObject:[self host] forKey:SPFavoriteHostKey]; + } + if ([self socket]) { + [theFavorite setObject:[self socket] forKey:SPFavoriteSocketKey]; + } + if ([self user]) { + [theFavorite setObject:[self user] forKey:SPFavoriteUserKey]; + } + if ([self port]) { + [theFavorite setObject:[self port] forKey:SPFavoritePortKey]; + } + if ([self database]) { + [theFavorite setObject:[self database] forKey:SPFavoriteDatabaseKey]; + } + + // SSL details + if ([self useSSL]) { + [theFavorite setObject:[NSNumber numberWithInteger:[self useSSL]] forKey:SPFavoriteUseSSLKey]; + } + [theFavorite setObject:[NSNumber numberWithInteger:[self sslKeyFileLocationEnabled]] forKey:SPFavoriteSSLKeyFileLocationEnabledKey]; + if ([self sslKeyFileLocation]) { + [theFavorite setObject:[self sslKeyFileLocation] forKey:SPFavoriteSSLKeyFileLocationKey]; + } + [theFavorite setObject:[NSNumber numberWithInteger:[self sslCertificateFileLocationEnabled]] forKey:SPFavoriteSSLCertificateFileLocationEnabledKey]; + if ([self sslCertificateFileLocation]) { + [theFavorite setObject:[self sslCertificateFileLocation] forKey:SPFavoriteSSLCertificateFileLocationKey]; + } + [theFavorite setObject:[NSNumber numberWithInteger:[self sslCACertFileLocationEnabled]] forKey:SPFavoriteSSLCACertFileLocationEnabledKey]; + if ([self sslCACertFileLocation]) { + [theFavorite setObject:[self sslCACertFileLocation] forKey:SPFavoriteSSLCACertFileLocationKey]; + } + + // SSH details + if ([self sshHost]) { + [theFavorite setObject:[self sshHost] forKey:SPFavoriteSSHHostKey]; + } + if ([self sshUser]) { + [theFavorite setObject:[self sshUser] forKey:SPFavoriteSSHUserKey]; + } + if ([self sshPort]) { + [theFavorite setObject:[self sshPort] forKey:SPFavoriteSSHPortKey]; + } + [theFavorite setObject:[NSNumber numberWithInteger:[self sshKeyLocationEnabled]] forKey:SPFavoriteSSHKeyLocationEnabledKey]; + if ([self sshKeyLocation]) { + [theFavorite setObject:[self sshKeyLocation] forKey:SPFavoriteSSHKeyLocationKey]; + } + + + /** + * Password handling for the SQL connection + */ + NSString *oldKeychainName, *oldKeychainAccount, *newKeychainName, *newKeychainAccount;; + NSString *oldHostnameForPassword = ([[currentFavorite objectForKey:SPFavoriteTypeKey] integerValue] == SPSocketConnection) ? @"localhost" : [currentFavorite objectForKey:SPFavoriteHostKey]; + NSString *newHostnameForPassword = ([self type] == SPSocketConnection) ? @"localhost" : [self host]; + + // Grab the password for this connection + // Add the password to keychain as appropriate + NSString *sqlPassword = [self password]; + if (mySQLConnection && connectionKeychainItemName) { + sqlPassword = [keychain getPasswordForName:connectionKeychainItemName account:connectionKeychainItemAccount]; + } + + // If creating a new favourite, always add the password to the keychain if it's set + if (createNewFavorite && [sqlPassword length]) { + [keychain addPassword:sqlPassword + forName:[keychain nameForFavoriteName:[theFavorite objectForKey:SPFavoriteNameKey] id:[theFavorite objectForKey:SPFavoriteIDKey]] + account:[keychain accountForUser:[self user] host:newHostnameForPassword database:[self database]]]; + } + + // If not creating a new favourite... + if (!createNewFavorite) { + + // Get the old keychain name and account strings + oldKeychainName = [keychain nameForFavoriteName:[currentFavorite objectForKey:SPFavoriteNameKey] id:[currentFavorite objectForKey:SPFavoriteIDKey]]; + oldKeychainAccount = [keychain accountForUser:[currentFavorite objectForKey:SPFavoriteUserKey] host:oldHostnameForPassword database:[currentFavorite objectForKey:SPFavoriteDatabaseKey]]; + + // If there's no new password, remove the old item from the keychain + if (![sqlPassword length]) { + [keychain deletePasswordForName:oldKeychainName account:oldKeychainAccount]; + + // Otherwise, set up the new keychain name and account strings and create or edit the item + } else { + newKeychainName = [keychain nameForFavoriteName:[theFavorite objectForKey:SPFavoriteNameKey] id:[theFavorite objectForKey:SPFavoriteIDKey]]; + newKeychainAccount = [keychain accountForUser:[self user] host:newHostnameForPassword database:[self database]]; + if ([keychain passwordExistsForName:oldKeychainName account:oldKeychainAccount]) { + [keychain updateItemWithName:oldKeychainName account:oldKeychainAccount toName:newKeychainName account:newKeychainAccount password:sqlPassword]; + } else { + [keychain addPassword:sqlPassword forName:newKeychainName account:newKeychainAccount]; + } + } + } + sqlPassword = nil; + + + /** + * Password handling for the SSH connection + */ + NSString *theSSHPassword = [self sshPassword]; + if (mySQLConnection && connectionSSHKeychainItemName) { + theSSHPassword = [keychain getPasswordForName:connectionSSHKeychainItemName account:connectionSSHKeychainItemAccount]; + } + + // If creating a new favourite, always add the password if it's set + if (createNewFavorite && [theSSHPassword length]) { + [keychain addPassword:theSSHPassword + forName:[keychain nameForSSHForFavoriteName:[theFavorite objectForKey:SPFavoriteNameKey] id:[theFavorite objectForKey:SPFavoriteIDKey]] + account:[keychain accountForSSHUser:[self sshUser] sshHost:[self sshHost]]]; + } + + // If not creating a new favourite... + if (!createNewFavorite) { + + // Get the old keychain name and account strings + oldKeychainName = [keychain nameForSSHForFavoriteName:[currentFavorite objectForKey:SPFavoriteNameKey] id:[currentFavorite objectForKey:SPFavoriteIDKey]]; + oldKeychainAccount = [keychain accountForSSHUser:[currentFavorite objectForKey:SPFavoriteSSHUserKey] sshHost:[currentFavorite objectForKey:SPFavoriteSSHHostKey]]; + + // If there's no new password, remove the old item from the keychain + if (![theSSHPassword length]) { + [keychain deletePasswordForName:oldKeychainName account:oldKeychainAccount]; + + // Otherwise, set up the new keychain name and account strings and create or edit the item + } else { + newKeychainName = [keychain nameForSSHForFavoriteName:[theFavorite objectForKey:SPFavoriteNameKey] id:[theFavorite objectForKey:SPFavoriteIDKey]]; + newKeychainAccount = [keychain accountForSSHUser:[self sshUser] sshHost:[self sshHost]]; + if ([keychain passwordExistsForName:oldKeychainName account:oldKeychainAccount]) { + [keychain updateItemWithName:oldKeychainName account:oldKeychainAccount toName:newKeychainName account:newKeychainAccount password:theSSHPassword]; + } else { + [keychain addPassword:theSSHPassword forName:newKeychainName account:newKeychainAccount]; + } + } + } + theSSHPassword = nil; + + + /** + * Saving the connection + */ + + // If creating the connection, add to the favourites tree. + if (createNewFavorite) { + SPTreeNode *selectedNode = [self selectedFavoriteNode]; + SPTreeNode *parentNode = nil; + + // If the current node is a group node, create the favorite as a child of it + if ([selectedNode isGroup]) { + parentNode = selectedNode; + + // Otherwise, create the new node as a sibling of the selected node if possible + } else if ([selectedNode parentNode] && [selectedNode parentNode] != favoritesRoot) { + parentNode = (SPTreeNode *)[selectedNode parentNode]; + } + + // Add the new node and select it + SPTreeNode *newNode = [favoritesController addFavoriteNodeWithData:theFavorite asChildOfNode:parentNode]; + [self _reloadFavoritesViewData]; + [self _selectNode:newNode]; + + // Update the favorites popup button in the preferences + [[[[NSApp delegate] preferenceController] generalPreferencePane] updateDefaultFavoritePopup]; + + // Otherwise, if editing the favourite, update it + } else { + [[[self selectedFavoriteNode] representedObject] setNodeFavorite:theFavorite]; + + // Save the new data to disk + [favoritesController saveFavorites]; + + [self _stopEditingConnection]; + + if (currentFavorite) [currentFavorite release], currentFavorite = nil; + currentFavorite = [theFavorite copy]; + + [self _reloadFavoritesViewData]; + } + + [[NSNotificationCenter defaultCenter] postNotificationName:SPConnectionFavoritesChangedNotification object:self]; +} + +/** * Check the host field and ensure it isn't set to 'localhost' for non-socket connections. */ - (BOOL)_checkHost @@ -1281,16 +1447,12 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, } // Update the name for newly added favorites if not already touched by the user, by triggering a KVO update - if (![[self name] length]) { - [self setName:[NSString stringWithFormat:@"%@@%@", - ([favorite objectForKey:SPFavoriteUserKey]) ? [favorite objectForKey:SPFavoriteUserKey] : @"", - ((previousType == SPSocketConnection) ? @"localhost" : - (([favorite objectForKey:SPFavoriteHostKey]) ? [favorite valueForKeyPath:SPFavoriteHostKey] : @"")) - ]]; + if (![[self name] length] || favoriteNameFieldWasAutogenerated) { + NSString *favoriteName = [self _generateNameForConnection]; + if (favoriteName) { + [self setName:favoriteName]; + } } - - // Trigger a password change in response to host changes - [self _updateFavoritePasswordsFromField:nil]; } /** @@ -1350,13 +1512,12 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, [dbDocument setIsProcessing:NO]; // Reset the UI - [addToFavoritesButton setHidden:NO]; - [addToFavoritesButton display]; [helpButton setHidden:NO]; [helpButton display]; [connectButton setTitle:NSLocalizedString(@"Connect", @"connect button")]; [connectButton setEnabled:YES]; [connectButton display]; + [testConnectButton setEnabled:YES]; [progressIndicator stopAnimation:self]; [progressIndicator display]; [progressIndicatorText setHidden:YES]; @@ -1463,6 +1624,8 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, SPTreeNode *favoriteNode = nil; if (!favoritesRoot) return favoriteNode; + + if (!favoriteID) return quickConnectItem; for (SPTreeNode *node in [favoritesRoot allChildLeafs]) { @@ -1486,106 +1649,70 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, return [result stringByReplacingOccurrencesOfString:@"\n" withString:@""]; } -#ifndef SP_REFACTOR /** - * 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. + * Generate a name for the current connection based on any other populated details. + * Currently uses the host and database fields. + * If a name cannot be generated because there are insufficient other details, returns nil. */ -- (void)_updateFavoritePasswordsFromField:(NSControl *)control +- (NSString *)_generateNameForConnection { - if (!currentFavorite) return; - - NSDictionary *oldFavorite = currentFavorite; - 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]]; - - // If there's no new password, remove the old item from the keychain - if (![passwordValue length]) { - [keychain deletePasswordForName:oldKeychainName account:oldKeychainAccount]; + NSString *aName; - // Otherwise, set up the new keychain name and account strings and create or edit the item - } else { - newKeychainName = [keychain nameForFavoriteName:[newFavorite objectForKey:SPFavoriteNameKey] id:[newFavorite objectForKey:SPFavoriteIDKey]]; - newKeychainAccount = [keychain accountForUser:[newFavorite objectForKey:SPFavoriteUserKey] host:newHostnameForPassword database:[newFavorite objectForKey:SPFavoriteDatabaseKey]]; - if ([keychain passwordExistsForName:oldKeychainName account:oldKeychainAccount]) { - [keychain updateItemWithName:oldKeychainName account:oldKeychainAccount toName:newKeychainName account:newKeychainAccount password:passwordValue]; - } else { - [keychain addPassword:passwordValue forName:newKeychainName account:newKeychainAccount]; - } - } - - // Synch password changes - [standardPasswordField setStringValue:passwordValue?passwordValue:@""]; - [socketPasswordField setStringValue:passwordValue?passwordValue:@""]; - [sshPasswordField setStringValue:passwordValue?passwordValue:@""]; - - passwordValue = @""; + if ([self type] != SPSocketConnection && ![[self host] length]) { + return nil; } - - // 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]]; - // If there's no new password, delete the keychain item - if (![[sshSSHPasswordField stringValue] length]) { - [keychain deletePasswordForName:oldKeychainName account:oldKeychainAccount]; + aName = ([self type] == SPSocketConnection) ? @"localhost" : [self host]; - // Otherwise, set up the new keychain name and account strings and create or update the keychain item - } else { - newKeychainName = [keychain nameForSSHForFavoriteName:[newFavorite objectForKey:SPFavoriteNameKey] id:[newFavorite objectForKey:SPFavoriteIDKey]]; - newKeychainAccount = [keychain accountForSSHUser:[newFavorite objectForKey:SPFavoriteSSHUserKey] sshHost:[newFavorite objectForKey:SPFavoriteSSHHostKey]]; - if ([keychain passwordExistsForName:oldKeychainName account:oldKeychainAccount]) { - [keychain updateItemWithName:oldKeychainName account:oldKeychainAccount toName:newKeychainName account:newKeychainAccount password:[sshSSHPasswordField stringValue]]; - } else { - [keychain addPassword:[sshSSHPasswordField stringValue] forName:newKeychainName account:newKeychainAccount]; - } - } + if ([[self database] length]) { + aName = [NSString stringWithFormat:@"%@/%@", aName, [self database]]; } - - // Update the current favorite - if (currentFavorite) [currentFavorite release], currentFavorite = nil; - - if ([[favoritesOutlineView selectedRowIndexes] count]) { - currentFavorite = [[[[self selectedFavoriteNode] representedObject] nodeFavorite] copy]; + + return aName; +} + + +/** + * If editing is not already active, mark editing as starting, triggering UI updates + * to match. + */ +- (void)_startEditingConnection +{ + + // If not connecting, hide the connection status text to reflect changes + if (!isConnecting) { + [progressIndicatorText setHidden:YES]; } + + if (isEditingConnection) return; + + // Fade and move the edit button area in + [editButtonsView setAlphaValue:0.0]; + [editButtonsView setHidden:NO]; + [editButtonsView setFrameOrigin:NSMakePoint([editButtonsView frame].origin.x, [editButtonsView frame].origin.y - 40)]; + [[editButtonsView animator] setFrameOrigin:NSMakePoint([editButtonsView frame].origin.x, [editButtonsView frame].origin.y + 40)]; + [[editButtonsView animator] setAlphaValue:1.0]; + + // Update the "Save" button state as appropriate + [saveFavoriteButton setEnabled:([self selectedFavorite] != nil)]; + + // Show the area to allow saving the changes + [self setIsEditingConnection:YES]; + [favoritesOutlineView setNeedsDisplayInRect:[favoritesOutlineView rectOfRow:[favoritesOutlineView selectedRow]]]; +} + +/** + * If editing is active, mark editing as complete, triggering UI updates to match. + */ +- (void)_stopEditingConnection +{ + if (!isEditingConnection) return; + + [editButtonsView setHidden:YES]; + [progressIndicatorText setHidden:YES]; + + [self setIsEditingConnection:NO]; } -#endif #pragma mark - @@ -1624,6 +1751,8 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, #ifndef SP_REFACTOR [folderImage release], folderImage = nil; + [quickConnectItem release], quickConnectItem = nil; + [quickConnectCell release], quickConnectCell = nil; #endif for (id retainedObject in nibObjectsToRelease) [retainedObject release]; |