diff options
author | Max <dmoagx@users.noreply.github.com> | 2017-12-28 01:36:08 +0100 |
---|---|---|
committer | Max <dmoagx@users.noreply.github.com> | 2018-01-20 02:42:35 +0100 |
commit | d0bf2ec8a55bb64555d5d0bc77a770a938b68be7 (patch) | |
tree | 2e92f5059a72e2b7077f46b5788410272325423d | |
parent | b7830d694092ba3418b803448798f9b7d9687bde (diff) | |
download | sequelpro-d0bf2ec8a55bb64555d5d0bc77a770a938b68be7.tar.gz sequelpro-d0bf2ec8a55bb64555d5d0bc77a770a938b68be7.tar.bz2 sequelpro-d0bf2ec8a55bb64555d5d0bc77a770a938b68be7.zip |
merge SPConnectionController (part of #2789)
-rw-r--r-- | Source/SPConnectionController.h | 28 | ||||
-rw-r--r-- | Source/SPConnectionController.m | 1620 | ||||
-rw-r--r-- | Source/SPConnectionControllerDataSource.h | 42 | ||||
-rw-r--r-- | Source/SPConnectionControllerDataSource.m | 121 | ||||
-rw-r--r-- | Source/SPConnectionControllerDelegate.h | 47 | ||||
-rw-r--r-- | Source/SPConnectionControllerDelegate.m | 756 | ||||
-rw-r--r-- | Source/SPConnectionControllerDelegateProtocol.h | 6 | ||||
-rw-r--r-- | Source/SPConnectionControllerInitializer.h | 49 | ||||
-rw-r--r-- | Source/SPConnectionControllerInitializer.m | 417 | ||||
-rw-r--r-- | Source/SPConnectionHandler.h | 53 | ||||
-rw-r--r-- | Source/SPConnectionHandler.m | 538 | ||||
-rw-r--r-- | Source/SPDatabaseDocument.m | 6 | ||||
-rw-r--r-- | Source/SPFavoritesOutlineView.m | 6 | ||||
-rw-r--r-- | sequel-pro.xcodeproj/project.pbxproj | 24 |
14 files changed, 1649 insertions, 2064 deletions
diff --git a/Source/SPConnectionController.h b/Source/SPConnectionController.h index dd7ac6e5..2da8f58a 100644 --- a/Source/SPConnectionController.h +++ b/Source/SPConnectionController.h @@ -1,5 +1,5 @@ // -// SPConnectionHandler.h +// SPConnectionController.h // sequel-pro // // Created by Stuart Connolly (stuconnolly.com) on November 15, 2010. @@ -30,6 +30,7 @@ #import "SPConnectionControllerDelegateProtocol.h" #import "SPFavoritesExportProtocol.h" +#import "SPFavoritesImportProtocol.h" #import <SPMySQL/SPMySQL.h> @@ -48,7 +49,7 @@ #endif ; -@interface SPConnectionController : NSViewController <SPMySQLConnectionDelegate, NSOpenSavePanelDelegate, SPFavoritesExportProtocol, NSSplitViewDelegate> +@interface SPConnectionController : NSViewController <SPMySQLConnectionDelegate, NSOpenSavePanelDelegate, SPFavoritesImportProtocol, SPFavoritesExportProtocol, NSSplitViewDelegate> { id <SPConnectionControllerDelegateProtocol, NSObject> delegate; @@ -266,4 +267,27 @@ - (SPFavoritesOutlineView *)favoritesOutlineView; #endif + +#pragma mark - SPConnectionHandler + +- (void)initiateMySQLConnection; +- (void)initiateMySQLConnectionInBackground; +- (void)initiateSSHTunnelConnection; + +- (void)mySQLConnectionEstablished; +- (void)sshTunnelCallback:(SPSSHTunnel *)theTunnel; + +- (void)addConnectionToDocument; + +- (void)failConnectionWithTitle:(NSString *)theTitle errorMessage:(NSString *)theErrorMessage detail:(NSString *)errorDetail rawErrorText:(NSString *)rawErrorText; + +#pragma mark - SPConnectionControllerInitializer + +- (id)initWithDocument:(SPDatabaseDocument *)document; + +- (void)loadNib; +- (void)registerForNotifications; +- (void)setUpFavoritesOutlineView; +- (void)setUpSelectedConnectionFavorite; + @end diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 96376404..018ecf93 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -1,5 +1,5 @@ // -// SPConnectionHandler.m +// SPConnectionController.m // sequel-pro // // Created by Stuart Connolly (stuconnolly.com) on November 15, 2010. @@ -29,9 +29,7 @@ // More info at <https://github.com/sequelpro/sequelpro> #import "SPConnectionController.h" -#import "SPConnectionHandler.h" #import "SPDatabaseDocument.h" - #ifndef SP_CODA /* headers */ #import "SPAppController.h" #import "SPPreferenceController.h" @@ -53,6 +51,13 @@ #import "SPNamedNode.h" #import "SPWindowController.h" #import "SPFavoritesOutlineView.h" +#import "SPCategoryAdditions.h" +#ifndef SP_CODA +#import "SPFavoriteTextFieldCell.h" +#import "SPGroupNode.h" +#endif +#import "SPSplitView.h" +#import "SPColorSelectorView.h" #import <SPMySQL/SPMySQL.h> @@ -60,6 +65,13 @@ #ifndef SP_CODA static NSString *SPRemoveNode = @"RemoveNode"; static NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; +static NSString *SPLocalhostAddress = @"127.0.0.1"; + +static NSString *SPDatabaseImage = @"database-small"; +static NSString *SPQuickConnectImage = @"quick-connect-icon.pdf"; +static NSString *SPQuickConnectImageWhite = @"quick-connect-icon-white.pdf"; + +static NSString *SPConnectionViewNibName = @"ConnectionView"; #endif /** @@ -105,12 +117,24 @@ static BOOL FindLinesInFile(NSData *fileData,const void *first,size_t first_len, static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, void *key); #endif -@end - -@interface SPConnectionController (SPConnectionControllerDelegate) +#pragma mark - SPConnectionControllerDelegate - (void)_stopEditingConnection; +#pragma mark - SPConnectionHandlerPrivateAPI + +- (void)_showConnectionTestResult:(NSString *)resultString; + +#pragma mark - SPConnectionControllerDelegate_Private_API + +- (void)_setNodeIsExpanded:(BOOL)expanded fromNotification:(NSNotification *)notification; + +#pragma mark - SPConnectionControllerInitializer_Private_API + +- (void)_restoreOutlineViewStateNode:(SPTreeNode *)node; +- (void)_processFavoritesDataChange:(NSNotification *)aNotification; +- (void)scrollViewFrameChanged:(NSNotification *)aNotification; + @end @implementation SPConnectionController @@ -1967,6 +1991,1590 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, if (sshTunnel) [sshTunnel setConnectionStateChangeSelector:nil delegate:nil], SPClear(sshTunnel); } +#pragma mark - SPConnectionHandler + +/** + * Set up the MySQL connection, either through a successful tunnel or directly in the background. + */ +- (void)initiateMySQLConnection +{ +#ifndef SP_CODA + if (isTestingConnection) { + if (sshTunnel) { + [progressIndicatorText setStringValue:NSLocalizedString(@"Testing MySQL...", @"MySQL connection test very short status message")]; + } + else { + [progressIndicatorText setStringValue:NSLocalizedString(@"Testing connection...", @"Connection test very short status message")]; + } + } + else 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(cancelConnection:)]; + [connectButton setEnabled:YES]; + [connectButton display]; +#endif + + [NSThread detachNewThreadWithName:SPCtxt(@"SPConnectionController MySQL connection task", dbDocument) target:self selector:@selector(initiateMySQLConnectionInBackground) object:nil]; +} + +/** + * Initiates the core of the MySQL connection process on a background thread. + */ +- (void)initiateMySQLConnectionInBackground +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + mySQLConnection = [[SPMySQLConnection alloc] init]; + + // Set up shared details + [mySQLConnection setUsername:[self user]]; + + // Initialise to socket if appropriate. + if ([self type] == SPSocketConnection) { + [mySQLConnection setUseSocket:YES]; + [mySQLConnection setSocketPath:[self socket]]; + + // Otherwise, initialise to host, using tunnel if appropriate + } + else { + [mySQLConnection setUseSocket:NO]; + + if ([self type] == SPSSHTunnelConnection) { + [mySQLConnection setHost:@"127.0.0.1"]; + + [mySQLConnection setPort:[sshTunnel localPort]]; + [mySQLConnection setProxy:sshTunnel]; + } + else { + [mySQLConnection setHost:[self host]]; + + if ([[self port] length]) [mySQLConnection setPort:[[self port] integerValue]]; + } + } + + // Only set the password if there is no Keychain item set and the connection is not being tested. + // The connection will otherwise ask the delegate for passwords in the Keychain. + if ((!connectionKeychainItemName || isTestingConnection) && [self password]) { + [mySQLConnection setPassword:[self password]]; + } + + // Enable SSL if set + if ([self useSSL]) { + [mySQLConnection setUseSSL:YES]; + + if ([self sslKeyFileLocationEnabled]) { + [mySQLConnection setSslKeyFilePath:[self sslKeyFileLocation]]; + } + + if ([self sslCertificateFileLocationEnabled]) { + [mySQLConnection setSslCertificatePath:[self sslCertificateFileLocation]]; + } + + if ([self sslCACertFileLocationEnabled]) { + [mySQLConnection setSslCACertificatePath:[self sslCACertFileLocation]]; + } + + NSString *userSSLCipherList = [prefs stringForKey:SPSSLCipherListKey]; + if(userSSLCipherList) { + //strip out disabled ciphers (e.g. in "foo:bar:--:baz") + NSRange markerPos = [userSSLCipherList rangeOfRegex:@":?--"]; + if(markerPos.location != NSNotFound) { + userSSLCipherList = [userSSLCipherList substringToIndex:markerPos.location]; + } + [mySQLConnection setSslCipherList:userSSLCipherList]; + } + } + + if(![self useCompression]) + [mySQLConnection removeClientFlags:SPMySQLClientFlagCompression]; + + // 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 setTimeout:[[prefs objectForKey:SPConnectionTimeoutValue] integerValue]]; + [mySQLConnection setUseKeepAlive:[[prefs objectForKey:SPUseKeepAlive] boolValue]]; + [mySQLConnection setKeepAliveInterval:[[prefs objectForKey:SPKeepAliveInterval] floatValue]]; + + // Connect + [mySQLConnection connect]; + + if (![mySQLConnection isConnected]) { + if (sshTunnel && !cancellingConnection) { + + // This is a race condition we cannot fix "properly": + // For meaningful error handling we need to also consider the debug output from the SSH connection. + // The SSH debug output might be sligthly delayed though (flush, delegates, ...) or + // there might not even by any output at all (when it is purely a libmysql issue). + // TL;DR: No guaranteed events we could wait for, just trying our luck. + [NSThread sleepForTimeInterval:0.1]; // 100ms + + // If the state is connection refused, attempt the MySQL connection again with the host using the hostfield value. + if ([sshTunnel state] == SPMySQLProxyForwardingFailed) { + if ([sshTunnel localPortFallback]) { + [mySQLConnection setPort:[sshTunnel localPortFallback]]; + [mySQLConnection connect]; + + if (![mySQLConnection isConnected]) { + [NSThread sleepForTimeInterval:0.1]; //100ms + } + } + } + } + + if (![mySQLConnection isConnected]) { + if (!cancellingConnection) { + NSString *errorMessage = @""; + if (sshTunnel && [sshTunnel state] == SPMySQLProxyForwardingFailed) { + 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 lastErrorMessage]]; + [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"SSH port forwarding failed", @"title when ssh tunnel port forwarding failed") errorMessage:errorMessage detail:[sshTunnel debugMessages] rawErrorText:[mySQLConnection lastErrorMessage]]; + } + else if ([mySQLConnection lastErrorID] == 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 lastErrorMessage]]; + [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Access denied!", @"connection failed due to access denied title") errorMessage:errorMessage detail:nil rawErrorText:[mySQLConnection lastErrorMessage]]; + } + else if ([self type] == SPSocketConnection && (![self socket] || ![[self socket] length]) && ![mySQLConnection socketPath]) { + 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 lastErrorMessage]]; + [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Socket not found!", @"socket not found title") errorMessage:errorMessage detail:nil rawErrorText:[mySQLConnection lastErrorMessage]]; + } + 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 lastErrorMessage]]; + [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Socket connection failed!", @"socket connection failed title") errorMessage:errorMessage detail:nil rawErrorText:[mySQLConnection lastErrorMessage]]; + } + 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 lastErrorMessage]]; + [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Connection failed!", @"connection failed title") errorMessage:errorMessage detail:nil rawErrorText:[mySQLConnection lastErrorMessage]]; + } + } + + // Tidy up + isConnecting = NO; + + if (sshTunnel) [sshTunnel disconnect], SPClear(sshTunnel); + + SPClear(mySQLConnection); +#ifndef SP_CODA + if (!cancellingConnection) [self _restoreConnectionInterface]; +#endif + [pool release]; + + return; + } + } + + if ([self database] && ![[self database] isEqualToString:@""]) { + if (![mySQLConnection selectDatabase:[self database]]) { + if (!isTestingConnection) { + [[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 lastErrorMessage]] detail:nil rawErrorText:[mySQLConnection lastErrorMessage]]; + } + + // Tidy up + isConnecting = NO; + + if (sshTunnel) SPClear(sshTunnel); + + SPClear(mySQLConnection); + [self _restoreConnectionInterface]; + if (isTestingConnection) { + [self _showConnectionTestResult:NSLocalizedString(@"Invalid database", @"Invalid database very short status message")]; + } + + [pool release]; + + return; + } + } + + // Connection established + [self performSelectorOnMainThread:@selector(mySQLConnectionEstablished) withObject:nil waitUntilDone:NO]; + + [pool release]; +} + +/** + * 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 +{ + if (isTestingConnection) { + [progressIndicatorText setStringValue:NSLocalizedString(@"Testing SSH...", @"SSH testing very short status message")]; + } else { + [progressIndicatorText setStringValue:NSLocalizedString(@"SSH connecting...", @"SSH connecting very short status message")]; + } + [progressIndicatorText display]; + + [connectButton setTitle:NSLocalizedString(@"Cancel", @"cancel button")]; + [connectButton setAction:@selector(cancelConnection:)]; + [connectButton setEnabled:YES]; + [connectButton 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:[dbDocument parentWindow]]; + + // Add keychain or plaintext password as appropriate - note the checks in initiateConnection. + if (connectionSSHKeychainItemName && !isTestingConnection) { + [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]; +} + +/** + * 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 is only testing the connection, kill the connection + // once established and reset the UI. Also catch connection cancels. + if (isTestingConnection || cancellingConnection) { + + // Clean up any connections remaining, and reset the UI + [self cancelConnection:self]; + + if (isTestingConnection) { + [self _showConnectionTestResult:NSLocalizedString(@"Connection succeeded", @"Connection success very short status message")]; + } + + return; + } + +#ifndef SP_CODA + [progressIndicatorText setStringValue:NSLocalizedString(@"Connected", @"connection established message")]; + [progressIndicatorText display]; +#endif + + // Stop the current tab's progress indicator + [dbDocument setIsProcessing:NO]; + + // Successful connection! +#ifndef SP_CODA + [connectButton setEnabled:NO]; + [connectButton display]; + [progressIndicator stopAnimation:self]; + [progressIndicatorText setHidden:YES]; +#endif + + // If SSL was enabled, check it was established correctly + if (useSSL && ([self type] == SPTCPIPConnection || [self type] == SPSocketConnection)) { + if (![mySQLConnection isConnectedViaSSL]) { + SPOnewayAlertSheet( + NSLocalizedString(@"SSL connection not established", @"SSL requested but not used title"), + [dbDocument parentWindow], + 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 { +#ifndef SP_CODA + [dbDocument setStatusIconToImageWithName:@"titlebarlock"]; +#endif + } + } + +#ifndef SP_CODA + // Re-enable favorites table view + [favoritesOutlineView setEnabled:YES]; + [(NSView *)favoritesOutlineView display]; +#endif + + // Release the tunnel if set - will now be retained by the connection + if (sshTunnel) SPClear(sshTunnel); + + // Pass the connection to the document and clean up the interface + [self addConnectionToDocument]; +} + +/** + * 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 the user cancelled the password prompt dialog, continue with no further action. + if ([theTunnel passwordPromptCancelled]) { + [self _restoreConnectionInterface]; + + return; + } + + if (newState == SPMySQLProxyIdle) { + +#ifndef SP_CODA + [dbDocument setTitlebarStatus:NSLocalizedString(@"SSH Disconnected", @"SSH disconnected titlebar marker")]; +#endif + + [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"SSH connection failed!", @"SSH connection failed title") errorMessage:[theTunnel lastError] detail:[sshTunnel debugMessages] rawErrorText:[theTunnel lastError]]; + } + else if (newState == SPMySQLProxyConnected) { +#ifndef SP_CODA + [dbDocument setTitlebarStatus:NSLocalizedString(@"SSH Connected", @"SSH connected titlebar marker")]; +#endif + + [self initiateMySQLConnection]; + } + else { +#ifndef SP_CODA + [dbDocument setTitlebarStatus:NSLocalizedString(@"SSH Connecting…", @"SSH connecting titlebar marker")]; +#endif + } +} + +/** + * Add the connection to the parent document and restore the + * interface, allowing the application to run as normal. + */ +- (void)addConnectionToDocument +{ +#ifndef SP_CODA + // Hide the connection view and restore the main view + [connectionView removeFromSuperviewWithoutNeedingDisplay]; + [databaseConnectionView setHidden:NO]; + + // Restore the toolbar icons + NSArray *toolbarItems = [[[dbDocument parentWindow] toolbar] items]; + + for (NSUInteger i = 0; i < [toolbarItems count]; i++) [[toolbarItems objectAtIndex:i] setEnabled:YES]; +#endif + + if (connectionKeychainID) [dbDocument setKeychainID:connectionKeychainID]; + + // Pass the connection to the table document, allowing it to set + // up the other classes and the rest of the interface. + [dbDocument setConnection:mySQLConnection]; +} + +/** + * 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 rawErrorText:(NSString *)rawErrorText +{ + BOOL isSSHTunnelBindError = NO; + +#ifndef SP_CODA + [self _restoreConnectionInterface]; +#endif + + // Release as appropriate + if (sshTunnel) { + [sshTunnel disconnect], SPClear(sshTunnel); + + // 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 ([rawErrorText 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:)]) { + [[(NSObject *)delegate onMainThread] connectionControllerConnectAttemptFailed:self]; + } + + // Only display the connection error message if there is a window visible + if ([[dbDocument parentWindow] isVisible]) { + SPBeginAlertSheet(theTitle, NSLocalizedString(@"OK", @"OK button"), (errorDetail) ? NSLocalizedString(@"Show Detail", @"Show detail button") : nil, (isSSHTunnelBindError) ? NSLocalizedString(@"Use Standard Connection", @"use standard connection button") : nil, [dbDocument 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 +{ + 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:SPLocalhostAddress]; + +#ifndef SP_CODA + // Change to standard TCP/IP connection view + [self resizeTabViewToConnectionType:SPTCPIPConnection animating:YES]; +#endif + + // Initiate the connection after a half second delay to give the connection view a chance to resize + [self performSelector:@selector(initiateConnection:) withObject:self afterDelay:0.5]; + } +} + +#pragma mark - SPConnectionHandlerPrivateAPI + +/** + * Display a connection test error or success message + */ +- (void)_showConnectionTestResult:(NSString *)resultString +{ + if (![NSThread isMainThread]) { + [[self onMainThread] _showConnectionTestResult:resultString]; + } + + [helpButton setHidden:NO]; + [progressIndicator stopAnimation:self]; + [progressIndicatorText setStringValue:resultString]; + [progressIndicatorText setHidden:NO]; +} + +#pragma mark - SPConnectionControllerDelegate + +#pragma mark SplitView delegate methods + +#ifndef SP_CODA + +/** + * When the split view is resized, trigger a resize in the hidden table + * width as well, to keep the connection view and connected view in sync. + */ +- (void)splitViewDidResizeSubviews:(NSNotification *)notification +{ + if (initComplete) { + [databaseConnectionView setPosition:[[[connectionSplitView subviews] objectAtIndex:0] frame].size.width ofDividerAtIndex:0]; + } +} + +- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)dividerIndex +{ + return 145.f; +} + +#endif + +#pragma mark - +#pragma mark Outline view delegate methods + +#ifndef SP_CODA + +- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item +{ + return ([[(SPTreeNode *)item parentNode] parentNode] == nil); +} + +- (void)outlineViewSelectionIsChanging:(NSNotification *)notification +{ + if (isEditingConnection) { + [self _stopEditingConnection]; + + [[notification object] setNeedsDisplay:YES]; + } +} + +- (void)outlineViewSelectionDidChange:(NSNotification *)notification +{ + NSInteger selected = [favoritesOutlineView numberOfSelectedRows]; + + if (isEditingConnection) { + [self _stopEditingConnection]; + [[notification object] setNeedsDisplay:YES]; + } + + if (selected == 1) { + [self updateFavoriteSelection:self]; + + favoriteNameFieldWasAutogenerated = NO; + [connectionResizeContainer setHidden:NO]; + [connectionInstructionsTextField setStringValue:NSLocalizedString(@"Enter connection details below, or choose a favorite", @"enter connection details label")]; + } + else if (selected > 1) { + [connectionResizeContainer setHidden:YES]; + [connectionInstructionsTextField setStringValue:NSLocalizedString(@"Please choose a favorite", @"please choose a favorite connection view label")]; + } +} + +- (NSCell *)outlineView:(NSOutlineView *)outlineView dataCellForTableColumn:(NSTableColumn *)tableColumn item:(id)item +{ + if (item == quickConnectItem) { + return (NSCell *)quickConnectCell; + } + + return [tableColumn dataCellForRow:[outlineView rowForItem:item]]; +} + +- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item +{ + SPTreeNode *node = (SPTreeNode *)item; + SPFavoriteTextFieldCell *favoriteCell = (SPFavoriteTextFieldCell *)cell; + + // Draw entries with the small system font by default + [cell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; + + // Set an image as appropriate; the quick connect image for that entry, no image for other + // top-level items, the folder image for group nodes, or the database image for other nodes. + if (![[node parentNode] parentNode]) { + if (node == quickConnectItem) { + if ([outlineView rowForItem:item] == [outlineView selectedRow]) { + [favoriteCell setImage:[NSImage imageNamed:SPQuickConnectImageWhite]]; + } + else { + [favoriteCell setImage:[NSImage imageNamed:SPQuickConnectImage]]; + } + } + else { + [favoriteCell setImage:nil]; + } + [favoriteCell setLabelColor:nil]; + } + else { + if ([node isGroup]) { + [favoriteCell setImage:folderImage]; + [favoriteCell setLabelColor:nil]; + } + else { + [favoriteCell setImage:[NSImage imageNamed:SPDatabaseImage]]; + NSColor *bgColor = nil; + NSNumber *colorIndexObj = [[[node representedObject] nodeFavorite] objectForKey:SPFavoriteColorIndexKey]; + if(colorIndexObj != nil) { + bgColor = [[SPFavoriteColorSupport sharedInstance] colorForIndex:[colorIndexObj integerValue]]; + } + [favoriteCell setLabelColor:bgColor]; + } + } + + // If a favourite item is being edited, draw the text in bold to show state + if (isEditingConnection && ![node isGroup] && [outlineView rowForItem:item] == [outlineView selectedRow]) { + NSMutableAttributedString *editedCellString = [[cell attributedStringValue] mutableCopy]; + [editedCellString addAttribute:NSForegroundColorAttributeName value:[NSColor colorWithDeviceWhite:0.25f alpha:1.f] range:NSMakeRange(0, [editedCellString length])]; + [cell setAttributedStringValue:editedCellString]; + [editedCellString release]; + } +} + +- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item +{ + if (item == quickConnectItem) { + return 24.f; + } + + return ([[item parentNode] parentNode]) ? 17.f : 22.f; +} + +- (NSString *)outlineView:(NSOutlineView *)outlineView toolTipForCell:(NSCell *)cell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tableColumn item:(id)item mouseLocation:(NSPoint)mouseLocation +{ + NSString *toolTip = nil; + + SPTreeNode *node = (SPTreeNode *)item; + + if (![node isGroup]) { + + NSString *favoriteName = [[[node representedObject] nodeFavorite] objectForKey:SPFavoriteNameKey]; + NSString *favoriteHostname = [[[node representedObject] nodeFavorite] objectForKey:SPFavoriteHostKey]; + + toolTip = ([favoriteHostname length]) ? [NSString stringWithFormat:@"%@ (%@)", favoriteName, favoriteHostname] : favoriteName; + } + + // Only display a tooltip for group nodes that are a descendant of the root node + else if ([[node parentNode] parentNode]) { + + NSUInteger favCount = 0; + NSUInteger groupCount = 0; + + for (SPTreeNode *eachNode in [node childNodes]) + { + if ([eachNode isGroup]) { + groupCount++; + } + else { + favCount++; + } + } + + NSMutableArray *tooltipParts = [NSMutableArray arrayWithCapacity:2]; + + if (favCount || !groupCount) { + [tooltipParts addObject:[NSString stringWithFormat:((favCount == 1) ? NSLocalizedString(@"%d favorite", @"favorite singular label (%d == 1)") : NSLocalizedString(@"%d favorites", @"favorites plural label (%d != 1)")), favCount]]; + } + + if (groupCount) { + [tooltipParts addObject:[NSString stringWithFormat:((groupCount == 1) ? NSLocalizedString(@"%d group", @"favorite group singular label (%d == 1)") : NSLocalizedString(@"%d groups", @"favorite groups plural label (%d != 1)")), groupCount]]; + } + + toolTip = [NSString stringWithFormat:@"%@ - %@", [[node representedObject] nodeName], [tooltipParts componentsJoinedByString:@", "]]; + } + + return toolTip; +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item +{ + // If this is a top level item, only allow the "Quick Connect" item to be selectable + if (![[item parentNode] parentNode]) { + return item == quickConnectItem; + } + + // Otherwise allow all items to be selectable + return YES; +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item +{ + return (item != quickConnectItem && ![item isLeaf]); +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView shouldShowOutlineCellForItem:(id)item +{ + return ([[item parentNode] parentNode] != nil); +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView shouldCollapseItem:(id)item +{ + return ([[item parentNode] parentNode] != nil); +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item +{ + NSEvent *event = [NSApp currentEvent]; + BOOL shiftTabbedIn = ([event type] == NSKeyDown && [[event characters] length] && [[event characters] characterAtIndex:0] == NSBackTabCharacter); + + if (shiftTabbedIn && [(SPFavoritesOutlineView *)outlineView justGainedFocus]) { + return NO; + } + + return item != quickConnectItem; +} + +- (void)outlineViewItemDidCollapse:(NSNotification *)notification +{ + [self _setNodeIsExpanded:NO fromNotification:notification]; +} + +- (void)outlineViewItemDidExpand:(NSNotification *)notification +{ + [self _setNodeIsExpanded:YES fromNotification:notification]; +} + +#endif + +#pragma mark - +#pragma mark Outline view drag & drop + +#ifndef SP_CODA + +- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard +{ + // Prevent a drag which includes the outline title group from taking place + for (id item in items) + { + if (![[item parentNode] parentNode]) return NO; + } + + // If the user is in the process of changing a node's name, trigger a save and prevent dragging. + if (isEditingItemName) { + [favoritesController saveFavorites]; + + [self _reloadFavoritesViewData]; + + isEditingItemName = NO; + + return NO; + } + + [pboard declareTypes:@[SPFavoritesPasteboardDragType] owner:self]; + + BOOL result = [pboard setData:[NSData data] forType:SPFavoritesPasteboardDragType]; + + draggedNodes = items; + + return result; +} + +- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)childIndex +{ + NSDragOperation result = NSDragOperationNone; + + // Prevent the top level or the quick connect item from being a target + if (!item || item == quickConnectItem) return result; + + // Prevent dropping favorites on other favorites (non-groups) + if ((childIndex == NSOutlineViewDropOnItemIndex) && (![item isGroup])) return result; + + // Ensure that none of the dragged nodes are being dragged into children of themselves; if they are, + // prevent the drag. + id itemToCheck = item; + + do { + if ([draggedNodes containsObject:itemToCheck]) { + return result; + } + } + while ((itemToCheck = [itemToCheck parentNode])); + + if ([info draggingSource] == outlineView) { + [outlineView setDropItem:item dropChildIndex:childIndex]; + + result = NSDragOperationMove; + } + + return result; +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)childIndex +{ + BOOL acceptedDrop = NO; + + if ((!item) || ([info draggingSource] != outlineView)) return acceptedDrop; + + SPTreeNode *node = item ? item : [[[[favoritesRoot childNodes] objectAtIndex:0] childNodes] objectAtIndex:0]; + + // Cache the selected nodes for selection restoration afterwards + NSArray *preDragSelection = [self selectedFavoriteNodes]; + + // Disable all automatic sorting + currentSortItem = -1; + reverseFavoritesSort = NO; + + [prefs setInteger:currentSortItem forKey:SPFavoritesSortedBy]; + [prefs setBool:NO forKey:SPFavoritesSortedInReverse]; + + // Uncheck sort by menu items + for (NSMenuItem *menuItem in [[favoritesSortByMenuItem submenu] itemArray]) + { + [menuItem setState:NSOffState]; + } + + if (![draggedNodes count]) return acceptedDrop; + + if ([node isGroup]) { + if (childIndex == NSOutlineViewDropOnItemIndex) { + childIndex = 0; + } + [outlineView expandItem:node]; + } + else { + if (childIndex == NSOutlineViewDropOnItemIndex) { + childIndex = 0; + } + } + + if (![[node representedObject] nodeName]) { + node = [[favoritesRoot childNodes] objectAtIndex:0]; + } + + NSMutableArray *childNodeArray = [node mutableChildNodes]; + + for (SPTreeNode *treeNode in draggedNodes) + { + // Remove the node from its old location + NSInteger oldIndex = [childNodeArray indexOfObject:treeNode]; + NSInteger newIndex = childIndex; + + if (oldIndex != NSNotFound) { + + [childNodeArray removeObjectAtIndex:oldIndex]; + + if (childIndex > oldIndex) { + newIndex--; + } + } + else { + [[[treeNode parentNode] mutableChildNodes] removeObject:treeNode]; + } + + [childNodeArray insertObject:treeNode atIndex:newIndex]; + + newIndex++; + } + + [favoritesController saveFavorites]; + + [self _reloadFavoritesViewData]; + + [[NSNotificationCenter defaultCenter] postNotificationName:SPConnectionFavoritesChangedNotification object:self]; + + [[[SPAppDelegate preferenceController] generalPreferencePane] updateDefaultFavoritePopup]; + + // Update the selection to account for rearranged faourites + NSMutableIndexSet *restoredSelection = [NSMutableIndexSet indexSet]; + + for (SPTreeNode *eachNode in preDragSelection) + { + [restoredSelection addIndex:[favoritesOutlineView rowForItem:eachNode]]; + } + + [favoritesOutlineView selectRowIndexes:restoredSelection byExtendingSelection:NO]; + + acceptedDrop = YES; + + return acceptedDrop; +} + +#endif + +#pragma mark - +#pragma mark Textfield delegate methods + +#ifndef SP_CODA + +/** + * React to control text changes in the connection interface + */ +- (void)controlTextDidChange:(NSNotification *)notification +{ + id field = [notification object]; + + // Ignore changes in the outline view edit fields + if ([field isKindOfClass:[NSOutlineView class]]) { + return; + } + + // If a 'name' field was edited, and is now of zero length, trigger a replacement + // with a standard suggestion + if (((field == standardNameField) || (field == socketNameField) || (field == sshNameField)) && [self selectedFavoriteNode]) { + if (![[self _stripInvalidCharactersFromString:[field stringValue]] length]) { + [self controlTextDidEndEditing:notification]; + } + } + + [self _startEditingConnection]; + + if (favoriteNameFieldWasAutogenerated && (field != standardNameField && field != socketNameField && field != sshNameField)) { + [self setName:[self _generateNameForConnection]]; + } +} + +/** + * React to the end of control text changes in the connection interface. + */ +- (void)controlTextDidEndEditing:(NSNotification *)notification +{ + id field = [notification object]; + + // Handle updates to the 'name' field of the selected favourite. The favourite name should + // have leading or trailing spaces removed at the end of editing, and if it's left empty, + // should have a default name set. + if (((field == standardNameField) || (field == socketNameField) || (field == sshNameField)) && [self selectedFavoriteNode]) { + + NSString *favoriteName = [self _stripInvalidCharactersFromString:[field stringValue]]; + + if (![favoriteName length]) { + favoriteName = [self _generateNameForConnection]; + + if (favoriteName) { + [self setName:favoriteName]; + } + + // Enable user@host update in reaction to other UI changes + favoriteNameFieldWasAutogenerated = YES; + } + else if (![[field stringValue] isEqualToString:[self _generateNameForConnection]]) { + favoriteNameFieldWasAutogenerated = NO; + [self setName:favoriteName]; + } + } + + // When a host field finishes editing, ensure that it hasn't been set to "localhost" to + // ensure that socket connections don't inadvertently occur. + if (field == standardSQLHostField || field == sshSQLHostField) { + [self _checkHost]; + } +} + +#endif + +#pragma mark - +#pragma mark Tab bar delegate methods + +#ifndef SP_CODA + +/** + * 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]; + + if (selectedTabView == previousType) return; + + [self _startEditingConnection]; + + [self resizeTabViewToConnectionType:selectedTabView animating:YES]; + + // Update the host as appropriate + if ((selectedTabView != SPSocketConnection) && [[self host] isEqualToString:@"localhost"]) { + [self setHost:@""]; + } + + previousType = selectedTabView; + + [self _favoriteTypeDidChange]; +} + +#endif + +#pragma mark - +#pragma mark Color Selector delegate + +- (void)colorSelectorDidChange:(SPColorSelectorView *)sel +{ + [self _startEditingConnection]; +} + +#pragma mark - +#pragma mark Scroll view notifications + +#ifndef SP_CODA + +/** + * 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]; + } +} + +#endif + +#pragma mark - +#pragma mark Menu Validation + +#ifndef SP_CODA + +/** + * Menu item validation. + */ +- (BOOL)validateMenuItem:(NSMenuItem *)menuItem +{ + SEL action = [menuItem action]; + + SPTreeNode *node = [self selectedFavoriteNode]; + NSInteger selectedRows = [favoritesOutlineView numberOfSelectedRows]; + + if ((action == @selector(sortFavorites:)) || (action == @selector(reverseSortFavorites:))) { + + if ([[favoritesRoot allChildLeafs] count] < 2) return NO; + + // 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)]; + } + + // Check or uncheck the reverse sort item + if (action == @selector(reverseSortFavorites:)) { + [menuItem setState:reverseFavoritesSort]; + } + + return YES; + } + + // import does not depend on a selection + if(action == @selector(importFavorites:)) return YES; + + if (node == quickConnectItem) return NO; + + // Remove/rename the selected node + if (action == @selector(removeNode:) || action == @selector(renameNode:)) { + return selectedRows == 1; + } + + // Duplicate and make the selected favorite the default + if (action == @selector(duplicateFavorite:)) { + return ((selectedRows == 1) && (![node isGroup])); + } + + // Make selected favorite the default + if (action == @selector(makeSelectedFavoriteDefault:)) { + NSInteger favoriteID = [[[self selectedFavorite] objectForKey:SPFavoriteIDKey] integerValue]; + + return ((selectedRows == 1) && (![node isGroup]) && (favoriteID != [prefs integerForKey:SPDefaultFavorite])); + } + + // Favorites export + if (action == @selector(exportFavorites:)) { + + if ([[favoritesRoot allChildLeafs] count] == 0 || selectedRows == 0) { + return NO; + } + else if (selectedRows > 1) { + [menuItem setTitle:NSLocalizedString(@"Export Selected...", @"export selected favorites menu item")]; + } + } + + return YES; +} + +#endif + +#pragma mark - +#pragma mark Favorites import/export delegate methods + +#ifndef SP_CODA + +/** + * Called by the favorites importer when the imported data is available. + */ +- (void)favoritesImportData:(NSArray *)data +{ + SPTreeNode *newNode; + NSMutableArray *importedNodes = [NSMutableArray array]; + NSMutableIndexSet *importedIndexSet = [NSMutableIndexSet indexSet]; + + // Add each of the imported favorites to the root node + for (NSMutableDictionary *favorite in data) + { + newNode = [favoritesController addFavoriteNodeWithData:favorite asChildOfNode:nil]; + [importedNodes addObject:newNode]; + } + + if (currentSortItem > SPFavoritesSortUnsorted) { + [self _sortFavorites]; + } + + [self _reloadFavoritesViewData]; + + // Select the new nodes and scroll into view + for (SPTreeNode *eachNode in importedNodes) + { + [importedIndexSet addIndex:[favoritesOutlineView rowForItem:eachNode]]; + } + + [favoritesOutlineView selectRowIndexes:importedIndexSet byExtendingSelection:NO]; + + [self _scrollToSelectedNode]; +} + +/** + * Called by the favorites importer when the import completes. + */ +- (void)favoritesImportCompletedWithError:(NSError *)error +{ + if (error) { + NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Favorites import error", @"favorites import error message") + defaultButton:NSLocalizedString(@"OK", @"OK") + alternateButton:nil + otherButton:nil + informativeTextWithFormat:NSLocalizedString(@"The following error occurred during the import process:\n\n%@", @"favorites import error informative message"), [error localizedDescription]]; + + [alert beginSheetModalForWindow:[dbDocument parentWindow] + modalDelegate:self + didEndSelector:NULL + contextInfo:NULL]; + } +} + +#endif + +#pragma mark - +#pragma mark Private API + +#ifndef SP_CODA + +/** + * Sets the expanded state of the node from the supplied outline view notification. + * + * @param expanded The state of the node + * @param notification The notification genrated from the state change + */ +- (void)_setNodeIsExpanded:(BOOL)expanded fromNotification:(NSNotification *)notification +{ + SPGroupNode *node = [[[notification userInfo] valueForKey:@"NSObject"] representedObject]; + + [node setNodeIsExpanded:expanded]; +} + +#endif + +#pragma mark - SPConnectionControllerInitializer + +/** + * 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; + +#ifndef SP_CODA + databaseConnectionSuperview = [dbDocument databaseView]; + databaseConnectionView = [dbDocument valueForKey:@"contentViewSplitter"]; +#endif + + // Keychain references + connectionKeychainID = nil; + connectionKeychainItemName = nil; + connectionKeychainItemAccount = nil; + connectionSSHKeychainItemName = nil; + connectionSSHKeychainItemAccount = nil; + + initComplete = NO; + isEditingItemName = NO; + isConnecting = NO; + isTestingConnection = NO; + sshTunnel = nil; + mySQLConnection = nil; + cancellingConnection = NO; + favoriteNameFieldWasAutogenerated = NO; + + [self loadNib]; + + NSArray *colorList = [[SPFavoriteColorSupport sharedInstance] userColorList]; + [sshColorField setColorList:colorList]; + [sshColorField bind:@"selectedTag" toObject:self withKeyPath:@"colorIndex" options:nil]; + [standardColorField setColorList:colorList]; + [standardColorField bind:@"selectedTag" toObject:self withKeyPath:@"colorIndex" options:nil]; + [socketColorField setColorList:colorList]; + [socketColorField bind:@"selectedTag" toObject:self withKeyPath:@"colorIndex" options:nil]; + + [self registerForNotifications]; + +#ifndef SP_CODA + // Hide the main view and position and display the connection view + [databaseConnectionView setHidden:YES]; + [connectionView setFrame:[databaseConnectionView frame]]; + [databaseConnectionSuperview addSubview:connectionView]; + + // Set up the splitview + [connectionSplitView setMinSize:80.f ofSubviewAtIndex:0]; + [connectionSplitView setMinSize:445.f ofSubviewAtIndex:1]; + + // 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 to be constructed. + favoritesController = [SPFavoritesController sharedFavoritesController]; + + // Tree references + favoritesRoot = [favoritesController favoritesTree]; + currentFavorite = nil; + + // Create the "Quick Connect" placeholder group + quickConnectItem = [[SPTreeNode treeNodeWithRepresentedObject:[SPGroupNode groupNodeWithName:[NSLocalizedString(@"Quick Connect", @"Quick connect item label") uppercaseString]]] retain]; + [quickConnectItem setIsGroup:YES]; + + // Create a NSOutlineView cell for the Quick Connect group + quickConnectCell = [[SPFavoriteTextFieldCell alloc] init]; + [quickConnectCell setDrawsDividerUnderCell:YES]; + [quickConnectCell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; + + // Update the UI + [self _reloadFavoritesViewData]; + [self setUpFavoritesOutlineView]; + [self _restoreOutlineViewStateNode:favoritesRoot]; + + // Set up the selected favourite, and scroll after a small delay to fix animation delay on Lion + [self setUpSelectedConnectionFavorite]; + if ([favoritesOutlineView selectedRow] != -1) { + [self performSelector:@selector(_scrollToSelectedNode) withObject:nil afterDelay:0.0]; + } + + // Set sort items + currentSortItem = (SPFavoritesSortItem)[prefs integerForKey:SPFavoritesSortedBy]; + reverseFavoritesSort = [prefs boolForKey:SPFavoritesSortedInReverse]; +#endif + + initComplete = YES; + } + + return self; +} + +/** + * Loads the connection controllers UI nib. + */ +- (void)loadNib +{ +#ifndef SP_CODA + + // 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:SPConnectionViewNibName bundle:[NSBundle mainBundle]]; + + [nibLoader instantiateNibWithOwner:self topLevelObjects:&connectionViewTopLevelObjects]; + [nibObjectsToRelease addObjectsFromArray:connectionViewTopLevelObjects]; + [nibLoader release]; + +#endif +} + +/** + * Registers for various notifications. + */ +- (void)registerForNotifications +{ + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(_documentWillClose:) + name:SPDocumentWillCloseNotification + object:dbDocument]; + +#ifndef SP_CODA + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(scrollViewFrameChanged:) + name:NSViewFrameDidChangeNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(_processFavoritesDataChange:) + name:SPConnectionFavoritesChangedNotification + object:nil]; + + // Registered to be notified of changes to connection information + [self addObserver:self + forKeyPath:SPFavoriteTypeKey + options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) + context:NULL]; + + [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:SPFavoriteColorIndexKey + 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]; +#endif +} + +/** + * Performs any set up necessary for the favorities outline view. + */ +- (void)setUpFavoritesOutlineView +{ + // 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:@[SPFavoritesPasteboardDragType]]; + [favoritesOutlineView setDraggingSourceOperationMask:NSDragOperationMove forLocal:YES]; +} + +/** + * Sets up the selected connection favorite according to the user's preferences. + */ +- (void)setUpSelectedConnectionFavorite +{ +#ifndef SP_CODA + SPTreeNode *favorite = [self _favoriteNodeForFavoriteID:[prefs integerForKey:[prefs boolForKey:SPSelectLastFavoriteUsed] ? SPLastFavoriteID : SPDefaultFavorite]]; + + if (favorite) { + + if (favorite == quickConnectItem) { + [self _selectNode:favorite]; + } + else { + 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]; + } + + [self _scrollToSelectedNode]; + } + else { + previousType = SPTCPIPConnection; + + [self resizeTabViewToConnectionType:SPTCPIPConnection animating:NO]; + } +#endif +} + +#pragma mark - +#pragma mark Private API + +/** + * Responds to notifications that the favorites root has changed, + * and updates the interface to match. + */ +- (void)_processFavoritesDataChange:(NSNotification *)aNotification +{ +#ifndef SP_CODA + // Check the supplied notification for the sender; if the sender + // was this object, ignore it + if ([aNotification object] == self) return; + + NSArray *selectedFavoriteNodes = [self selectedFavoriteNodes]; + + [self _reloadFavoritesViewData]; + + NSMutableIndexSet *selectionIndexes = [NSMutableIndexSet indexSet]; + + for (SPTreeNode *eachNode in selectedFavoriteNodes) + { + NSInteger anIndex = [favoritesOutlineView rowForItem:eachNode]; + + if (anIndex == -1) continue; + + [selectionIndexes addIndex:anIndex]; + } + + [favoritesOutlineView selectRowIndexes:selectionIndexes byExtendingSelection:NO]; +#endif +} + +/** + * Restores the outline views group nodes expansion state. + * + * @param node The node to traverse + */ +#ifndef SP_CODA +- (void)_restoreOutlineViewStateNode:(SPTreeNode *)node +{ + if ([node isGroup]) { + if ([[node representedObject] nodeIsExpanded]) { + [favoritesOutlineView expandItem:node]; + } + else { + [favoritesOutlineView collapseItem:node]; + } + + for (SPTreeNode *childNode in [node childNodes]) + { + if ([childNode isGroup]) { + [self _restoreOutlineViewStateNode:childNode]; + } + } + } +} +#endif + +#pragma mark - SPConnectionControllerDataSource + +#ifndef SP_CODA + +/** + * Return the number of children for the specified item in the favourites tree. + * Note that to support the "Quick Connect" entry, the returned count is amended + * for the top level. + */ +- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item +{ + SPTreeNode *node = (item == nil ? favoritesRoot : (SPTreeNode *)item); + + // If at the root, return the count plus one for the "Quick Connect" entry + if (!item) { + return [[node childNodes] count] + 1; + } + + return [[node childNodes] count]; +} + +/** + * Return the branch at the specified index of a supplied tree level. + * Note that to support the "Quick Connect" entry, children of the top level + * have their offsets amended. + */ +- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)childIndex ofItem:(id)item +{ + // For the top level of the tree, return the "Quick Connect" child for position zero; + // amend all other positions to compensate for the faked position. + if (!item) { + if (childIndex == 0) { + return quickConnectItem; + } + + childIndex--; + } + + SPTreeNode *node = (item == nil ? favoritesRoot : (SPTreeNode *)item); + + return NSArrayObjectAtIndex([node childNodes], childIndex); +} + +- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item +{ + SPTreeNode *node = (SPTreeNode *)item; + + return (![node isGroup]) ? [[[node representedObject] nodeFavorite] objectForKey:SPFavoriteNameKey] : [[node representedObject] nodeName]; +} + +- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item +{ + NSString *newName = [object stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + + if ([newName length]) { + + // Get the node that was renamed + SPTreeNode *node = [self selectedFavoriteNode]; + + if (![node isGroup]) { + + // Updating the name triggers a KVO update + [self setName:newName]; + [self _saveCurrentDetailsCreatingNewFavorite:NO validateDetails:NO]; + } + else { + [[node representedObject] setNodeName:newName]; + + [favoritesController saveFavorites]; + + [self _reloadFavoritesViewData]; + } + } +} + +#endif + #pragma mark - - (void)dealloc diff --git a/Source/SPConnectionControllerDataSource.h b/Source/SPConnectionControllerDataSource.h deleted file mode 100644 index 1543b6ca..00000000 --- a/Source/SPConnectionControllerDataSource.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// SPConnectionControllerDataSource.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on February 20, 2011. -// Copyright (c) 2011 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPConnectionController.h" - -/** - * @category SPConnectionControllerDelegate SPConnectionControllerDelegate.h - * - * @author Stuart Connolly http://stuconnolly.com/ - * - * Connection controller data source category. - */ -@interface SPConnectionController (SPConnectionControllerDataSource) - -@end diff --git a/Source/SPConnectionControllerDataSource.m b/Source/SPConnectionControllerDataSource.m deleted file mode 100644 index 2b83938c..00000000 --- a/Source/SPConnectionControllerDataSource.m +++ /dev/null @@ -1,121 +0,0 @@ -// -// SPConnectionControllerDataSource.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on February 20, 2011. -// Copyright (c) 2011 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPConnectionControllerDataSource.h" -#import "SPFavoritesController.h" -#import "SPFavoriteNode.h" -#import "SPGroupNode.h" -#import "SPTreeNode.h" - -@interface SPConnectionController () - -- (void)_reloadFavoritesViewData; -- (void)_saveCurrentDetailsCreatingNewFavorite:(BOOL)createNewFavorite validateDetails:(BOOL)validateDetails; - -@end - -@implementation SPConnectionController (SPConnectionControllerDataSource) - -#ifndef SP_CODA - -/** - * Return the number of children for the specified item in the favourites tree. - * Note that to support the "Quick Connect" entry, the returned count is amended - * for the top level. - */ -- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item -{ - SPTreeNode *node = (item == nil ? favoritesRoot : (SPTreeNode *)item); - - // If at the root, return the count plus one for the "Quick Connect" entry - if (!item) { - return [[node childNodes] count] + 1; - } - - return [[node childNodes] count]; -} - -/** - * Return the branch at the specified index of a supplied tree level. - * Note that to support the "Quick Connect" entry, children of the top level - * have their offsets amended. - */ -- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)childIndex ofItem:(id)item -{ - // For the top level of the tree, return the "Quick Connect" child for position zero; - // amend all other positions to compensate for the faked position. - if (!item) { - if (childIndex == 0) { - return quickConnectItem; - } - - childIndex--; - } - - SPTreeNode *node = (item == nil ? favoritesRoot : (SPTreeNode *)item); - - return NSArrayObjectAtIndex([node childNodes], childIndex); -} - -- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item -{ - SPTreeNode *node = (SPTreeNode *)item; - - return (![node isGroup]) ? [[[node representedObject] nodeFavorite] objectForKey:SPFavoriteNameKey] : [[node representedObject] nodeName]; -} - -- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item -{ - NSString *newName = [object stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - - if ([newName length]) { - - // Get the node that was renamed - SPTreeNode *node = [self selectedFavoriteNode]; - - if (![node isGroup]) { - - // Updating the name triggers a KVO update - [self setName:newName]; - [self _saveCurrentDetailsCreatingNewFavorite:NO validateDetails:NO]; - } - else { - [[node representedObject] setNodeName:newName]; - - [favoritesController saveFavorites]; - - [self _reloadFavoritesViewData]; - } - } -} - -#endif - -@end diff --git a/Source/SPConnectionControllerDelegate.h b/Source/SPConnectionControllerDelegate.h deleted file mode 100644 index ad46b662..00000000 --- a/Source/SPConnectionControllerDelegate.h +++ /dev/null @@ -1,47 +0,0 @@ -// -// SPConnectionControllerDelegate.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on October 29, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPConnectionController.h" -#import "SPFavoritesExportProtocol.h" -#import "SPFavoritesImportProtocol.h" - -/** - * @category SPConnectionControllerDelegate SPConnectionControllerDelegate.h - * - * @author Stuart Connolly http://stuconnolly.com/ - * - * Connection controller delegate category. - */ -@interface SPConnectionController (SPConnectionControllerDelegate) -#ifndef SP_CODA - <SPFavoritesImportProtocol, SPFavoritesExportProtocol> -#endif - -@end diff --git a/Source/SPConnectionControllerDelegate.m b/Source/SPConnectionControllerDelegate.m deleted file mode 100644 index 4ec0148a..00000000 --- a/Source/SPConnectionControllerDelegate.m +++ /dev/null @@ -1,756 +0,0 @@ -// -// SPConnectionControllerDelegate.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on October 29, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPConnectionControllerDelegate.h" -#ifndef SP_CODA -#import "SPFavoritesController.h" -#import "SPTableTextFieldCell.h" -#import "SPFavoriteTextFieldCell.h" -#import "SPPreferenceController.h" -#import "SPGeneralPreferencePane.h" -#import "SPAppController.h" -#import "SPFavoriteNode.h" -#import "SPGroupNode.h" -#import "SPTreeNode.h" -#import "SPFavoritesOutlineView.h" -#import "SPFavoriteColorSupport.h" -#endif - -#ifndef SP_CODA -static NSString *SPDatabaseImage = @"database-small"; -static NSString *SPQuickConnectImage = @"quick-connect-icon.pdf"; -static NSString *SPQuickConnectImageWhite = @"quick-connect-icon-white.pdf"; -#endif - -@interface SPConnectionController (SPConnectionControllerDelegate_Private_API) - -// Privately redeclare as read/write to get the synthesized setter -@property (readwrite, assign) BOOL isEditingConnection; - -- (void)_checkHost; -- (void)_sortFavorites; -- (void)_favoriteTypeDidChange; -- (void)_reloadFavoritesViewData; -- (void)_scrollToSelectedNode; - -- (NSString *)_stripInvalidCharactersFromString:(NSString *)subject; - -- (void)_startEditingConnection; -- (void)_stopEditingConnection; -- (void)_setNodeIsExpanded:(BOOL)expanded fromNotification:(NSNotification *)notification; - -- (NSString *)_generateNameForConnection; - -@end - -@implementation SPConnectionController (SPConnectionControllerDelegate) - -#pragma mark - -#pragma mark SplitView delegate methods - -#ifndef SP_CODA - -/** - * When the split view is resized, trigger a resize in the hidden table - * width as well, to keep the connection view and connected view in sync. - */ -- (void)splitViewDidResizeSubviews:(NSNotification *)notification -{ - if (initComplete) { - [databaseConnectionView setPosition:[[[connectionSplitView subviews] objectAtIndex:0] frame].size.width ofDividerAtIndex:0]; - } -} - -- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)dividerIndex -{ - return 145.f; -} - -#endif - -#pragma mark - -#pragma mark Outline view delegate methods - -#ifndef SP_CODA - -- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item -{ - return ([[(SPTreeNode *)item parentNode] parentNode] == nil); -} - -- (void)outlineViewSelectionIsChanging:(NSNotification *)notification -{ - if (isEditingConnection) { - [self _stopEditingConnection]; - - [[notification object] setNeedsDisplay:YES]; - } -} - -- (void)outlineViewSelectionDidChange:(NSNotification *)notification - { - NSInteger selected = [favoritesOutlineView numberOfSelectedRows]; - - if (isEditingConnection) { - [self _stopEditingConnection]; - [[notification object] setNeedsDisplay:YES]; - } - - if (selected == 1) { - [self updateFavoriteSelection:self]; - - favoriteNameFieldWasAutogenerated = NO; - [connectionResizeContainer setHidden:NO]; - [connectionInstructionsTextField setStringValue:NSLocalizedString(@"Enter connection details below, or choose a favorite", @"enter connection details label")]; - } - else if (selected > 1) { - [connectionResizeContainer setHidden:YES]; - [connectionInstructionsTextField setStringValue:NSLocalizedString(@"Please choose a favorite", @"please choose a favorite connection view label")]; - } -} - -- (NSCell *)outlineView:(NSOutlineView *)outlineView dataCellForTableColumn:(NSTableColumn *)tableColumn item:(id)item -{ - if (item == quickConnectItem) { - return (NSCell *)quickConnectCell; - } - - return [tableColumn dataCellForRow:[outlineView rowForItem:item]]; -} - -- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item -{ - SPTreeNode *node = (SPTreeNode *)item; - SPFavoriteTextFieldCell *favoriteCell = (SPFavoriteTextFieldCell *)cell; - - // Draw entries with the small system font by default - [cell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; - - // Set an image as appropriate; the quick connect image for that entry, no image for other - // top-level items, the folder image for group nodes, or the database image for other nodes. - if (![[node parentNode] parentNode]) { - if (node == quickConnectItem) { - if ([outlineView rowForItem:item] == [outlineView selectedRow]) { - [favoriteCell setImage:[NSImage imageNamed:SPQuickConnectImageWhite]]; - } - else { - [favoriteCell setImage:[NSImage imageNamed:SPQuickConnectImage]]; - } - } - else { - [favoriteCell setImage:nil]; - } - [favoriteCell setLabelColor:nil]; - } - else { - if ([node isGroup]) { - [favoriteCell setImage:folderImage]; - [favoriteCell setLabelColor:nil]; - } - else { - [favoriteCell setImage:[NSImage imageNamed:SPDatabaseImage]]; - NSColor *bgColor = nil; - NSNumber *colorIndexObj = [[[node representedObject] nodeFavorite] objectForKey:SPFavoriteColorIndexKey]; - if(colorIndexObj != nil) { - bgColor = [[SPFavoriteColorSupport sharedInstance] colorForIndex:[colorIndexObj integerValue]]; - } - [favoriteCell setLabelColor:bgColor]; - } - } - - // If a favourite item is being edited, draw the text in bold to show state - if (isEditingConnection && ![node isGroup] && [outlineView rowForItem:item] == [outlineView selectedRow]) { - NSMutableAttributedString *editedCellString = [[cell attributedStringValue] mutableCopy]; - [editedCellString addAttribute:NSForegroundColorAttributeName value:[NSColor colorWithDeviceWhite:0.25f alpha:1.f] range:NSMakeRange(0, [editedCellString length])]; - [cell setAttributedStringValue:editedCellString]; - [editedCellString release]; - } -} - -- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item -{ - if (item == quickConnectItem) { - return 24.f; - } - - return ([[item parentNode] parentNode]) ? 17.f : 22.f; -} - -- (NSString *)outlineView:(NSOutlineView *)outlineView toolTipForCell:(NSCell *)cell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tableColumn item:(id)item mouseLocation:(NSPoint)mouseLocation -{ - NSString *toolTip = nil; - - SPTreeNode *node = (SPTreeNode *)item; - - if (![node isGroup]) { - - NSString *favoriteName = [[[node representedObject] nodeFavorite] objectForKey:SPFavoriteNameKey]; - NSString *favoriteHostname = [[[node representedObject] nodeFavorite] objectForKey:SPFavoriteHostKey]; - - toolTip = ([favoriteHostname length]) ? [NSString stringWithFormat:@"%@ (%@)", favoriteName, favoriteHostname] : favoriteName; - } - - // Only display a tooltip for group nodes that are a descendant of the root node - else if ([[node parentNode] parentNode]) { - - NSUInteger favCount = 0; - NSUInteger groupCount = 0; - - for (SPTreeNode *eachNode in [node childNodes]) - { - if ([eachNode isGroup]) { - groupCount++; - } - else { - favCount++; - } - } - - NSMutableArray *tooltipParts = [NSMutableArray arrayWithCapacity:2]; - - if (favCount || !groupCount) { - [tooltipParts addObject:[NSString stringWithFormat:((favCount == 1) ? NSLocalizedString(@"%d favorite", @"favorite singular label (%d == 1)") : NSLocalizedString(@"%d favorites", @"favorites plural label (%d != 1)")), favCount]]; - } - - if (groupCount) { - [tooltipParts addObject:[NSString stringWithFormat:((groupCount == 1) ? NSLocalizedString(@"%d group", @"favorite group singular label (%d == 1)") : NSLocalizedString(@"%d groups", @"favorite groups plural label (%d != 1)")), groupCount]]; - } - - toolTip = [NSString stringWithFormat:@"%@ - %@", [[node representedObject] nodeName], [tooltipParts componentsJoinedByString:@", "]]; - } - - return toolTip; -} - -- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item -{ - // If this is a top level item, only allow the "Quick Connect" item to be selectable - if (![[item parentNode] parentNode]) { - return item == quickConnectItem; - } - - // Otherwise allow all items to be selectable - return YES; -} - -- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item -{ - return (item != quickConnectItem && ![item isLeaf]); -} - -- (BOOL)outlineView:(NSOutlineView *)outlineView shouldShowOutlineCellForItem:(id)item -{ - return ([[item parentNode] parentNode] != nil); -} - -- (BOOL)outlineView:(NSOutlineView *)outlineView shouldCollapseItem:(id)item -{ - return ([[item parentNode] parentNode] != nil); -} - -- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item -{ - NSEvent *event = [NSApp currentEvent]; - BOOL shiftTabbedIn = ([event type] == NSKeyDown && [[event characters] length] && [[event characters] characterAtIndex:0] == NSBackTabCharacter); - - if (shiftTabbedIn && [(SPFavoritesOutlineView *)outlineView justGainedFocus]) { - return NO; - } - - return item != quickConnectItem; -} - -- (void)outlineViewItemDidCollapse:(NSNotification *)notification -{ - [self _setNodeIsExpanded:NO fromNotification:notification]; - } - -- (void)outlineViewItemDidExpand:(NSNotification *)notification - { - [self _setNodeIsExpanded:YES fromNotification:notification]; -} - -#endif - -#pragma mark - -#pragma mark Outline view drag & drop - -#ifndef SP_CODA - -- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard -{ - // Prevent a drag which includes the outline title group from taking place - for (id item in items) - { - if (![[item parentNode] parentNode]) return NO; - } - - // If the user is in the process of changing a node's name, trigger a save and prevent dragging. - if (isEditingItemName) { - [favoritesController saveFavorites]; - - [self _reloadFavoritesViewData]; - - isEditingItemName = NO; - - return NO; - } - - [pboard declareTypes:@[SPFavoritesPasteboardDragType] owner:self]; - - BOOL result = [pboard setData:[NSData data] forType:SPFavoritesPasteboardDragType]; - - draggedNodes = items; - - return result; -} - -- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)childIndex - { - NSDragOperation result = NSDragOperationNone; - - // Prevent the top level or the quick connect item from being a target - if (!item || item == quickConnectItem) return result; - - // Prevent dropping favorites on other favorites (non-groups) - if ((childIndex == NSOutlineViewDropOnItemIndex) && (![item isGroup])) return result; - - // Ensure that none of the dragged nodes are being dragged into children of themselves; if they are, - // prevent the drag. - id itemToCheck = item; - - do { - if ([draggedNodes containsObject:itemToCheck]) { - return result; - } - } - while ((itemToCheck = [itemToCheck parentNode])); - - if ([info draggingSource] == outlineView) { - [outlineView setDropItem:item dropChildIndex:childIndex]; - - result = NSDragOperationMove; - } - - return result; - } - -- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)childIndex -{ - BOOL acceptedDrop = NO; - - if ((!item) || ([info draggingSource] != outlineView)) return acceptedDrop; - - SPTreeNode *node = item ? item : [[[[favoritesRoot childNodes] objectAtIndex:0] childNodes] objectAtIndex:0]; - - // Cache the selected nodes for selection restoration afterwards - NSArray *preDragSelection = [self selectedFavoriteNodes]; - - // Disable all automatic sorting - currentSortItem = -1; - reverseFavoritesSort = NO; - - [prefs setInteger:currentSortItem forKey:SPFavoritesSortedBy]; - [prefs setBool:NO forKey:SPFavoritesSortedInReverse]; - - // Uncheck sort by menu items - for (NSMenuItem *menuItem in [[favoritesSortByMenuItem submenu] itemArray]) - { - [menuItem setState:NSOffState]; - } - - if (![draggedNodes count]) return acceptedDrop; - - if ([node isGroup]) { - if (childIndex == NSOutlineViewDropOnItemIndex) { - childIndex = 0; - } - [outlineView expandItem:node]; - } - else { - if (childIndex == NSOutlineViewDropOnItemIndex) { - childIndex = 0; - } - } - - if (![[node representedObject] nodeName]) { - node = [[favoritesRoot childNodes] objectAtIndex:0]; - } - - NSMutableArray *childNodeArray = [node mutableChildNodes]; - - for (SPTreeNode *treeNode in draggedNodes) - { - // Remove the node from its old location - NSInteger oldIndex = [childNodeArray indexOfObject:treeNode]; - NSInteger newIndex = childIndex; - - if (oldIndex != NSNotFound) { - - [childNodeArray removeObjectAtIndex:oldIndex]; - - if (childIndex > oldIndex) { - newIndex--; - } - } - else { - [[[treeNode parentNode] mutableChildNodes] removeObject:treeNode]; - } - - [childNodeArray insertObject:treeNode atIndex:newIndex]; - - newIndex++; - } - - [favoritesController saveFavorites]; - - [self _reloadFavoritesViewData]; - - [[NSNotificationCenter defaultCenter] postNotificationName:SPConnectionFavoritesChangedNotification object:self]; - - [[[SPAppDelegate preferenceController] generalPreferencePane] updateDefaultFavoritePopup]; - - // Update the selection to account for rearranged faourites - NSMutableIndexSet *restoredSelection = [NSMutableIndexSet indexSet]; - - for (SPTreeNode *eachNode in preDragSelection) - { - [restoredSelection addIndex:[favoritesOutlineView rowForItem:eachNode]]; - } - - [favoritesOutlineView selectRowIndexes:restoredSelection byExtendingSelection:NO]; - - acceptedDrop = YES; - - return acceptedDrop; -} - -#endif - -#pragma mark - -#pragma mark Textfield delegate methods - -#ifndef SP_CODA - -/** - * React to control text changes in the connection interface - */ -- (void)controlTextDidChange:(NSNotification *)notification -{ - id field = [notification object]; - - // Ignore changes in the outline view edit fields - if ([field isKindOfClass:[NSOutlineView class]]) { - return; - } - - // If a 'name' field was edited, and is now of zero length, trigger a replacement - // with a standard suggestion - if (((field == standardNameField) || (field == socketNameField) || (field == sshNameField)) && [self selectedFavoriteNode]) { - if (![[self _stripInvalidCharactersFromString:[field stringValue]] length]) { - [self controlTextDidEndEditing:notification]; - } - } - - [self _startEditingConnection]; - - if (favoriteNameFieldWasAutogenerated && (field != standardNameField && field != socketNameField && field != sshNameField)) { - [self setName:[self _generateNameForConnection]]; - } -} - -/** - * React to the end of control text changes in the connection interface. - */ -- (void)controlTextDidEndEditing:(NSNotification *)notification -{ - id field = [notification object]; - - // Handle updates to the 'name' field of the selected favourite. The favourite name should - // have leading or trailing spaces removed at the end of editing, and if it's left empty, - // should have a default name set. - if (((field == standardNameField) || (field == socketNameField) || (field == sshNameField)) && [self selectedFavoriteNode]) { - - NSString *favoriteName = [self _stripInvalidCharactersFromString:[field stringValue]]; - - if (![favoriteName length]) { - favoriteName = [self _generateNameForConnection]; - - if (favoriteName) { - [self setName:favoriteName]; - } - - // Enable user@host update in reaction to other UI changes - favoriteNameFieldWasAutogenerated = YES; - } - else if (![[field stringValue] isEqualToString:[self _generateNameForConnection]]) { - favoriteNameFieldWasAutogenerated = NO; - [self setName:favoriteName]; - } - } - - // When a host field finishes editing, ensure that it hasn't been set to "localhost" to - // ensure that socket connections don't inadvertently occur. - if (field == standardSQLHostField || field == sshSQLHostField) { - [self _checkHost]; - } -} - -#endif - -#pragma mark - -#pragma mark Tab bar delegate methods - -#ifndef SP_CODA - -/** - * 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]; - - if (selectedTabView == previousType) return; - - [self _startEditingConnection]; - - [self resizeTabViewToConnectionType:selectedTabView animating:YES]; - - // Update the host as appropriate - if ((selectedTabView != SPSocketConnection) && [[self host] isEqualToString:@"localhost"]) { - [self setHost:@""]; - } - - previousType = selectedTabView; - - [self _favoriteTypeDidChange]; -} - -#endif - -#pragma mark - -#pragma mark Color Selector delegate - -- (void)colorSelectorDidChange:(SPColorSelectorView *)sel -{ - [self _startEditingConnection]; -} - -#pragma mark - -#pragma mark Scroll view notifications - -#ifndef SP_CODA - -/** - * 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]; - } -} - -#endif - -#pragma mark - -#pragma mark Menu Validation - -#ifndef SP_CODA - -/** - * Menu item validation. - */ -- (BOOL)validateMenuItem:(NSMenuItem *)menuItem -{ - SEL action = [menuItem action]; - - SPTreeNode *node = [self selectedFavoriteNode]; - NSInteger selectedRows = [favoritesOutlineView numberOfSelectedRows]; - - if ((action == @selector(sortFavorites:)) || (action == @selector(reverseSortFavorites:))) { - - if ([[favoritesRoot allChildLeafs] count] < 2) return NO; - - // 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)]; - } - - // Check or uncheck the reverse sort item - if (action == @selector(reverseSortFavorites:)) { - [menuItem setState:reverseFavoritesSort]; - } - - return YES; - } - - // import does not depend on a selection - if(action == @selector(importFavorites:)) return YES; - - if (node == quickConnectItem) return NO; - - // Remove/rename the selected node - if (action == @selector(removeNode:) || action == @selector(renameNode:)) { - return selectedRows == 1; - } - - // Duplicate and make the selected favorite the default - if (action == @selector(duplicateFavorite:)) { - return ((selectedRows == 1) && (![node isGroup])); - } - - // Make selected favorite the default - if (action == @selector(makeSelectedFavoriteDefault:)) { - NSInteger favoriteID = [[[self selectedFavorite] objectForKey:SPFavoriteIDKey] integerValue]; - - return ((selectedRows == 1) && (![node isGroup]) && (favoriteID != [prefs integerForKey:SPDefaultFavorite])); - } - - // Favorites export - if (action == @selector(exportFavorites:)) { - - if ([[favoritesRoot allChildLeafs] count] == 0 || selectedRows == 0) { - return NO; - } - else if (selectedRows > 1) { - [menuItem setTitle:NSLocalizedString(@"Export Selected...", @"export selected favorites menu item")]; - } - } - - return YES; -} - -#endif - -#pragma mark - -#pragma mark Favorites import/export delegate methods - -#ifndef SP_CODA - -/** - * Called by the favorites importer when the imported data is available. - */ -- (void)favoritesImportData:(NSArray *)data -{ - SPTreeNode *newNode; - NSMutableArray *importedNodes = [NSMutableArray array]; - NSMutableIndexSet *importedIndexSet = [NSMutableIndexSet indexSet]; - - // Add each of the imported favorites to the root node - for (NSMutableDictionary *favorite in data) - { - newNode = [favoritesController addFavoriteNodeWithData:favorite asChildOfNode:nil]; - [importedNodes addObject:newNode]; - } - - if (currentSortItem > SPFavoritesSortUnsorted) { - [self _sortFavorites]; - } - - [self _reloadFavoritesViewData]; - - // Select the new nodes and scroll into view - for (SPTreeNode *eachNode in importedNodes) - { - [importedIndexSet addIndex:[favoritesOutlineView rowForItem:eachNode]]; - } - - [favoritesOutlineView selectRowIndexes:importedIndexSet byExtendingSelection:NO]; - - [self _scrollToSelectedNode]; -} - -/** - * Called by the favorites importer when the import completes. - */ -- (void)favoritesImportCompletedWithError:(NSError *)error -{ - if (error) { - NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Favorites import error", @"favorites import error message") - defaultButton:NSLocalizedString(@"OK", @"OK") - alternateButton:nil - otherButton:nil - informativeTextWithFormat:NSLocalizedString(@"The following error occurred during the import process:\n\n%@", @"favorites import error informative message"), [error localizedDescription]]; - - [alert beginSheetModalForWindow:[dbDocument parentWindow] - modalDelegate:self - didEndSelector:NULL - contextInfo:NULL]; - } -} - -#endif - -#pragma mark - -#pragma mark Private API - -#ifndef SP_CODA - -/** - * Sets the expanded state of the node from the supplied outline view notification. - * - * @param expanded The state of the node - * @param notification The notification genrated from the state change - */ -- (void)_setNodeIsExpanded:(BOOL)expanded fromNotification:(NSNotification *)notification -{ - SPGroupNode *node = [[[notification userInfo] valueForKey:@"NSObject"] representedObject]; - - [node setNodeIsExpanded:expanded]; -} - -#endif - -@end diff --git a/Source/SPConnectionControllerDelegateProtocol.h b/Source/SPConnectionControllerDelegateProtocol.h index 74410309..ae049778 100644 --- a/Source/SPConnectionControllerDelegateProtocol.h +++ b/Source/SPConnectionControllerDelegateProtocol.h @@ -28,6 +28,8 @@ // // More info at <https://github.com/sequelpro/sequelpro> +@class SPConnectionController; + /** * @protocol SPConnectionControllerDelegateProtocol SPConnectionControllerDelegateProtocol.h * @@ -42,13 +44,13 @@ * * @param controller The calling connection controller. */ -- (void)connectionControllerInitiatingConnection:(id)controller; +- (void)connectionControllerInitiatingConnection:(SPConnectionController *)controller; /** * Called when the connection controller's connection attempt failed. * * @param controller The calling connection controller. */ -- (void)connectionControllerConnectAttemptFailed:(id)controller; +- (void)connectionControllerConnectAttemptFailed:(SPConnectionController *)controller; @end diff --git a/Source/SPConnectionControllerInitializer.h b/Source/SPConnectionControllerInitializer.h deleted file mode 100644 index bfa5401a..00000000 --- a/Source/SPConnectionControllerInitializer.h +++ /dev/null @@ -1,49 +0,0 @@ -// -// SPConnectionControllerInitializer.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on January 22, 2012. -// Copyright (c) 2012 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPConnectionController.h" - -/** - * @category SPConnectionControllerInitializer SPConnectionControllerInitializer.h - * - * @author Stuart Connolly http://stuconnolly.com/ - * - * Connection controller initialization category. - */ -@interface SPConnectionController (SPConnectionControllerInitializer) - -- (id)initWithDocument:(SPDatabaseDocument *)document; - -- (void)loadNib; -- (void)registerForNotifications; -- (void)setUpFavoritesOutlineView; -- (void)setUpSelectedConnectionFavorite; - -@end diff --git a/Source/SPConnectionControllerInitializer.m b/Source/SPConnectionControllerInitializer.m deleted file mode 100644 index 22e52502..00000000 --- a/Source/SPConnectionControllerInitializer.m +++ /dev/null @@ -1,417 +0,0 @@ -// -// SPConnectionControllerInitializer.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on January 22, 2012. -// Copyright (c) 2012 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPConnectionControllerInitializer.h" -#import "SPKeychain.h" -#import "SPFavoritesController.h" -#import "SPFavoriteTextFieldCell.h" -#import "SPTreeNode.h" -#import "SPFavoriteNode.h" -#import "SPGroupNode.h" -#import "SPDatabaseDocument.h" -#import "SPSplitView.h" -#import "SPFavoriteColorSupport.h" -#import "SPColorSelectorView.h" - -#ifndef SP_CODA -static NSString *SPConnectionViewNibName = @"ConnectionView"; -#endif - -@interface SPConnectionController () - -- (void)_processFavoritesDataChange; -- (void)_reloadFavoritesViewData; -- (void)_selectNode:(SPTreeNode *)node; -- (void)_scrollToSelectedNode; -- (void)_documentWillClose:(NSNotification *)notification; - -- (SPTreeNode *)_favoriteNodeForFavoriteID:(NSInteger)favoriteID; - -- (void)scrollViewFrameChanged:(NSNotification *)aNotification; - -@end - -@interface SPConnectionController (SPConnectionControllerInitializer_Private_API) - -- (void)_restoreOutlineViewStateNode:(SPTreeNode *)node; - -@end - - -@implementation SPConnectionController (SPConnectionControllerInitializer) - -/** - * 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; - -#ifndef SP_CODA - databaseConnectionSuperview = [dbDocument databaseView]; - databaseConnectionView = [dbDocument valueForKey:@"contentViewSplitter"]; -#endif - - // Keychain references - connectionKeychainID = nil; - connectionKeychainItemName = nil; - connectionKeychainItemAccount = nil; - connectionSSHKeychainItemName = nil; - connectionSSHKeychainItemAccount = nil; - - initComplete = NO; - isEditingItemName = NO; - isConnecting = NO; - isTestingConnection = NO; - sshTunnel = nil; - mySQLConnection = nil; - cancellingConnection = NO; - favoriteNameFieldWasAutogenerated = NO; - - [self loadNib]; - - NSArray *colorList = [[SPFavoriteColorSupport sharedInstance] userColorList]; - [sshColorField setColorList:colorList]; - [sshColorField bind:@"selectedTag" toObject:self withKeyPath:@"colorIndex" options:nil]; - [standardColorField setColorList:colorList]; - [standardColorField bind:@"selectedTag" toObject:self withKeyPath:@"colorIndex" options:nil]; - [socketColorField setColorList:colorList]; - [socketColorField bind:@"selectedTag" toObject:self withKeyPath:@"colorIndex" options:nil]; - - [self registerForNotifications]; - -#ifndef SP_CODA - // Hide the main view and position and display the connection view - [databaseConnectionView setHidden:YES]; - [connectionView setFrame:[databaseConnectionView frame]]; - [databaseConnectionSuperview addSubview:connectionView]; - - // Set up the splitview - [connectionSplitView setMinSize:80.f ofSubviewAtIndex:0]; - [connectionSplitView setMinSize:445.f ofSubviewAtIndex:1]; - - // 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 to be constructed. - favoritesController = [SPFavoritesController sharedFavoritesController]; - - // Tree references - favoritesRoot = [favoritesController favoritesTree]; - currentFavorite = nil; - - // Create the "Quick Connect" placeholder group - quickConnectItem = [[SPTreeNode treeNodeWithRepresentedObject:[SPGroupNode groupNodeWithName:[NSLocalizedString(@"Quick Connect", @"Quick connect item label") uppercaseString]]] retain]; - [quickConnectItem setIsGroup:YES]; - - // Create a NSOutlineView cell for the Quick Connect group - quickConnectCell = [[SPFavoriteTextFieldCell alloc] init]; - [quickConnectCell setDrawsDividerUnderCell:YES]; - [quickConnectCell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; - - // Update the UI - [self _reloadFavoritesViewData]; - [self setUpFavoritesOutlineView]; - [self _restoreOutlineViewStateNode:favoritesRoot]; - - // Set up the selected favourite, and scroll after a small delay to fix animation delay on Lion - [self setUpSelectedConnectionFavorite]; - if ([favoritesOutlineView selectedRow] != -1) { - [self performSelector:@selector(_scrollToSelectedNode) withObject:nil afterDelay:0.0]; - } - - // Set sort items - currentSortItem = (SPFavoritesSortItem)[prefs integerForKey:SPFavoritesSortedBy]; - reverseFavoritesSort = [prefs boolForKey:SPFavoritesSortedInReverse]; -#endif - - initComplete = YES; - } - - return self; -} - -/** - * Loads the connection controllers UI nib. - */ -- (void)loadNib -{ -#ifndef SP_CODA - - // 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:SPConnectionViewNibName bundle:[NSBundle mainBundle]]; - - [nibLoader instantiateNibWithOwner:self topLevelObjects:&connectionViewTopLevelObjects]; - [nibObjectsToRelease addObjectsFromArray:connectionViewTopLevelObjects]; - [nibLoader release]; - -#endif -} - -/** - * Registers for various notifications. - */ -- (void)registerForNotifications -{ - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(_documentWillClose:) - name:SPDocumentWillCloseNotification - object:dbDocument]; - -#ifndef SP_CODA - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(scrollViewFrameChanged:) - name:NSViewFrameDidChangeNotification - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(_processFavoritesDataChange:) - name:SPConnectionFavoritesChangedNotification - object:nil]; - - // Registered to be notified of changes to connection information - [self addObserver:self - forKeyPath:SPFavoriteTypeKey - options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) - context:NULL]; - - [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:SPFavoriteColorIndexKey - 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]; -#endif -} - -/** - * Performs any set up necessary for the favorities outline view. - */ -- (void)setUpFavoritesOutlineView -{ - // 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:@[SPFavoritesPasteboardDragType]]; - [favoritesOutlineView setDraggingSourceOperationMask:NSDragOperationMove forLocal:YES]; -} - -/** - * Sets up the selected connection favorite according to the user's preferences. - */ -- (void)setUpSelectedConnectionFavorite -{ -#ifndef SP_CODA - SPTreeNode *favorite = [self _favoriteNodeForFavoriteID:[prefs integerForKey:[prefs boolForKey:SPSelectLastFavoriteUsed] ? SPLastFavoriteID : SPDefaultFavorite]]; - - if (favorite) { - - if (favorite == quickConnectItem) { - [self _selectNode:favorite]; - } - else { - 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]; - } - - [self _scrollToSelectedNode]; - } - else { - previousType = SPTCPIPConnection; - - [self resizeTabViewToConnectionType:SPTCPIPConnection animating:NO]; - } -#endif -} - -#pragma mark - -#pragma mark Private API - -/** - * Responds to notifications that the favorites root has changed, - * and updates the interface to match. - */ -- (void)_processFavoritesDataChange:(NSNotification *)aNotification -{ -#ifndef SP_CODA - // Check the supplied notification for the sender; if the sender - // was this object, ignore it - if ([aNotification object] == self) return; - - NSArray *selectedFavoriteNodes = [self selectedFavoriteNodes]; - - [self _reloadFavoritesViewData]; - - NSMutableIndexSet *selectionIndexes = [NSMutableIndexSet indexSet]; - - for (SPTreeNode *eachNode in selectedFavoriteNodes) - { - NSInteger anIndex = [favoritesOutlineView rowForItem:eachNode]; - - if (anIndex == -1) continue; - - [selectionIndexes addIndex:anIndex]; - } - - [favoritesOutlineView selectRowIndexes:selectionIndexes byExtendingSelection:NO]; -#endif -} - -/** - * Restores the outline views group nodes expansion state. - * - * @param node The node to traverse - */ -#ifndef SP_CODA -- (void)_restoreOutlineViewStateNode:(SPTreeNode *)node -{ - if ([node isGroup]) { - if ([[node representedObject] nodeIsExpanded]) { - [favoritesOutlineView expandItem:node]; - } - else { - [favoritesOutlineView collapseItem:node]; - } - - for (SPTreeNode *childNode in [node childNodes]) - { - if ([childNode isGroup]) { - [self _restoreOutlineViewStateNode:childNode]; - } - } - } -} -#endif - -@end diff --git a/Source/SPConnectionHandler.h b/Source/SPConnectionHandler.h deleted file mode 100644 index 05883608..00000000 --- a/Source/SPConnectionHandler.h +++ /dev/null @@ -1,53 +0,0 @@ -// -// SPConnectionHandler.h -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on November 15, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPConnectionController.h" - -/** - * @category SPConnectionHandler SPConnectionHandler.h - * - * @author Stuart Connolly http://stuconnolly.com/ - * - * Connection handler category. Handles all connection related non-interface processes. - */ -@interface SPConnectionController (SPConnectionHandler) - -- (void)initiateMySQLConnection; -- (void)initiateMySQLConnectionInBackground; -- (void)initiateSSHTunnelConnection; - -- (void)mySQLConnectionEstablished; -- (void)sshTunnelCallback:(SPSSHTunnel *)theTunnel; - -- (void)addConnectionToDocument; - -- (void)failConnectionWithTitle:(NSString *)theTitle errorMessage:(NSString *)theErrorMessage detail:(NSString *)errorDetail rawErrorText:(NSString *)rawErrorText; - -@end diff --git a/Source/SPConnectionHandler.m b/Source/SPConnectionHandler.m deleted file mode 100644 index a1c7a532..00000000 --- a/Source/SPConnectionHandler.m +++ /dev/null @@ -1,538 +0,0 @@ -// -// SPConnectionHandler.m -// sequel-pro -// -// Created by Stuart Connolly (stuconnolly.com) on November 15, 2010. -// Copyright (c) 2010 Stuart Connolly. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPConnectionHandler.h" -#import "SPDatabaseDocument.h" -#import "SPAlertSheets.h" -#import "SPSSHTunnel.h" -#import "SPKeychain.h" -#import "RegexKitLite.h" -#import "SPCategoryAdditions.h" -#import "SPThreadAdditions.h" - -#import <SPMySQL/SPMySQL.h> - -static NSString *SPLocalhostAddress = @"127.0.0.1"; - -@interface SPConnectionController () - -- (void)_restoreConnectionInterface; - -@end - -@interface SPConnectionController (SPConnectionHandlerPrivateAPI) - -- (void)_showConnectionTestResult:(NSString *)resultString; - -@end - -#pragma mark - - -@implementation SPConnectionController (SPConnectionHandler) - -/** - * Set up the MySQL connection, either through a successful tunnel or directly in the background. - */ -- (void)initiateMySQLConnection -{ -#ifndef SP_CODA - if (isTestingConnection) { - if (sshTunnel) { - [progressIndicatorText setStringValue:NSLocalizedString(@"Testing MySQL...", @"MySQL connection test very short status message")]; - } - else { - [progressIndicatorText setStringValue:NSLocalizedString(@"Testing connection...", @"Connection test very short status message")]; - } - } - else 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(cancelConnection:)]; - [connectButton setEnabled:YES]; - [connectButton display]; -#endif - - [NSThread detachNewThreadWithName:SPCtxt(@"SPConnectionHandler MySQL connection task", dbDocument) target:self selector:@selector(initiateMySQLConnectionInBackground) object:nil]; -} - -/** - * Initiates the core of the MySQL connection process on a background thread. - */ -- (void)initiateMySQLConnectionInBackground -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - mySQLConnection = [[SPMySQLConnection alloc] init]; - - // Set up shared details - [mySQLConnection setUsername:[self user]]; - - // Initialise to socket if appropriate. - if ([self type] == SPSocketConnection) { - [mySQLConnection setUseSocket:YES]; - [mySQLConnection setSocketPath:[self socket]]; - - // Otherwise, initialise to host, using tunnel if appropriate - } - else { - [mySQLConnection setUseSocket:NO]; - - if ([self type] == SPSSHTunnelConnection) { - [mySQLConnection setHost:@"127.0.0.1"]; - - [mySQLConnection setPort:[sshTunnel localPort]]; - [mySQLConnection setProxy:sshTunnel]; - } - else { - [mySQLConnection setHost:[self host]]; - - if ([[self port] length]) [mySQLConnection setPort:[[self port] integerValue]]; - } - } - - // Only set the password if there is no Keychain item set and the connection is not being tested. - // The connection will otherwise ask the delegate for passwords in the Keychain. - if ((!connectionKeychainItemName || isTestingConnection) && [self password]) { - [mySQLConnection setPassword:[self password]]; - } - - // Enable SSL if set - if ([self useSSL]) { - [mySQLConnection setUseSSL:YES]; - - if ([self sslKeyFileLocationEnabled]) { - [mySQLConnection setSslKeyFilePath:[self sslKeyFileLocation]]; - } - - if ([self sslCertificateFileLocationEnabled]) { - [mySQLConnection setSslCertificatePath:[self sslCertificateFileLocation]]; - } - - if ([self sslCACertFileLocationEnabled]) { - [mySQLConnection setSslCACertificatePath:[self sslCACertFileLocation]]; - } - - NSString *userSSLCipherList = [prefs stringForKey:SPSSLCipherListKey]; - if(userSSLCipherList) { - //strip out disabled ciphers (e.g. in "foo:bar:--:baz") - NSRange markerPos = [userSSLCipherList rangeOfRegex:@":?--"]; - if(markerPos.location != NSNotFound) { - userSSLCipherList = [userSSLCipherList substringToIndex:markerPos.location]; - } - [mySQLConnection setSslCipherList:userSSLCipherList]; - } - } - - if(![self useCompression]) - [mySQLConnection removeClientFlags:SPMySQLClientFlagCompression]; - - // 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 setTimeout:[[prefs objectForKey:SPConnectionTimeoutValue] integerValue]]; - [mySQLConnection setUseKeepAlive:[[prefs objectForKey:SPUseKeepAlive] boolValue]]; - [mySQLConnection setKeepAliveInterval:[[prefs objectForKey:SPKeepAliveInterval] floatValue]]; - - // Connect - [mySQLConnection connect]; - - if (![mySQLConnection isConnected]) { - if (sshTunnel && !cancellingConnection) { - - // This is a race condition we cannot fix "properly": - // For meaningful error handling we need to also consider the debug output from the SSH connection. - // The SSH debug output might be sligthly delayed though (flush, delegates, ...) or - // there might not even by any output at all (when it is purely a libmysql issue). - // TL;DR: No guaranteed events we could wait for, just trying our luck. - [NSThread sleepForTimeInterval:0.1]; // 100ms - - // If the state is connection refused, attempt the MySQL connection again with the host using the hostfield value. - if ([sshTunnel state] == SPMySQLProxyForwardingFailed) { - if ([sshTunnel localPortFallback]) { - [mySQLConnection setPort:[sshTunnel localPortFallback]]; - [mySQLConnection connect]; - - if (![mySQLConnection isConnected]) { - [NSThread sleepForTimeInterval:0.1]; //100ms - } - } - } - } - - if (![mySQLConnection isConnected]) { - if (!cancellingConnection) { - NSString *errorMessage = @""; - if (sshTunnel && [sshTunnel state] == SPMySQLProxyForwardingFailed) { - 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 lastErrorMessage]]; - [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"SSH port forwarding failed", @"title when ssh tunnel port forwarding failed") errorMessage:errorMessage detail:[sshTunnel debugMessages] rawErrorText:[mySQLConnection lastErrorMessage]]; - } - else if ([mySQLConnection lastErrorID] == 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 lastErrorMessage]]; - [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Access denied!", @"connection failed due to access denied title") errorMessage:errorMessage detail:nil rawErrorText:[mySQLConnection lastErrorMessage]]; - } - else if ([self type] == SPSocketConnection && (![self socket] || ![[self socket] length]) && ![mySQLConnection socketPath]) { - 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 lastErrorMessage]]; - [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Socket not found!", @"socket not found title") errorMessage:errorMessage detail:nil rawErrorText:[mySQLConnection lastErrorMessage]]; - } - 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 lastErrorMessage]]; - [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Socket connection failed!", @"socket connection failed title") errorMessage:errorMessage detail:nil rawErrorText:[mySQLConnection lastErrorMessage]]; - } - 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 lastErrorMessage]]; - [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"Connection failed!", @"connection failed title") errorMessage:errorMessage detail:nil rawErrorText:[mySQLConnection lastErrorMessage]]; - } - } - - // Tidy up - isConnecting = NO; - - if (sshTunnel) [sshTunnel disconnect], SPClear(sshTunnel); - - SPClear(mySQLConnection); -#ifndef SP_CODA - if (!cancellingConnection) [self _restoreConnectionInterface]; -#endif - [pool release]; - - return; - } - } - - if ([self database] && ![[self database] isEqualToString:@""]) { - if (![mySQLConnection selectDatabase:[self database]]) { - if (!isTestingConnection) { - [[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 lastErrorMessage]] detail:nil rawErrorText:[mySQLConnection lastErrorMessage]]; - } - - // Tidy up - isConnecting = NO; - - if (sshTunnel) SPClear(sshTunnel); - - SPClear(mySQLConnection); - [self _restoreConnectionInterface]; - if (isTestingConnection) { - [self _showConnectionTestResult:NSLocalizedString(@"Invalid database", @"Invalid database very short status message")]; - } - - [pool release]; - - return; - } - } - - // Connection established - [self performSelectorOnMainThread:@selector(mySQLConnectionEstablished) withObject:nil waitUntilDone:NO]; - - [pool release]; -} - -/** - * 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 -{ - if (isTestingConnection) { - [progressIndicatorText setStringValue:NSLocalizedString(@"Testing SSH...", @"SSH testing very short status message")]; - } else { - [progressIndicatorText setStringValue:NSLocalizedString(@"SSH connecting...", @"SSH connecting very short status message")]; - } - [progressIndicatorText display]; - - [connectButton setTitle:NSLocalizedString(@"Cancel", @"cancel button")]; - [connectButton setAction:@selector(cancelConnection:)]; - [connectButton setEnabled:YES]; - [connectButton 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:[dbDocument parentWindow]]; - - // Add keychain or plaintext password as appropriate - note the checks in initiateConnection. - if (connectionSSHKeychainItemName && !isTestingConnection) { - [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]; -} - -/** - * 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 is only testing the connection, kill the connection - // once established and reset the UI. Also catch connection cancels. - if (isTestingConnection || cancellingConnection) { - - // Clean up any connections remaining, and reset the UI - [self cancelConnection:self]; - - if (isTestingConnection) { - [self _showConnectionTestResult:NSLocalizedString(@"Connection succeeded", @"Connection success very short status message")]; - } - - return; - } - -#ifndef SP_CODA - [progressIndicatorText setStringValue:NSLocalizedString(@"Connected", @"connection established message")]; - [progressIndicatorText display]; -#endif - - // Stop the current tab's progress indicator - [dbDocument setIsProcessing:NO]; - - // Successful connection! -#ifndef SP_CODA - [connectButton setEnabled:NO]; - [connectButton display]; - [progressIndicator stopAnimation:self]; - [progressIndicatorText setHidden:YES]; -#endif - - // If SSL was enabled, check it was established correctly - if (useSSL && ([self type] == SPTCPIPConnection || [self type] == SPSocketConnection)) { - if (![mySQLConnection isConnectedViaSSL]) { - SPOnewayAlertSheet( - NSLocalizedString(@"SSL connection not established", @"SSL requested but not used title"), - [dbDocument parentWindow], - 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 { -#ifndef SP_CODA - [dbDocument setStatusIconToImageWithName:@"titlebarlock"]; -#endif - } - } - -#ifndef SP_CODA - // Re-enable favorites table view - [favoritesOutlineView setEnabled:YES]; - [(NSView *)favoritesOutlineView display]; -#endif - - // Release the tunnel if set - will now be retained by the connection - if (sshTunnel) SPClear(sshTunnel); - - // Pass the connection to the document and clean up the interface - [self addConnectionToDocument]; -} - -/** - * 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 the user cancelled the password prompt dialog, continue with no further action. - if ([theTunnel passwordPromptCancelled]) { - [self _restoreConnectionInterface]; - - return; - } - - if (newState == SPMySQLProxyIdle) { - -#ifndef SP_CODA - [dbDocument setTitlebarStatus:NSLocalizedString(@"SSH Disconnected", @"SSH disconnected titlebar marker")]; -#endif - - [[self onMainThread] failConnectionWithTitle:NSLocalizedString(@"SSH connection failed!", @"SSH connection failed title") errorMessage:[theTunnel lastError] detail:[sshTunnel debugMessages] rawErrorText:[theTunnel lastError]]; - } - else if (newState == SPMySQLProxyConnected) { -#ifndef SP_CODA - [dbDocument setTitlebarStatus:NSLocalizedString(@"SSH Connected", @"SSH connected titlebar marker")]; -#endif - - [self initiateMySQLConnection]; - } - else { -#ifndef SP_CODA - [dbDocument setTitlebarStatus:NSLocalizedString(@"SSH Connecting…", @"SSH connecting titlebar marker")]; -#endif - } -} - -/** - * Add the connection to the parent document and restore the - * interface, allowing the application to run as normal. - */ -- (void)addConnectionToDocument -{ -#ifndef SP_CODA - // Hide the connection view and restore the main view - [connectionView removeFromSuperviewWithoutNeedingDisplay]; - [databaseConnectionView setHidden:NO]; - - // Restore the toolbar icons - NSArray *toolbarItems = [[[dbDocument parentWindow] toolbar] items]; - - for (NSUInteger i = 0; i < [toolbarItems count]; i++) [[toolbarItems objectAtIndex:i] setEnabled:YES]; -#endif - - if (connectionKeychainID) [dbDocument setKeychainID:connectionKeychainID]; - - // Pass the connection to the table document, allowing it to set - // up the other classes and the rest of the interface. - [dbDocument setConnection:mySQLConnection]; -} - -/** - * 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 rawErrorText:(NSString *)rawErrorText -{ - BOOL isSSHTunnelBindError = NO; - -#ifndef SP_CODA - [self _restoreConnectionInterface]; -#endif - - // Release as appropriate - if (sshTunnel) { - [sshTunnel disconnect], SPClear(sshTunnel); - - // 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 ([rawErrorText 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:)]) { - [[(NSObject *)delegate onMainThread] connectionControllerConnectAttemptFailed:self]; - } - - // Only display the connection error message if there is a window visible - if ([[dbDocument parentWindow] isVisible]) { - SPBeginAlertSheet(theTitle, NSLocalizedString(@"OK", @"OK button"), (errorDetail) ? NSLocalizedString(@"Show Detail", @"Show detail button") : nil, (isSSHTunnelBindError) ? NSLocalizedString(@"Use Standard Connection", @"use standard connection button") : nil, [dbDocument 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 -{ - 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:SPLocalhostAddress]; - -#ifndef SP_CODA - // Change to standard TCP/IP connection view - [self resizeTabViewToConnectionType:SPTCPIPConnection animating:YES]; -#endif - - // Initiate the connection after a half second delay to give the connection view a chance to resize - [self performSelector:@selector(initiateConnection:) withObject:self afterDelay:0.5]; - } -} - -@end - -#pragma mark - - -@implementation SPConnectionController (SPConnectionHandlerPrivateAPI) - -/** - * Display a connection test error or success message - */ -- (void)_showConnectionTestResult:(NSString *)resultString -{ - if (![NSThread isMainThread]) { - [[self onMainThread] _showConnectionTestResult:resultString]; - } - - [helpButton setHidden:NO]; - [progressIndicator stopAnimation:self]; - [progressIndicatorText setStringValue:resultString]; - [progressIndicatorText setHidden:NO]; -} - -@end diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index d591121d..69e5cb65 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -31,8 +31,6 @@ #import "SPDatabaseDocument.h" #import "SPConnectionController.h" -#import "SPConnectionHandler.h" -#import "SPConnectionControllerInitializer.h" #import "SPTablesList.h" #import "SPDatabaseStructure.h" #import "SPFileHandle.h" @@ -5247,7 +5245,7 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; /** * Invoked by the connection controller when it starts the process of initiating a connection. */ -- (void)connectionControllerInitiatingConnection:(id)controller +- (void)connectionControllerInitiatingConnection:(SPConnectionController *)controller { #ifndef SP_CODA /* ui manipulation */ // Update the window title to indicate that we are trying to establish a connection @@ -5262,7 +5260,7 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; /** * Invoked by the connection controller when the attempt to initiate a connection failed. */ -- (void)connectionControllerConnectAttemptFailed:(id)controller +- (void)connectionControllerConnectAttemptFailed:(SPConnectionController *)controller { #ifdef SP_CODA /* glue */ if ( delegate && [delegate respondsToSelector:@selector(databaseDocumentConnectionFailed:)] ) diff --git a/Source/SPFavoritesOutlineView.m b/Source/SPFavoritesOutlineView.m index 8dbec5da..6a64cca1 100644 --- a/Source/SPFavoritesOutlineView.m +++ b/Source/SPFavoritesOutlineView.m @@ -29,7 +29,7 @@ // More info at <https://github.com/sequelpro/sequelpro> #import "SPFavoritesOutlineView.h" -#import "SPConnectionControllerDelegate.h" +#import "SPConnectionController.h" @interface SPFavoritesOutlineView () @@ -191,12 +191,12 @@ static NSUInteger SPFavoritesOutlineViewUnindent = 6; /** - * If the delegate is a SPConnectionControllerDelegate, and editing is currently in + * If the delegate is a SPConnectionController, and editing is currently in * progress, draw a custom highlight. */ - (void)highlightSelectionInClipRect:(NSRect)clipRect { - // Only proceed if a the delegate is a SPConnectionControllerDelegate and a favoruite being edited + // Only proceed if a the delegate is a SPConnectionController and a favorite being edited if ([[self delegate] isKindOfClass:[SPConnectionController class]] && [(SPConnectionController *)[self delegate] isEditingConnection] && [(SPConnectionController *)[self delegate] selectedFavorite]) diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index c068b48c..013f4762 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -83,9 +83,6 @@ 1792C13710AD75C800ABE758 /* SPServerVariablesController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1792C13610AD75C800ABE758 /* SPServerVariablesController.m */; }; 1798F1871550175B004B0AB8 /* SPFavoritesExporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 1798F1821550175B004B0AB8 /* SPFavoritesExporter.m */; }; 1798F1881550175B004B0AB8 /* SPFavoritesImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 1798F1851550175B004B0AB8 /* SPFavoritesImporter.m */; }; - 1798F18F1550178E004B0AB8 /* SPConnectionControllerDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 1798F18A1550178E004B0AB8 /* SPConnectionControllerDataSource.m */; }; - 1798F1901550178E004B0AB8 /* SPConnectionControllerInitializer.m in Sources */ = {isa = PBXBuildFile; fileRef = 1798F18C1550178E004B0AB8 /* SPConnectionControllerInitializer.m */; }; - 1798F1911550178E004B0AB8 /* SPConnectionHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 1798F18E1550178E004B0AB8 /* SPConnectionHandler.m */; }; 1798F1951550181B004B0AB8 /* SPGroupNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 1798F1941550181B004B0AB8 /* SPGroupNode.m */; }; 1798F19815501838004B0AB8 /* SPMutableArrayAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 1798F19715501838004B0AB8 /* SPMutableArrayAdditions.m */; }; 1798F19B1550185B004B0AB8 /* SPTreeNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 1798F19A1550185B004B0AB8 /* SPTreeNode.m */; }; @@ -121,7 +118,6 @@ 17D390C8127B65AF00672B13 /* SPGeneralPreferencePane.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D390C7127B65AF00672B13 /* SPGeneralPreferencePane.m */; }; 17D390CB127B6BF800672B13 /* SPPreferencesUpgrade.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D390CA127B6BF800672B13 /* SPPreferencesUpgrade.m */; }; 17D3C22212859E070047709F /* SPFavoriteNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D3C22112859E070047709F /* SPFavoriteNode.m */; }; - 17D3C6041289BF350047709F /* SPConnectionControllerDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D3C6031289BF350047709F /* SPConnectionControllerDelegate.m */; }; 17D3C66E128AD4710047709F /* SPFavoritesController.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D3C66D128AD4710047709F /* SPFavoritesController.m */; }; 17D3C671128AD8160047709F /* SPSingleton.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D3C670128AD8160047709F /* SPSingleton.m */; }; 17D3C6D3128B1C900047709F /* SPFavoritesOutlineView.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D3C6D2128B1C900047709F /* SPFavoritesOutlineView.m */; }; @@ -776,12 +772,6 @@ 1798F1841550175B004B0AB8 /* SPFavoritesImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPFavoritesImporter.h; sourceTree = "<group>"; }; 1798F1851550175B004B0AB8 /* SPFavoritesImporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPFavoritesImporter.m; sourceTree = "<group>"; }; 1798F1861550175B004B0AB8 /* SPFavoritesImportProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPFavoritesImportProtocol.h; sourceTree = "<group>"; }; - 1798F1891550178E004B0AB8 /* SPConnectionControllerDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPConnectionControllerDataSource.h; sourceTree = "<group>"; }; - 1798F18A1550178E004B0AB8 /* SPConnectionControllerDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPConnectionControllerDataSource.m; sourceTree = "<group>"; }; - 1798F18B1550178E004B0AB8 /* SPConnectionControllerInitializer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPConnectionControllerInitializer.h; sourceTree = "<group>"; }; - 1798F18C1550178E004B0AB8 /* SPConnectionControllerInitializer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPConnectionControllerInitializer.m; sourceTree = "<group>"; }; - 1798F18D1550178E004B0AB8 /* SPConnectionHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPConnectionHandler.h; sourceTree = "<group>"; }; - 1798F18E1550178E004B0AB8 /* SPConnectionHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPConnectionHandler.m; sourceTree = "<group>"; }; 1798F1931550181B004B0AB8 /* SPGroupNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPGroupNode.h; sourceTree = "<group>"; }; 1798F1941550181B004B0AB8 /* SPGroupNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPGroupNode.m; sourceTree = "<group>"; }; 1798F19615501838004B0AB8 /* SPMutableArrayAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPMutableArrayAdditions.h; sourceTree = "<group>"; }; @@ -836,8 +826,6 @@ 17D390CA127B6BF800672B13 /* SPPreferencesUpgrade.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPPreferencesUpgrade.m; sourceTree = "<group>"; }; 17D3C22012859E070047709F /* SPFavoriteNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPFavoriteNode.h; sourceTree = "<group>"; }; 17D3C22112859E070047709F /* SPFavoriteNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPFavoriteNode.m; sourceTree = "<group>"; }; - 17D3C6021289BF350047709F /* SPConnectionControllerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPConnectionControllerDelegate.h; sourceTree = "<group>"; }; - 17D3C6031289BF350047709F /* SPConnectionControllerDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPConnectionControllerDelegate.m; sourceTree = "<group>"; }; 17D3C66C128AD4710047709F /* SPFavoritesController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPFavoritesController.h; sourceTree = "<group>"; }; 17D3C66D128AD4710047709F /* SPFavoritesController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPFavoritesController.m; sourceTree = "<group>"; }; 17D3C66F128AD8160047709F /* SPSingleton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPSingleton.h; sourceTree = "<group>"; }; @@ -1877,16 +1865,8 @@ 17D38FC2127B0C9500672B13 /* Connection View */ = { isa = PBXGroup; children = ( - 1798F18D1550178E004B0AB8 /* SPConnectionHandler.h */, - 1798F18E1550178E004B0AB8 /* SPConnectionHandler.m */, 5822C9B31000DB2400DCC3D6 /* SPConnectionController.h */, 5822C9B41000DB2400DCC3D6 /* SPConnectionController.m */, - 17D3C6021289BF350047709F /* SPConnectionControllerDelegate.h */, - 17D3C6031289BF350047709F /* SPConnectionControllerDelegate.m */, - 1798F18B1550178E004B0AB8 /* SPConnectionControllerInitializer.h */, - 1798F18C1550178E004B0AB8 /* SPConnectionControllerInitializer.m */, - 1798F1891550178E004B0AB8 /* SPConnectionControllerDataSource.h */, - 1798F18A1550178E004B0AB8 /* SPConnectionControllerDataSource.m */, 17D38FC3127B0CFC00672B13 /* SPConnectionControllerDelegateProtocol.h */, 1798F1801550172A004B0AB8 /* Import & Export */, ); @@ -3407,7 +3387,6 @@ 17FDB04C1280778B00DBBBC2 /* SPFontPreviewTextField.m in Sources */, 17D3C22212859E070047709F /* SPFavoriteNode.m in Sources */, 506CE9311A311C6C0039F736 /* SPTableContentFilterController.m in Sources */, - 17D3C6041289BF350047709F /* SPConnectionControllerDelegate.m in Sources */, 17D3C66E128AD4710047709F /* SPFavoritesController.m in Sources */, 17D3C671128AD8160047709F /* SPSingleton.m in Sources */, 17D3C6D3128B1C900047709F /* SPFavoritesOutlineView.m in Sources */, @@ -3434,9 +3413,6 @@ 17BA2A3215275D8600389803 /* SPExportInterfaceController.m in Sources */, 1798F1871550175B004B0AB8 /* SPFavoritesExporter.m in Sources */, 1798F1881550175B004B0AB8 /* SPFavoritesImporter.m in Sources */, - 1798F18F1550178E004B0AB8 /* SPConnectionControllerDataSource.m in Sources */, - 1798F1901550178E004B0AB8 /* SPConnectionControllerInitializer.m in Sources */, - 1798F1911550178E004B0AB8 /* SPConnectionHandler.m in Sources */, 1798F1951550181B004B0AB8 /* SPGroupNode.m in Sources */, 1798F19815501838004B0AB8 /* SPMutableArrayAdditions.m in Sources */, 1798F19B1550185B004B0AB8 /* SPTreeNode.m in Sources */, |