From aea8e96f2adc91b64e7c521f34890e93f27da36c Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Fri, 28 Aug 2009 18:27:36 +0000 Subject: If the SSH tunnel is unable to bind to the local port because there is already an existing tunnel, give the user the option of using a standard connection to localhost on the port that is in use in order to use the existing tunnel. Fixes issue #371. --- Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m | 21 +++++------ Interfaces/English.lproj/ConnectionView.xib | 13 +++++-- Source/SPConnectionController.m | 44 +++++++++++++++++++--- Source/SPSSHTunnel.m | 2 +- Source/SPTableData.m | 7 +--- 5 files changed, 60 insertions(+), 27 deletions(-) diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m b/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m index 06a8a757..fd2d6444 100644 --- a/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m +++ b/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m @@ -192,8 +192,7 @@ static BOOL sTruncateLongFieldInLogs = YES; { delegate = connectionDelegate; - // Check that the delegate implements willQueryString:connection: and cache the result as its used - // vert frequently. + // Check that the delegate implements willQueryString:connection: and cache the result as its used very frequently. delegateResponseToWillQueryString = [delegate respondsToSelector:@selector(willQueryString:connection:)]; } @@ -273,11 +272,11 @@ static BOOL sTruncateLongFieldInLogs = YES; */ - (BOOL)connect { - const char *theLogin = [self cStringFromString:connectionLogin]; - const char *theHost; - const char *thePass; - const char *theSocket; - void *theRet; + const char *theLogin = [self cStringFromString:connectionLogin]; + const char *theHost; + const char *thePass; + const char *theSocket; + void *theRet; // Disconnect if a connection is already active if (mConnected) { @@ -301,8 +300,6 @@ static BOOL sTruncateLongFieldInLogs = YES; // Set the host as appropriate if (!connectionHost || ![connectionHost length]) { theHost = NULL; - - } else { theHost = [self cStringFromString:connectionHost]; } @@ -559,7 +556,7 @@ static BOOL sTruncateLongFieldInLogs = YES; * ping, but cause long queries to be terminated. * Unlike mysql_ping, this function returns FALSE on failure and TRUE on success. */ -- (BOOL) pingConnection +- (BOOL)pingConnection { struct sigaction timeoutAction; NSDate *startDate = [[NSDate alloc] initWithTimeIntervalSinceNow:0]; @@ -1531,7 +1528,7 @@ static void forcePingTimeout(NSInteger signalNumber) const char *theCDBsName = (const char *)[self cStringFromString:dbsName]; if (theResPtr = mysql_list_dbs(mConnection, theCDBsName)) { - [theResult initWithResPtr: theResPtr encoding: mEncoding timeZone:mTimeZone]; + [theResult initWithResPtr:theResPtr encoding:mEncoding timeZone:mTimeZone]; } else { [theResult init]; @@ -1593,11 +1590,13 @@ static void forcePingTimeout(NSInteger signalNumber) [theResult init]; } } + [queryLock unlock]; if (theResult) { [theResult autorelease]; } + return theResult; } diff --git a/Interfaces/English.lproj/ConnectionView.xib b/Interfaces/English.lproj/ConnectionView.xib index 2eb2d7a1..c47e0ab7 100644 --- a/Interfaces/English.lproj/ConnectionView.xib +++ b/Interfaces/English.lproj/ConnectionView.xib @@ -2,9 +2,9 @@ 1050 - 9J61 + 9L30 677 - 949.46 + 949.54 353.00 YES @@ -3673,7 +3673,7 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - {{61, 193}, {882, 563}} + {{280, 258}, {882, 563}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -4067,6 +4067,13 @@ Source/SPConnectionController.h + + NSObject + + IBProjectSource + Source/SPQueryFavoriteManager.h + + NSObject diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 34446959..5d93a4f4 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -27,6 +27,7 @@ #import "SPAppController.h" #import "SPPreferenceController.h" #import "ImageAndTextCell.h" +#import "RegexKitLite.h" @implementation SPConnectionController @@ -145,7 +146,6 @@ */ - (IBAction)initiateConnection:(id)sender { - // Ensure that host is not empty if this is a TCP/IP or SSH connection if (([self type] == SP_CONNECTION_TCPIP || [self type] == SP_CONNECTION_SSHTUNNEL) && ![[self host] length]) { NSRunAlertPanel(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), NSLocalizedString(@"Insufficient details provided to establish a connection. Please provide at least a host.", @"insufficient details informative message"), NSLocalizedString(@"OK", @"OK button"), nil, nil); @@ -260,6 +260,7 @@ [progressIndicatorText setStringValue:NSLocalizedString(@"MySQL connecting...", @"MySQL connecting very short status message")]; else [progressIndicatorText setStringValue:NSLocalizedString(@"Connecting...", @"Generic connecting very short status message")]; + [progressIndicatorText display]; // Initialise to socket if appropriate. @@ -366,6 +367,8 @@ */ - (void)failConnectionWithTitle:(NSString *)theTitle errorMessage:(NSString *)theErrorMessage detail:(NSString *)errorDetail { + BOOL isSSHTunnelBindError = NO; + // Clean up the interface [progressIndicator stopAnimation:self]; [progressIndicator display]; @@ -376,23 +379,29 @@ [tableDocument clearStatusIcon]; // Release as appropriate - if (sshTunnel) [sshTunnel disconnect], [sshTunnel release], sshTunnel = nil; + if (sshTunnel) { + [sshTunnel disconnect], [sshTunnel release], sshTunnel = nil; + + // If the SSH tunnel connection failed because the port it was trying to bind to was already in use take note + // of it so we can give the user the option of connecting via standard connection and use the existing tunnel. + if ([theErrorMessage rangeOfString:@"bind"].location != NSNotFound) { + isSSHTunnelBindError = YES; + } + } if (errorDetail) [errorDetailText setString:errorDetail]; // Display the connection error message - NSBeginAlertSheet(theTitle, NSLocalizedString(@"OK", @"OK button"), errorDetail?NSLocalizedString(@"Show detail", @"Show detail button"):nil, nil, documentWindow, self, nil, @selector(errorSheetDidEnd:returnCode:contextInfo:), @"connect", theErrorMessage); + NSBeginAlertSheet(theTitle, NSLocalizedString(@"OK", @"OK button"), (errorDetail) ? NSLocalizedString(@"Show Detail", @"Show detail button") : nil, (isSSHTunnelBindError) ? NSLocalizedString(@"Use Standard Connection", @"use standard connection button") : nil, documentWindow, self, nil, @selector(errorSheetDidEnd:returnCode:contextInfo:), @"connect", theErrorMessage); } - /** * Alert sheet callback method - invoked when an error sheet is closed. */ - (void)errorSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(NSString *)contextInfo { [sheet orderOut:self]; - if (returnCode == NSAlertAlternateReturn) [errorDetailWindow makeKeyAndOrderFront:self]; - + // Restore the passwords from keychain for editing if appropriate if (connectionKeychainItemName) { [self setPassword:[keychain getPasswordForName:connectionKeychainItemName account:connectionKeychainItemAccount]]; @@ -400,6 +409,29 @@ if (connectionSSHKeychainItemName) { [self setSshPassword:[keychain getPasswordForName:connectionSSHKeychainItemName account:connectionSSHKeychainItemAccount]]; } + + if (returnCode == NSAlertAlternateReturn) { + [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:SP_CONNECTION_TCPIP]; + + // Change connection details + [self setPort:tunnelPort]; + [self setHost:@"127.0.0.1"]; + + // Change to standard TCP/IP connection view + [self resizeTabViewToConnectionType:SP_CONNECTION_TCPIP animating:YES]; + + // Initiate the connection after half a second to give the connection view a chance to resize + [self performSelector:@selector(initiateConnection:) withObject:self afterDelay:0.5]; + } } /** diff --git a/Source/SPSSHTunnel.m b/Source/SPSSHTunnel.m index 3623a2f2..6c5be81c 100644 --- a/Source/SPSSHTunnel.m +++ b/Source/SPSSHTunnel.m @@ -387,7 +387,7 @@ connectionState = PROXY_STATE_IDLE; [task terminate]; if (lastError) [lastError release]; - lastError = [[NSString alloc] initWithString:NSLocalizedString(@"The SSH Tunnel was unable to bind to the local port. This error may occur if you already have an SSH connection to the same server and are using a 'LocalForward' option in your SSH configuration.", @"SSH tunnel unable to bind to local port message")]; + lastError = [[NSString alloc] initWithString:NSLocalizedString(@"The SSH Tunnel was unable to bind to the local port. This error may occur if you already have an SSH connection to the same server and are using a 'LocalForward' setting in your SSH configuration.\n\nWould you like to fall back to a standard connection to localhost in order to use the existing tunnel?", @"SSH tunnel unable to bind to local port message")]; if (delegate) [delegate performSelectorOnMainThread:stateChangeSelector withObject:self waitUntilDone:NO]; } diff --git a/Source/SPTableData.m b/Source/SPTableData.m index e61eb552..083de094 100644 --- a/Source/SPTableData.m +++ b/Source/SPTableData.m @@ -433,9 +433,8 @@ int nextOffs = 12; if( [parts count] > 8 ) { - // NOTE: this won't get SET NULL | NO ACTION + // NOTE: this won't get SET NULL | NO ACTION | RESTRICT if( [[parts objectAtIndex:9] hasPrefix:@"UPDATE"] ) { - //NSLog( @"update: %@", [parts objectAtIndex:10] ); if( [NSArrayObjectAtIndex(parts, 10) hasPrefix:@"SET"] ) { [constraintDetails setObject:@"SET NULL" forKey:@"update"]; @@ -450,7 +449,6 @@ } } else if( [NSArrayObjectAtIndex(parts, 9) hasPrefix:@"DELETE"] ) { - //NSLog( @"delete: %@", [parts objectAtIndex:10] ); if( [NSArrayObjectAtIndex(parts, 10) hasPrefix:@"SET"] ) { [constraintDetails setObject:@"SET NULL" forKey:@"delete"]; @@ -467,7 +465,6 @@ } if( [parts count] > nextOffs - 1 ) { if( [NSArrayObjectAtIndex(parts, nextOffs) hasPrefix:@"UPDATE"] ) { - //NSLog( @"update: %@", [parts objectAtIndex:13] ); if( [NSArrayObjectAtIndex(parts, nextOffs+1) hasPrefix:@"SET"] ) { [constraintDetails setObject:@"SET NULL" forKey:@"update"]; @@ -480,7 +477,6 @@ } } else if( [NSArrayObjectAtIndex(parts, nextOffs) hasPrefix:@"DELETE"] ) { - //NSLog( @"delete: %@", [parts objectAtIndex:13] ); if( [NSArrayObjectAtIndex(parts, nextOffs+1) hasPrefix:@"SET"] ) { [constraintDetails setObject:@"SET NULL" forKey:@"delete"]; @@ -498,7 +494,6 @@ } // primary key else if( [NSArrayObjectAtIndex(parts, 0) hasPrefix:@"PRIMARY"] ) { - //NSLog( @"pkey is %@", [[parts objectAtIndex:2] stringByTrimmingCharactersInSet:junk] ); } // key else if( [NSArrayObjectAtIndex(parts, 0) hasPrefix:@"KEY"] ) { -- cgit v1.2.3