diff options
author | Max <dmoagx@users.noreply.github.com> | 2018-05-03 22:26:12 +0200 |
---|---|---|
committer | Max <dmoagx@users.noreply.github.com> | 2018-05-03 22:26:27 +0200 |
commit | b49edf67744ba6e54b7c0bdab7dc197cf8faac96 (patch) | |
tree | 25333e30eabfc6c40c9251322d2342ff6c21a530 /Source | |
parent | 7f35608f0ab5f9192245a5bc8dd74da793788389 (diff) | |
download | sequelpro-b49edf67744ba6e54b7c0bdab7dc197cf8faac96.tar.gz sequelpro-b49edf67744ba6e54b7c0bdab7dc197cf8faac96.tar.bz2 sequelpro-b49edf67744ba6e54b7c0bdab7dc197cf8faac96.zip |
Replace all non-cyclic NSAutoreleasepools with @autoreleasepool
Diffstat (limited to 'Source')
26 files changed, 2363 insertions, 2454 deletions
diff --git a/Source/GeneratePreviewForURL.m b/Source/GeneratePreviewForURL.m index 511a3f2e..e0e86aa7 100644 --- a/Source/GeneratePreviewForURL.m +++ b/Source/GeneratePreviewForURL.m @@ -67,73 +67,74 @@ static inline NSString *PathForHTMLResource(NSString *named) OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options) { + @autoreleasepool { + NSURL *myURL = (NSURL *)url; + NSString *urlExtension = [[[myURL path] pathExtension] lowercaseString]; - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + NSString *html = nil; + NSInteger previewHeight = 280; - NSURL *myURL = (NSURL *)url; - NSString *urlExtension = [[[myURL path] pathExtension] lowercaseString]; + // Dispatch different file extensions + if([urlExtension isEqualToString:@"spf"]) { + html = PreviewForSPF(myURL, &previewHeight); + } + else if([urlExtension isEqualToString:@"spfs"]) { + html = PreviewForSPFS(myURL,&previewHeight); + } + else if([urlExtension isEqualToString:@"sql"]) { + html = PreviewForSQL(myURL,&previewHeight,preview); + } - NSString *html = nil; - NSInteger previewHeight = 280; + if(html) { + NSImage *iconImage; + + // Get current Sequel Pro's set of file icons + NSArray *iconImages = [[[NSWorkspace sharedWorkspace] iconForFile:[myURL path]] representations]; + + // just in case + if(!iconImages || [iconImages count] < 1) { + iconImages = @[[NSImage imageNamed:NSImageNameStopProgressTemplate]]; + } + + if([iconImages count] > 1) { + iconImage = [iconImages objectAtIndex:1]; + } + else { + iconImage = [iconImages objectAtIndex:0]; + } - // Dispatch different file extensions - if([urlExtension isEqualToString:@"spf"]) { - html = PreviewForSPF(myURL, &previewHeight); - } - else if([urlExtension isEqualToString:@"spfs"]) { - html = PreviewForSPFS(myURL,&previewHeight); - } - else if([urlExtension isEqualToString:@"sql"]) { - html = PreviewForSQL(myURL,&previewHeight,preview); - } - - if(html) { - NSImage *iconImage; - - // Get current Sequel Pro's set of file icons - NSArray *iconImages = [[[NSWorkspace sharedWorkspace] iconForFile:[myURL path]] representations]; - - // just in case - if(!iconImages || [iconImages count] < 1) - iconImages = @[[NSImage imageNamed:NSImageNameStopProgressTemplate]]; - - if([iconImages count] > 1) - iconImage = [iconImages objectAtIndex:1]; - else - iconImage = [iconImages objectAtIndex:0]; - #warning This can cause a runtime error: "This application is assuming that a particular image contains an NSBitmapImageRep, which is not a good assumption. We are instantiating a bitmap so that whatever this is keeps working, but please do not do this. (...) This may break in the future." - // TODO: draw the image into a bitmap context and grab the jpeg representation? - NSData *image = [iconImage TIFFRepresentation]; - - NSMutableDictionary *props = [[NSMutableDictionary alloc] initWithCapacity:6]; - NSMutableDictionary *imgProps = [[NSMutableDictionary alloc] initWithCapacity:2]; - - [props setObject:@(previewHeight) forKey:(NSString *)kQLPreviewPropertyHeightKey]; - [props setObject:@600 forKey:(NSString *) kQLPreviewPropertyWidthKey]; - - if(image) { - [imgProps setObject:@"image/tiff" forKey:(NSString *)kQLPreviewPropertyMIMETypeKey]; - [imgProps setObject:image forKey:(NSString *)kQLPreviewPropertyAttachmentDataKey]; + // TODO: draw the image into a bitmap context and grab the jpeg representation? + NSData *image = [iconImage TIFFRepresentation]; + + NSMutableDictionary *props = [[NSMutableDictionary alloc] initWithCapacity:6]; + NSMutableDictionary *imgProps = [[NSMutableDictionary alloc] initWithCapacity:2]; + + [props setObject:@(previewHeight) forKey:(NSString *)kQLPreviewPropertyHeightKey]; + [props setObject:@600 forKey:(NSString *) kQLPreviewPropertyWidthKey]; + + if(image) { + [imgProps setObject:@"image/tiff" forKey:(NSString *)kQLPreviewPropertyMIMETypeKey]; + [imgProps setObject:image forKey:(NSString *)kQLPreviewPropertyAttachmentDataKey]; + } + + [props setObject:@{@"icon.tiff" : imgProps} forKey:(NSString *) kQLPreviewPropertyAttachmentsKey]; + [props setObject:@"UTF-8" forKey:(NSString *)kQLPreviewPropertyTextEncodingNameKey]; + [props setObject:[NSNumber numberWithInt:NSUTF8StringEncoding] forKey:(NSString *)kQLPreviewPropertyStringEncodingKey]; + [props setObject:@"text/html" forKey:(NSString *)kQLPreviewPropertyMIMETypeKey]; + + QLPreviewRequestSetDataRepresentation( + preview, + (CFDataRef)[html dataUsingEncoding:NSUTF8StringEncoding], + kUTTypeHTML, + (CFDictionaryRef)props + ); + + [props release]; + [imgProps release]; } - - [props setObject:@{@"icon.tiff" : imgProps} forKey:(NSString *) kQLPreviewPropertyAttachmentsKey]; - [props setObject:@"UTF-8" forKey:(NSString *)kQLPreviewPropertyTextEncodingNameKey]; - [props setObject:[NSNumber numberWithInt:NSUTF8StringEncoding] forKey:(NSString *)kQLPreviewPropertyStringEncodingKey]; - [props setObject:@"text/html" forKey:(NSString *)kQLPreviewPropertyMIMETypeKey]; - - QLPreviewRequestSetDataRepresentation(preview, - (CFDataRef)[html dataUsingEncoding:NSUTF8StringEncoding], - kUTTypeHTML, - (CFDictionaryRef)props - ); - - [props release]; - [imgProps release]; } - [pool release]; - return noErr; } diff --git a/Source/GenerateThumbnailForURL.m b/Source/GenerateThumbnailForURL.m index d2469485..a6a02c02 100644 --- a/Source/GenerateThumbnailForURL.m +++ b/Source/GenerateThumbnailForURL.m @@ -47,47 +47,44 @@ void CancelThumbnailGeneration(void* thisInterface, QLThumbnailRequestRef thumbn OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options, CGSize maximumSize) { - return noErr; - // The following code is meant as example maybe for the future +#if 0 + @autoreleasepool { + NSData *thumbnailData = [NSData dataWithContentsOfFile:@"appIcon.icns"]; + if ( thumbnailData == nil || [thumbnailData length] == 0 ) { + // Nothing Found. Don't care. + [pool release]; + return noErr; + } + + NSSize canvasSize = NSMakeSize((NSInteger)(maximumSize.height/1.3f), maximumSize.height); + + // Thumbnail will be drawn with maximum resolution for desired thumbnail request + // Here we create a graphics context to draw the Quick Look Thumbnail in. + CGContextRef cgContext = QLThumbnailRequestCreateContext(thumbnail, *(CGSize *)&canvasSize, true, NULL); + if(cgContext) { + NSGraphicsContext* context = [NSGraphicsContext graphicsContextWithGraphicsPort:(void *)cgContext flipped:YES]; + if(context) { + //These two lines of code are just good safe programming... + [NSGraphicsContext saveGraphicsState]; + [NSGraphicsContext setCurrentContext:context]; + + // [context setCompositingOperation:NSCompositeSourceOver]; + // CGContextSetAlpha(cgContext, 0.5); - // NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; - // - // NSData *thumbnailData = [NSData dataWithContentsOfFile:@"appIcon.icns"]; - // if ( thumbnailData == nil || [thumbnailData length] == 0 ) { - // // Nothing Found. Don't care. - // [pool release]; - // return noErr; - // } - // - // NSSize canvasSize = NSMakeSize((NSInteger)(maximumSize.height/1.3f), maximumSize.height); - // - // // Thumbnail will be drawn with maximum resolution for desired thumbnail request - // // Here we create a graphics context to draw the Quick Look Thumbnail in. - // CGContextRef cgContext = QLThumbnailRequestCreateContext(thumbnail, *(CGSize *)&canvasSize, true, NULL); - // if(cgContext) { - // NSGraphicsContext* context = [NSGraphicsContext graphicsContextWithGraphicsPort:(void *)cgContext flipped:YES]; - // if(context) { - // //These two lines of code are just good safe programming... - // [NSGraphicsContext saveGraphicsState]; - // [NSGraphicsContext setCurrentContext:context]; - // - // // [context setCompositingOperation:NSCompositeSourceOver]; - // // CGContextSetAlpha(cgContext, 0.5); - // - // NSBitmapImageRep *thumbnailBitmap = [NSBitmapImageRep imageRepWithData:thumbnailData]; - // [thumbnailBitmap drawInRect:NSMakeRect(10,10,200,200)]; - // - // //This line sets the context back to what it was when we're done - // [NSGraphicsContext restoreGraphicsState]; - // } - // - // // When we are done with our drawing code QLThumbnailRequestFlushContext() is called to flush the context - // QLThumbnailRequestFlushContext(thumbnail, cgContext); - // - // CFRelease(cgContext); - // } - // - // [pool release]; - // return noErr; + NSBitmapImageRep *thumbnailBitmap = [NSBitmapImageRep imageRepWithData:thumbnailData]; + [thumbnailBitmap drawInRect:NSMakeRect(10,10,200,200)]; + + //This line sets the context back to what it was when we're done + [NSGraphicsContext restoreGraphicsState]; + } + + // When we are done with our drawing code QLThumbnailRequestFlushContext() is called to flush the context + QLThumbnailRequestFlushContext(thumbnail, cgContext); + + CFRelease(cgContext); + } + } +#endif + return noErr; } diff --git a/Source/SPCSVExporter.m b/Source/SPCSVExporter.m index d3ce39ad..aa69be86 100644 --- a/Source/SPCSVExporter.m +++ b/Source/SPCSVExporter.m @@ -365,7 +365,7 @@ } // Append the line ending to the string for this row, and record the length processed for pool flushing - [csvString appendString:[self csvLineEndingString]]; + [csvString appendString:[self csvLineEndingString]]; currentPoolDataLength += [csvString length]; // Write it to the fileHandle @@ -397,7 +397,7 @@ } // Write data to disk - [[(SPExportFile*)[self exportOutputFile] exportFileHandle] synchronizeFile]; + [[[self exportOutputFile] exportFileHandle] synchronizeFile]; // Mark the process as not running [self setExportProcessIsRunning:NO]; diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 24298318..8380f827 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -2022,175 +2022,170 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, */ - (void)initiateMySQLConnectionInBackground { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + @autoreleasepool { + mySQLConnection = [[SPMySQLConnection alloc] init]; - mySQLConnection = [[SPMySQLConnection alloc] init]; + // Set up shared details + [mySQLConnection setUsername:[self user]]; - // Set up shared details - [mySQLConnection setUsername:[self user]]; + // Initialise to socket if appropriate. + if ([self type] == SPSocketConnection) { + [mySQLConnection setUseSocket:YES]; + [mySQLConnection setSocketPath:[self socket]]; - // 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]; + // Otherwise, initialise to host, using tunnel if appropriate } else { - [mySQLConnection setHost:[self host]]; - - if ([[self port] length]) [mySQLConnection setPort:[[self port] integerValue]]; - } - } + [mySQLConnection setUseSocket:NO]; - // 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]]; - } + if ([self type] == SPSSHTunnelConnection) { + [mySQLConnection setHost:@"127.0.0.1"]; - // Enable SSL if set - if ([self useSSL]) { - [mySQLConnection setUseSSL:YES]; + [mySQLConnection setPort:[sshTunnel localPort]]; + [mySQLConnection setProxy:sshTunnel]; + } + else { + [mySQLConnection setHost:[self host]]; - if ([self sslKeyFileLocationEnabled]) { - [mySQLConnection setSslKeyFilePath:[self sslKeyFileLocation]]; + if ([[self port] length]) [mySQLConnection setPort:[[self port] integerValue]]; + } } - if ([self sslCertificateFileLocationEnabled]) { - [mySQLConnection setSslCertificatePath:[self sslCertificateFileLocation]]; + // 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]]; } - if ([self sslCACertFileLocationEnabled]) { - [mySQLConnection setSslCACertificatePath:[self sslCACertFileLocation]]; - } + // Enable SSL if set + if ([self useSSL]) { + [mySQLConnection setUseSSL:YES]; - 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]; + if ([self sslKeyFileLocationEnabled]) { + [mySQLConnection setSslKeyFilePath:[self sslKeyFileLocation]]; } - [mySQLConnection setSslCipherList:userSSLCipherList]; - } - } - if(![self useCompression]) [mySQLConnection removeClientFlags:SPMySQLClientFlagCompression]; + if ([self sslCertificateFileLocationEnabled]) { + [mySQLConnection setSslCertificatePath:[self sslCertificateFileLocation]]; + } - // Connection delegate must be set before actual connection attempt is made - [mySQLConnection setDelegate:dbDocument]; + if ([self sslCACertFileLocationEnabled]) { + [mySQLConnection setSslCACertificatePath:[self sslCACertFileLocation]]; + } - // Set whether or not we should enable delegate logging according to the prefs - [mySQLConnection setDelegateQueryLogging:[prefs boolForKey:SPConsoleEnableLogging]]; + 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]; + } + } - // Set options from preferences - [mySQLConnection setTimeout:[[prefs objectForKey:SPConnectionTimeoutValue] integerValue]]; - [mySQLConnection setUseKeepAlive:[[prefs objectForKey:SPUseKeepAlive] boolValue]]; - [mySQLConnection setKeepAliveInterval:[[prefs objectForKey:SPKeepAliveInterval] floatValue]]; + if(![self useCompression]) [mySQLConnection removeClientFlags:SPMySQLClientFlagCompression]; - // Connect - [mySQLConnection connect]; + // Connection delegate must be set before actual connection attempt is made + [mySQLConnection setDelegate:dbDocument]; - if (![mySQLConnection isConnected]) { - if (sshTunnel && !cancellingConnection) { + // Set whether or not we should enable delegate logging according to the prefs + [mySQLConnection setDelegateQueryLogging:[prefs boolForKey:SPConsoleEnableLogging]]; - // 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 + // Set options from preferences + [mySQLConnection setTimeout:[[prefs objectForKey:SPConnectionTimeoutValue] integerValue]]; + [mySQLConnection setUseKeepAlive:[[prefs objectForKey:SPUseKeepAlive] boolValue]]; + [mySQLConnection setKeepAliveInterval:[[prefs objectForKey:SPKeepAliveInterval] floatValue]]; - // 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]; + // Connect + [mySQLConnection connect]; - if (![mySQLConnection isConnected]) { - [NSThread sleepForTimeInterval:0.1]; //100ms + 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]]; + 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; + // Tidy up + isConnecting = NO; - if (sshTunnel) [sshTunnel disconnect], SPClear(sshTunnel); + if (sshTunnel) [sshTunnel disconnect], SPClear(sshTunnel); - SPClear(mySQLConnection); + SPClear(mySQLConnection); #ifndef SP_CODA - if (!cancellingConnection) [self _restoreConnectionInterface]; + 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]]; + return; } + } - // Tidy up - isConnecting = NO; + 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]]; + } - if (sshTunnel) SPClear(sshTunnel); + // Tidy up + isConnecting = NO; - SPClear(mySQLConnection); - [self _restoreConnectionInterface]; - if (isTestingConnection) { - [self _showConnectionTestResult:NSLocalizedString(@"Invalid database", @"Invalid database very short status message")]; - } + if (sshTunnel) SPClear(sshTunnel); - [pool release]; + SPClear(mySQLConnection); + [self _restoreConnectionInterface]; + if (isTestingConnection) { + [self _showConnectionTestResult:NSLocalizedString(@"Invalid database", @"Invalid database very short status message")]; + } - return; + return; + } } - } - - // Connection established - [self performSelectorOnMainThread:@selector(mySQLConnectionEstablished) withObject:nil waitUntilDone:NO]; - [pool release]; + // Connection established + [self performSelectorOnMainThread:@selector(mySQLConnectionEstablished) withObject:nil waitUntilDone:NO]; + } } /** diff --git a/Source/SPCustomQuery.m b/Source/SPCustomQuery.m index bddb0b6c..e8c1d6ad 100644 --- a/Source/SPCustomQuery.m +++ b/Source/SPCustomQuery.m @@ -603,372 +603,370 @@ - (void)performQueriesTask:(NSDictionary *)taskArguments { - NSAutoreleasePool *queryRunningPool = [[NSAutoreleasePool alloc] init]; - NSArray *queries = [taskArguments objectForKey:@"queries"]; - SPMySQLStreamingResultStore *resultStore = nil; - NSMutableString *errors = [NSMutableString string]; - SEL callbackMethod = NULL; - NSString *taskButtonString; - - NSUInteger i, totalQueriesRun = 0, totalAffectedRows = 0; - double executionTime = 0; - NSInteger firstErrorOccuredInQuery = -1; - BOOL suppressErrorSheet = NO; - BOOL tableListNeedsReload = NO; - BOOL databaseWasChanged = NO; - // BOOL queriesSeparatedByDelimiter = NO; - - NSCharacterSet *whitespaceAndNewlineSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; + @autoreleasepool { + NSArray *queries = [taskArguments objectForKey:@"queries"]; + SPMySQLStreamingResultStore *resultStore = nil; + NSMutableString *errors = [NSMutableString string]; + SEL callbackMethod = NULL; + NSString *taskButtonString; + + NSUInteger i, totalQueriesRun = 0, totalAffectedRows = 0; + double executionTime = 0; + NSInteger firstErrorOccuredInQuery = -1; + BOOL suppressErrorSheet = NO; + BOOL tableListNeedsReload = NO; + BOOL databaseWasChanged = NO; + // BOOL queriesSeparatedByDelimiter = NO; + + NSCharacterSet *whitespaceAndNewlineSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; #ifndef SP_CODA /* [tableDocumentInstance setQueryMode:] */ - [tableDocumentInstance setQueryMode:SPCustomQueryQueryMode]; + [tableDocumentInstance setQueryMode:SPCustomQueryQueryMode]; #endif - // Notify listeners that a query has started - [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance]; + // Notify listeners that a query has started + [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance]; #ifndef SP_CODA /* growl */ - // Start the notification timer to allow notifications to be shown even if frontmost for long queries - [[SPGrowlController sharedGrowlController] setVisibilityForNotificationName:@"Query Finished"]; + // Start the notification timer to allow notifications to be shown even if frontmost for long queries + [[SPGrowlController sharedGrowlController] setVisibilityForNotificationName:@"Query Finished"]; #endif - // Reset the current table view as necessary to avoid redraw and reload issues. - // Restore the view position to the top left to be within the results for all datasets. - if(editedRow == -1 && !reloadingExistingResult) { - [[customQueryView onMainThread] scrollRowToVisible:0]; - [[customQueryView onMainThread] scrollColumnToVisible:0]; - } + // Reset the current table view as necessary to avoid redraw and reload issues. + // Restore the view position to the top left to be within the results for all datasets. + if(editedRow == -1 && !reloadingExistingResult) { + [[customQueryView onMainThread] scrollRowToVisible:0]; + [[customQueryView onMainThread] scrollColumnToVisible:0]; + } - // Remove all the columns if not reloading the table - if(!reloadingExistingResult) { - if (cqColumnDefinition) SPClear(cqColumnDefinition); - [[self onMainThread] updateTableView]; - } + // Remove all the columns if not reloading the table + if(!reloadingExistingResult) { + SPClear(cqColumnDefinition); + [[self onMainThread] updateTableView]; + } - // Disable automatic query retries on failure for the custom queries - [mySQLConnection setRetryQueriesOnConnectionFailure:NO]; + // Disable automatic query retries on failure for the custom queries + [mySQLConnection setRetryQueriesOnConnectionFailure:NO]; - NSUInteger queryCount = [queries count]; - NSMutableArray *tempQueries = [NSMutableArray arrayWithCapacity:queryCount]; + NSUInteger queryCount = [queries count]; + NSMutableArray *tempQueries = [NSMutableArray arrayWithCapacity:queryCount]; - // Enable task cancellation - if (queryCount > 1) - taskButtonString = NSLocalizedString(@"Stop queries", @"Stop queries string"); - else - taskButtonString = NSLocalizedString(@"Stop query", @"Stop query string"); - [tableDocumentInstance enableTaskCancellationWithTitle:taskButtonString callbackObject:nil callbackFunction:NULL]; + // Enable task cancellation + taskButtonString = (queryCount > 1)? NSLocalizedString(@"Stop queries", @"Stop queries string") : NSLocalizedString(@"Stop query", @"Stop query string"); + [tableDocumentInstance enableTaskCancellationWithTitle:taskButtonString callbackObject:nil callbackFunction:NULL]; - // Perform the supplied queries in series - for ( i = 0 ; i < queryCount ; i++ ) { + // Perform the supplied queries in series + for ( i = 0 ; i < queryCount ; i++ ) { - if (i > 0) { - NSString *taskString = [NSString stringWithFormat:NSLocalizedString(@"Running query %ld of %lu...", @"Running multiple queries string"), (long)(i+1), (unsigned long)queryCount]; - [[tableDocumentInstance onMainThread] setTaskDescription:taskString]; - [[errorText onMainThread] setString:taskString]; - } + if (i > 0) { + NSString *taskString = [NSString stringWithFormat:NSLocalizedString(@"Running query %ld of %lu...", @"Running multiple queries string"), (long)(i+1), (unsigned long)queryCount]; + [[tableDocumentInstance onMainThread] setTaskDescription:taskString]; + [[errorText onMainThread] setString:taskString]; + } - NSString *query = [NSArrayObjectAtIndex(queries, i) stringByTrimmingCharactersInSet:whitespaceAndNewlineSet]; + NSString *query = [NSArrayObjectAtIndex(queries, i) stringByTrimmingCharactersInSet:whitespaceAndNewlineSet]; - // Don't run blank queries, or queries which only contain whitespace. - if (![query length]) - continue; + // Don't run blank queries, or queries which only contain whitespace. + if (![query length]) continue; - // store trimmed queries for usedQueries and history - [tempQueries addObject:query]; + // store trimmed queries for usedQueries and history + [tempQueries addObject:query]; - // Run the query, timing execution (note this also includes network and overhead) - resultStore = [[mySQLConnection resultStoreFromQueryString:query] retain]; - executionTime += [resultStore queryExecutionTime]; - totalQueriesRun++; + // Run the query, timing execution (note this also includes network and overhead) + resultStore = [[mySQLConnection resultStoreFromQueryString:query] retain]; + executionTime += [resultStore queryExecutionTime]; + totalQueriesRun++; - // If this is the last query, retrieve and store the result; otherwise, - // discard the result without fully loading. - if (totalQueriesRun == queryCount || [mySQLConnection lastQueryWasCancelled]) { + // If this is the last query, retrieve and store the result; otherwise, + // discard the result without fully loading. + if (totalQueriesRun == queryCount || [mySQLConnection lastQueryWasCancelled]) { - // Retrieve and cache the column definitions for the result array - if (cqColumnDefinition) [cqColumnDefinition release]; - cqColumnDefinition = [[resultStore fieldDefinitions] retain]; + // Retrieve and cache the column definitions for the result array + if (cqColumnDefinition) [cqColumnDefinition release]; + cqColumnDefinition = [[resultStore fieldDefinitions] retain]; - if(!reloadingExistingResult) { - [[self onMainThread] updateTableView]; - } + if(!reloadingExistingResult) { + [[self onMainThread] updateTableView]; + } - // Find result table name for copying as SQL INSERT. - // If more than one table name is found set resultTableName to nil. - // resultTableName will be set to the original table name (not defined via AS) provided by mysql return - // and the resultTableName can differ due to case-sensitive/insensitive settings!. - NSString *resultTableName = [[cqColumnDefinition objectAtIndex:0] objectForKey:@"org_table"]; - for(id field in cqColumnDefinition) { - if(![[field objectForKey:@"org_table"] isEqualToString:resultTableName]) { - resultTableName = nil; - break; + // Find result table name for copying as SQL INSERT. + // If more than one table name is found set resultTableName to nil. + // resultTableName will be set to the original table name (not defined via AS) provided by mysql return + // and the resultTableName can differ due to case-sensitive/insensitive settings!. + NSString *resultTableName = [[cqColumnDefinition objectAtIndex:0] objectForKey:@"org_table"]; + for(id field in cqColumnDefinition) { + if(![[field objectForKey:@"org_table"] isEqualToString:resultTableName]) { + resultTableName = nil; + break; + } } - } - // Init copyTable with necessary information for copying selected rows as SQL INSERT - [customQueryView setTableInstance:self withTableData:resultData withColumns:cqColumnDefinition withTableName:resultTableName withConnection:mySQLConnection]; + // Init copyTable with necessary information for copying selected rows as SQL INSERT + [customQueryView setTableInstance:self + withTableData:resultData + withColumns:cqColumnDefinition + withTableName:resultTableName + withConnection:mySQLConnection]; - [self updateResultStore:resultStore]; - } else { - [resultStore cancelResultLoad]; - } + [self updateResultStore:resultStore]; + } else { + [resultStore cancelResultLoad]; + } - // Record any affected rows - if ( [mySQLConnection rowsAffectedByLastQuery] != (unsigned long long)~0 ) - totalAffectedRows += (NSUInteger)[mySQLConnection rowsAffectedByLastQuery]; - else if ( [resultStore numberOfRows] ) - totalAffectedRows += (NSUInteger)[resultStore numberOfRows]; + // Record any affected rows + if ( [mySQLConnection rowsAffectedByLastQuery] != (unsigned long long)~0 ) { + totalAffectedRows += (NSUInteger) [mySQLConnection rowsAffectedByLastQuery]; + } + else if ( [resultStore numberOfRows] ) { + totalAffectedRows += (NSUInteger) [resultStore numberOfRows]; + } - [resultStore release]; + [resultStore release]; - // Store any error messages - if ([mySQLConnection queryErrored] || [mySQLConnection lastQueryWasCancelled]) { + // Store any error messages + if ([mySQLConnection queryErrored] || [mySQLConnection lastQueryWasCancelled]) { - NSString *errorString; - if ([mySQLConnection lastQueryWasCancelled]) { - if ([mySQLConnection lastQueryWasCancelledUsingReconnect]) - errorString = NSLocalizedString(@"Query cancelled. Please note that to cancel the query the connection had to be reset; transactions and connection variables were reset.", @"Query cancel by resetting connection error"); - else - errorString = NSLocalizedString(@"Query cancelled.", @"Query cancelled error"); - } else { - errorString = [mySQLConnection lastErrorMessage]; + NSString *errorString; + if ([mySQLConnection lastQueryWasCancelled]) { + if ([mySQLConnection lastQueryWasCancelledUsingReconnect]){ + errorString = NSLocalizedString(@"Query cancelled. Please note that to cancel the query the connection had to be reset; transactions and connection variables were reset.", @"Query cancel by resetting connection error"); + } + else { + errorString = NSLocalizedString(@"Query cancelled.", @"Query cancelled error"); + } + } else { + errorString = [mySQLConnection lastErrorMessage]; - // If dealing with a "MySQL server has gone away" error, explain the situation. - // Error 2006 is CR_SERVER_GONE_ERROR, which means the query write couldn't complete. - if ([mySQLConnection lastErrorID] == 2006) { - errorString = [NSString stringWithFormat:@"%@.\n\n%@", errorString, NSLocalizedString(@"(This usually indicates that the connection has been closed by the server after inactivity, but can also occur due to other conditions. The connection has been restored; please try again if the query is safe to re-run.)", @"Explanation for MySQL server has gone away error")]; + // If dealing with a "MySQL server has gone away" error, explain the situation. + // Error 2006 is CR_SERVER_GONE_ERROR, which means the query write couldn't complete. + if ([mySQLConnection lastErrorID] == 2006) { + errorString = [NSString stringWithFormat:@"%@.\n\n%@", errorString, NSLocalizedString(@"(This usually indicates that the connection has been closed by the server after inactivity, but can also occur due to other conditions. The connection has been restored; please try again if the query is safe to re-run.)", @"Explanation for MySQL server has gone away error")]; + } } - } - // If the query errored, append error to the error log for display at the end - if ( queryCount > 1 ) { - if(firstErrorOccuredInQuery == -1) - firstErrorOccuredInQuery = i+1; - - if(!suppressErrorSheet) - { - // Update error text for the user - [errors appendFormat:NSLocalizedString(@"[ERROR in query %ld] %@\n", @"error text when multiple custom query failed"), - (long)(i+1), - errorString]; - [[errorTextTitle onMainThread] setStringValue:NSLocalizedString(@"Last Error Message", @"Last Error Message")]; - [[errorText onMainThread] setString:errors]; - - // ask the user to continue after detecting an error - if (![mySQLConnection lastQueryWasCancelled]) { - - [tableDocumentInstance setTaskIndicatorShouldAnimate:NO]; - [SPAlertSheets beginWaitingAlertSheetWithTitle:NSLocalizedString(@"MySQL Error", @"mysql error message") - defaultButton:NSLocalizedString(@"Run All", @"run all button") - alternateButton:NSLocalizedString(@"Continue", @"continue button") - otherButton:NSLocalizedString(@"Stop", @"stop button") - alertStyle:NSWarningAlertStyle - docWindow:[tableDocumentInstance parentWindow] - modalDelegate:self - didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) - contextInfo:@"runAllContinueStopSheet" - infoText:[mySQLConnection lastErrorMessage] - returnCode:&runAllContinueStopSheetReturnCode]; - - [tableDocumentInstance setTaskIndicatorShouldAnimate:YES]; - - switch (runAllContinueStopSheetReturnCode) { - case NSAlertDefaultReturn: - suppressErrorSheet = YES; - case NSAlertAlternateReturn: - break; - default: - if(i < queryCount-1) // output that message only if it was not the last one - [errors appendString:NSLocalizedString(@"Execution stopped!\n", @"execution stopped message")]; - i = queryCount; // break for loop; for safety reasons stop the execution of the following queries + // If the query errored, append error to the error log for display at the end + if ( queryCount > 1 ) { + if(firstErrorOccuredInQuery == -1) firstErrorOccuredInQuery = i+1; + + if(!suppressErrorSheet) + { + // Update error text for the user + [errors appendFormat:NSLocalizedString(@"[ERROR in query %ld] %@\n", @"error text when multiple custom query failed"), + (long)(i+1), + errorString]; + [[errorTextTitle onMainThread] setStringValue:NSLocalizedString(@"Last Error Message", @"Last Error Message")]; + [[errorText onMainThread] setString:errors]; + + // ask the user to continue after detecting an error + if (![mySQLConnection lastQueryWasCancelled]) { + + [tableDocumentInstance setTaskIndicatorShouldAnimate:NO]; + [SPAlertSheets beginWaitingAlertSheetWithTitle:NSLocalizedString(@"MySQL Error", @"mysql error message") + defaultButton:NSLocalizedString(@"Run All", @"run all button") + alternateButton:NSLocalizedString(@"Continue", @"continue button") + otherButton:NSLocalizedString(@"Stop", @"stop button") + alertStyle:NSWarningAlertStyle + docWindow:[tableDocumentInstance parentWindow] + modalDelegate:self + didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) + contextInfo:@"runAllContinueStopSheet" + infoText:[mySQLConnection lastErrorMessage] + returnCode:&runAllContinueStopSheetReturnCode]; + + [tableDocumentInstance setTaskIndicatorShouldAnimate:YES]; + + switch (runAllContinueStopSheetReturnCode) { + case NSAlertDefaultReturn: + suppressErrorSheet = YES; + case NSAlertAlternateReturn: + break; + default: + if(i < queryCount-1) { + // output that message only if it was not the last one + [errors appendString:NSLocalizedString(@"Execution stopped!\n", @"execution stopped message")]; + } + i = queryCount; // break for loop; for safety reasons stop the execution of the following queries + } } + } else { + [errors appendFormat:NSLocalizedString(@"[ERROR in query %ld] %@\n", @"error text when multiple custom query failed"), + (long)(i+1), + errorString]; } } else { - [errors appendFormat:NSLocalizedString(@"[ERROR in query %ld] %@\n", @"error text when multiple custom query failed"), - (long)(i+1), - errorString]; + [errors setString:errorString]; } } else { - [errors setString:errorString]; + // Check if table/db list needs an update + // The regex is a compromise between speed and usefullness. TODO: further improvements are needed + if(!tableListNeedsReload && [query isMatchedByRegex:@"(?i)^\\s*\\b(create|alter|drop|rename)\\b\\s+."]) { + tableListNeedsReload = YES; + } + if(!databaseWasChanged && [query isMatchedByRegex:@"(?i)^\\s*\\b(use|drop\\s+database|drop\\s+schema)\\b\\s+."]){ + databaseWasChanged = YES; + } } - } else { - // Check if table/db list needs an update - // The regex is a compromise between speed and usefullness. TODO: further improvements are needed - if(!tableListNeedsReload && [query isMatchedByRegex:@"(?i)^\\s*\\b(create|alter|drop|rename)\\b\\s+."]) - tableListNeedsReload = YES; - if(!databaseWasChanged && [query isMatchedByRegex:@"(?i)^\\s*\\b(use|drop\\s+database|drop\\s+schema)\\b\\s+."]) - databaseWasChanged = YES; + // If the query was cancelled, end all queries. + if ([mySQLConnection lastQueryWasCancelled]) break; } - // If the query was cancelled, end all queries. - if ([mySQLConnection lastQueryWasCancelled]) break; - } - - // Reload table list if at least one query began with drop, alter, rename, or create - if(tableListNeedsReload || databaseWasChanged) { - // Build database pulldown menu - [[tableDocumentInstance onMainThread] setDatabases:self]; - if (databaseWasChanged) - // Reset the current database - [tableDocumentInstance refreshCurrentDatabase]; + // Reload table list if at least one query began with drop, alter, rename, or create + if(tableListNeedsReload || databaseWasChanged) { + // Build database pulldown menu + [[tableDocumentInstance onMainThread] setDatabases:self]; - // Reload table list - [tablesListInstance updateTables:self]; + if (databaseWasChanged) { + // Reset the current database + [tableDocumentInstance refreshCurrentDatabase]; + } - } + // Reload table list + [tablesListInstance updateTables:self]; + } - if(usedQuery) - [usedQuery release]; + if(usedQuery) [usedQuery release]; - // if(!queriesSeparatedByDelimiter) // TODO: How to combine queries delimited by DELIMITER? - usedQuery = [[NSString stringWithString:[tempQueries componentsJoinedByString:@";\n"]] retain]; + // if(!queriesSeparatedByDelimiter) // TODO: How to combine queries delimited by DELIMITER? + usedQuery = [[NSString stringWithString:[tempQueries componentsJoinedByString:@";\n"]] retain]; - if (lastExecutedQuery) [lastExecutedQuery release]; - lastExecutedQuery = [[tempQueries lastObject] retain]; + if (lastExecutedQuery) [lastExecutedQuery release]; + lastExecutedQuery = [[tempQueries lastObject] retain]; - // Perform empty query if no query is given - if ( !queryCount ) { - resultStore = [mySQLConnection resultStoreFromQueryString:@""]; - [resultStore cancelResultLoad]; - [errors setString:[mySQLConnection lastErrorMessage]]; - } + // Perform empty query if no query is given + if ( !queryCount ) { + resultStore = [mySQLConnection resultStoreFromQueryString:@""]; + [resultStore cancelResultLoad]; + [errors setString:[mySQLConnection lastErrorMessage]]; + } #ifndef SP_CODA - // add query to history - if(!reloadingExistingResult && [usedQuery length]) - [self performSelectorOnMainThread:@selector(addHistoryEntry:) withObject:usedQuery waitUntilDone:NO]; + // add query to history + if(!reloadingExistingResult && [usedQuery length]) { + [self performSelectorOnMainThread:@selector(addHistoryEntry:) withObject:usedQuery waitUntilDone:NO]; + } #endif - // Update status/errors text - NSDictionary *statusDetails = [NSDictionary dictionaryWithObjectsAndKeys: - errors, @"errorString", - [NSNumber numberWithInteger:firstErrorOccuredInQuery], @"firstErrorQueryNumber", - nil]; - [self performSelectorOnMainThread:@selector(updateStatusInterfaceWithDetails:) withObject:statusDetails waitUntilDone:YES]; - - // Set up the status string - NSString *statusString = nil; - NSString *statusErrorString = [errors length]?NSLocalizedString(@"Errors", @"Errors title"):NSLocalizedString(@"No errors", @"No errors title"); - if ( [mySQLConnection lastQueryWasCancelled] ) { - if (totalQueriesRun > 1) { - statusString = [NSString stringWithFormat:NSLocalizedString(@"%@; Cancelled in query %ld, after %@", @"text showing multiple queries were cancelled"), - statusErrorString, - (long)totalQueriesRun, - [NSString stringForTimeInterval:executionTime] - ]; - } else { - statusString = [NSString stringWithFormat:NSLocalizedString(@"%@; Cancelled after %@", @"text showing a query was cancelled"), - statusErrorString, - [NSString stringForTimeInterval:executionTime] - ]; - } - } else if ( totalQueriesRun > 1 ) { - if (totalAffectedRows==1) { - statusString = [NSString stringWithFormat:NSLocalizedString(@"%@; 1 row affected in total, by %ld queries taking %@", @"text showing one row has been affected by multiple queries"), - statusErrorString, - (long)totalQueriesRun, - [NSString stringForTimeInterval:executionTime] - ]; - } else { - statusString = [NSString stringWithFormat:NSLocalizedString(@"%@; %ld rows affected in total, by %ld queries taking %@", @"text showing how many rows have been affected by multiple queries"), - statusErrorString, - (long)totalAffectedRows, - (long)totalQueriesRun, - [NSString stringForTimeInterval:executionTime] - ]; - } - } else { - if (totalAffectedRows==1) { - statusString = [NSString stringWithFormat:NSLocalizedString(@"%@; 1 row affected", @"text showing one row has been affected by a single query"), - statusErrorString - ]; + // Update status/errors text + NSDictionary *statusDetails = [NSDictionary dictionaryWithObjectsAndKeys: + errors, @"errorString", + [NSNumber numberWithInteger:firstErrorOccuredInQuery], @"firstErrorQueryNumber", + nil + ]; + [self performSelectorOnMainThread:@selector(updateStatusInterfaceWithDetails:) withObject:statusDetails waitUntilDone:YES]; + + // Set up the status string + NSString *statusString = nil; + NSString *statusErrorString = [errors length]?NSLocalizedString(@"Errors", @"Errors title"):NSLocalizedString(@"No errors", @"No errors title"); + if ( [mySQLConnection lastQueryWasCancelled] ) { + if (totalQueriesRun > 1) { + statusString = [NSString stringWithFormat:NSLocalizedString(@"%@; Cancelled in query %ld, after %@", @"text showing multiple queries were cancelled"), + statusErrorString, + (long)totalQueriesRun, + [NSString stringForTimeInterval:executionTime]]; + } else { + statusString = [NSString stringWithFormat:NSLocalizedString(@"%@; Cancelled after %@", @"text showing a query was cancelled"), + statusErrorString, + [NSString stringForTimeInterval:executionTime]]; + } + } else if ( totalQueriesRun > 1 ) { + if (totalAffectedRows==1) { + statusString = [NSString stringWithFormat:NSLocalizedString(@"%@; 1 row affected in total, by %ld queries taking %@", @"text showing one row has been affected by multiple queries"), + statusErrorString, + (long)totalQueriesRun, + [NSString stringForTimeInterval:executionTime]]; + } else { + statusString = [NSString stringWithFormat:NSLocalizedString(@"%@; %ld rows affected in total, by %ld queries taking %@", @"text showing how many rows have been affected by multiple queries"), + statusErrorString, + (long)totalAffectedRows, + (long)totalQueriesRun, + [NSString stringForTimeInterval:executionTime]]; + } } else { - statusString = [NSString stringWithFormat:NSLocalizedString(@"%@; %ld rows affected", @"text showing how many rows have been affected by a single query"), - statusErrorString, - (long)totalAffectedRows - ]; - } - if([resultData count]) { - // we were running a query that returns a result set (ie. SELECT). - // TODO: mysql_query() returns as soon as the first result row is found (which might be pretty soon when using indexes / not doing aggregations) - // and that makes our query time measurement pretty useless (see #264) - statusString = [statusString stringByAppendingFormat:NSLocalizedString(@", first row available after %1$@",@"Custom Query : text appended to the “x row(s) affected” messages. $1 is a time interval"),[NSString stringForTimeInterval:executionTime]]; - } - else { - statusString = [statusString stringByAppendingFormat:NSLocalizedString(@", taking %1$@",@"Custom Query : text appended to the “x row(s) affected” messages (for update/delete queries). $1 is a time interval"),[NSString stringForTimeInterval:executionTime]]; + if (totalAffectedRows==1) { + statusString = [NSString stringWithFormat:NSLocalizedString(@"%@; 1 row affected", @"text showing one row has been affected by a single query"), + statusErrorString]; + } else { + statusString = [NSString stringWithFormat:NSLocalizedString(@"%@; %ld rows affected", @"text showing how many rows have been affected by a single query"), + statusErrorString, + (long)totalAffectedRows]; + } + if([resultData count]) { + // we were running a query that returns a result set (ie. SELECT). + // TODO: mysql_query() returns as soon as the first result row is found (which might be pretty soon when using indexes / not doing aggregations) + // and that makes our query time measurement pretty useless (see #264) + statusString = [statusString stringByAppendingFormat:NSLocalizedString(@", first row available after %1$@",@"Custom Query : text appended to the “x row(s) affected” messages. $1 is a time interval"),[NSString stringForTimeInterval:executionTime]]; + } + else { + statusString = [statusString stringByAppendingFormat:NSLocalizedString(@", taking %1$@",@"Custom Query : text appended to the “x row(s) affected” messages (for update/delete queries). $1 is a time interval"),[NSString stringForTimeInterval:executionTime]]; + } } - } - [[affectedRowsText onMainThread] setStringValue:statusString]; + [[affectedRowsText onMainThread] setStringValue:statusString]; - // Restore automatic query retries - [mySQLConnection setRetryQueriesOnConnectionFailure:YES]; + // Restore automatic query retries + [mySQLConnection setRetryQueriesOnConnectionFailure:YES]; #ifndef SP_CODA /* [tableDocumentInstance setQueryMode:] */ - [tableDocumentInstance setQueryMode:SPInterfaceQueryMode]; + [tableDocumentInstance setQueryMode:SPInterfaceQueryMode]; #endif - // If no results were returned, redraw the empty table and post notifications before returning. - if ( ![resultData count] ) { - [customQueryView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:YES]; + // If no results were returned, redraw the empty table and post notifications before returning. + if ( ![resultData count] ) { + [customQueryView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:YES]; - // Notify any listeners that the query has completed - [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; + // Notify any listeners that the query has completed + [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; #ifndef SP_CODA /* growl */ - // Perform the Growl notification for query completion - [[SPGrowlController sharedGrowlController] notifyWithTitle:@"Query Finished" - description:[NSString stringWithFormat:NSLocalizedString(@"%@",@"description for query finished growl notification"), [errorText string]] - document:tableDocumentInstance - notificationName:@"Query Finished"]; + // Perform the Growl notification for query completion + [[SPGrowlController sharedGrowlController] notifyWithTitle:@"Query Finished" + description:[NSString stringWithFormat:NSLocalizedString(@"%@",@"description for query finished growl notification"), [errorText string]] + document:tableDocumentInstance + notificationName:@"Query Finished"]; #endif - // Set up the callback if present - if ([taskArguments objectForKey:@"callback"]) { - [[taskArguments objectForKey:@"callback"] getValue:&callbackMethod]; - [self performSelectorOnMainThread:callbackMethod withObject:nil waitUntilDone:NO]; - } + // Set up the callback if present + if ([taskArguments objectForKey:@"callback"]) { + [[taskArguments objectForKey:@"callback"] getValue:&callbackMethod]; + [self performSelectorOnMainThread:callbackMethod withObject:nil waitUntilDone:NO]; + } - [tableDocumentInstance endTask]; - [queryRunningPool release]; + [tableDocumentInstance endTask]; - return; - } + return; + } - // Restore the result view origin if appropriate - if (!NSEqualRects(selectionViewportToRestore, NSZeroRect)) { + // Restore the result view origin if appropriate + if (!NSEqualRects(selectionViewportToRestore, NSZeroRect)) { - // Scroll the viewport to the saved location - selectionViewportToRestore.size = [customQueryView visibleRect].size; - [(SPCopyTable*)[customQueryView onMainThread] scrollRectToVisible:selectionViewportToRestore]; - } + // Scroll the viewport to the saved location + selectionViewportToRestore.size = [customQueryView visibleRect].size; + [[customQueryView onMainThread] scrollRectToVisible:selectionViewportToRestore]; + } - //query finished - [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; + //query finished + [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; #ifndef SP_CODA /* growl */ - // Query finished Growl notification - [[SPGrowlController sharedGrowlController] notifyWithTitle:@"Query Finished" - description:[NSString stringWithFormat:NSLocalizedString(@"%@",@"description for query finished growl notification"), [errorText string]] - document:tableDocumentInstance - notificationName:@"Query Finished"]; + // Query finished Growl notification + [[SPGrowlController sharedGrowlController] notifyWithTitle:@"Query Finished" + description:[NSString stringWithFormat:NSLocalizedString(@"%@",@"description for query finished growl notification"), [errorText string]] + document:tableDocumentInstance + notificationName:@"Query Finished"]; #endif - // Set up the callback if present - if ([taskArguments objectForKey:@"callback"]) { - [[taskArguments objectForKey:@"callback"] getValue:&callbackMethod]; - [self performSelectorOnMainThread:callbackMethod withObject:nil waitUntilDone:YES]; - } - - [tableDocumentInstance endTask]; + // Set up the callback if present + if ([taskArguments objectForKey:@"callback"]) { + [[taskArguments objectForKey:@"callback"] getValue:&callbackMethod]; + [self performSelectorOnMainThread:callbackMethod withObject:nil waitUntilDone:YES]; + } - // Restore selection indexes if appropriate - if (selectionIndexToRestore) - [customQueryView selectRowIndexes:selectionIndexToRestore byExtendingSelection:NO]; + [tableDocumentInstance endTask]; - if(reloadingExistingResult) - [[tableDocumentInstance parentWindow] makeFirstResponder:customQueryView]; + // Restore selection indexes if appropriate + if (selectionIndexToRestore) [customQueryView selectRowIndexes:selectionIndexToRestore byExtendingSelection:NO]; - [queryRunningPool release]; + if(reloadingExistingResult) [[tableDocumentInstance parentWindow] makeFirstResponder:customQueryView]; + } } /** diff --git a/Source/SPDataImport.m b/Source/SPDataImport.m index 629dde25..f350ae0f 100644 --- a/Source/SPDataImport.m +++ b/Source/SPDataImport.m @@ -1759,17 +1759,14 @@ cleanup: */ - (void)_importBackgroundProcess:(NSDictionary *)userInfo { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSString *filename = [userInfo objectForKey:@"filename"]; - NSString *fileType = [userInfo objectForKey:@"fileType"]; - - // Use the appropriate processing function for the file type - if ([fileType isEqualToString:@"SQL"]) - [self importSQLFile:filename]; - else if ([fileType isEqualToString:@"CSV"]) - [self importCSVFile:filename]; - - [pool release]; + @autoreleasepool { + NSString *filename = [userInfo objectForKey:@"filename"]; + NSString *fileType = [userInfo objectForKey:@"fileType"]; + + // Use the appropriate processing function for the file type + if ([fileType isEqualToString:@"SQL"]) [self importSQLFile:filename]; + else if ([fileType isEqualToString:@"CSV"]) [self importCSVFile:filename]; + } } /** diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index 2ec74ae3..3a079883 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -5081,70 +5081,65 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; */ - (void)restoreSession { - NSAutoreleasePool *taskPool = [[NSAutoreleasePool alloc] init]; + @autoreleasepool { + // Check and set the table + NSArray *tables = [tablesListInstance tables]; - // Check and set the table - NSArray *tables = [tablesListInstance tables]; + BOOL isSelectedTableDefined = YES; - BOOL isSelectedTableDefined = YES; + if([tables indexOfObject:[spfSession objectForKey:@"table"]] == NSNotFound) { + isSelectedTableDefined = NO; + } - if([tables indexOfObject:[spfSession objectForKey:@"table"]] == NSNotFound) { - isSelectedTableDefined = NO; - } + // Restore toolbar setting + if([spfSession objectForKey:@"isToolbarVisible"]) { + [mainToolbar setVisible:[[spfSession objectForKey:@"isToolbarVisible"] boolValue]]; + } - // Restore toolbar setting - if([spfSession objectForKey:@"isToolbarVisible"]) { - [mainToolbar setVisible:[[spfSession objectForKey:@"isToolbarVisible"] boolValue]]; - } - - // Reset database view encoding if differs from default - if([spfSession objectForKey:@"connectionEncoding"] && ![[mySQLConnection encoding] isEqualToString:[spfSession objectForKey:@"connectionEncoding"]]) { - [self setConnectionEncoding:[spfSession objectForKey:@"connectionEncoding"] reloadingViews:YES]; - } + // Reset database view encoding if differs from default + if([spfSession objectForKey:@"connectionEncoding"] && ![[mySQLConnection encoding] isEqualToString:[spfSession objectForKey:@"connectionEncoding"]]) { + [self setConnectionEncoding:[spfSession objectForKey:@"connectionEncoding"] reloadingViews:YES]; + } - if(isSelectedTableDefined) { - // Set table content details for restore - if([spfSession objectForKey:@"contentSortCol"]) - [tableContentInstance setSortColumnNameToRestore:[spfSession objectForKey:@"contentSortCol"] isAscending:[[spfSession objectForKey:@"contentSortColIsAsc"] boolValue]]; - if([spfSession objectForKey:@"contentPageNumber"]) - [tableContentInstance setPageToRestore:[[spfSession objectForKey:@"pageNumber"] integerValue]]; - if([spfSession objectForKey:@"contentViewport"]) - [tableContentInstance setViewportToRestore:NSRectFromString([spfSession objectForKey:@"contentViewport"])]; - if([spfSession objectForKey:@"contentFilter"]) - [tableContentInstance setFiltersToRestore:[spfSession objectForKey:@"contentFilter"]]; + if(isSelectedTableDefined) { + // Set table content details for restore + if([spfSession objectForKey:@"contentSortCol"]) [tableContentInstance setSortColumnNameToRestore:[spfSession objectForKey:@"contentSortCol"] isAscending:[[spfSession objectForKey:@"contentSortColIsAsc"] boolValue]]; + if([spfSession objectForKey:@"contentPageNumber"]) [tableContentInstance setPageToRestore:[[spfSession objectForKey:@"pageNumber"] integerValue]]; + if([spfSession objectForKey:@"contentViewport"]) [tableContentInstance setViewportToRestore:NSRectFromString([spfSession objectForKey:@"contentViewport"])]; + if([spfSession objectForKey:@"contentFilter"]) [tableContentInstance setFiltersToRestore:[spfSession objectForKey:@"contentFilter"]]; - // Select table - [tablesListInstance selectTableAtIndex:[NSNumber numberWithInteger:[tables indexOfObject:[spfSession objectForKey:@"table"]]]]; + // Select table + [tablesListInstance selectTableAtIndex:[NSNumber numberWithInteger:[tables indexOfObject:[spfSession objectForKey:@"table"]]]]; - // Restore table selection indexes - if([spfSession objectForKey:@"contentSelection"]) { - [tableContentInstance setSelectionToRestore:[spfSession objectForKey:@"contentSelection"]]; - } + // Restore table selection indexes + if([spfSession objectForKey:@"contentSelection"]) { + [tableContentInstance setSelectionToRestore:[spfSession objectForKey:@"contentSelection"]]; + } - [[tablesListInstance valueForKeyPath:@"tablesListView"] scrollRowToVisible:[tables indexOfObject:[spfSession objectForKey:@"selectedTable"]]]; + [[tablesListInstance valueForKeyPath:@"tablesListView"] scrollRowToVisible:[tables indexOfObject:[spfSession objectForKey:@"selectedTable"]]]; - } + } - // update UI on main thread - SPMainQSync(^{ - // Select view - NSString *view = [spfSession objectForKey:@"view"]; - if([view isEqualToString:@"SP_VIEW_STRUCTURE"]) [self viewStructure:self]; - else if([view isEqualToString:@"SP_VIEW_CONTENT"]) [self viewContent:self]; - else if([view isEqualToString:@"SP_VIEW_CUSTOMQUERY"]) [self viewQuery:self]; - else if([view isEqualToString:@"SP_VIEW_STATUS"]) [self viewStatus:self]; - else if([view isEqualToString:@"SP_VIEW_RELATIONS"]) [self viewRelations:self]; - else if([view isEqualToString:@"SP_VIEW_TRIGGERS"]) [self viewTriggers:self]; - - [self updateWindowTitle:self]; - }); + // update UI on main thread + SPMainQSync(^{ + // Select view + NSString *view = [spfSession objectForKey:@"view"]; + if([view isEqualToString:@"SP_VIEW_STRUCTURE"]) [self viewStructure:self]; + else if([view isEqualToString:@"SP_VIEW_CONTENT"]) [self viewContent:self]; + else if([view isEqualToString:@"SP_VIEW_CUSTOMQUERY"]) [self viewQuery:self]; + else if([view isEqualToString:@"SP_VIEW_STATUS"]) [self viewStatus:self]; + else if([view isEqualToString:@"SP_VIEW_RELATIONS"]) [self viewRelations:self]; + else if([view isEqualToString:@"SP_VIEW_TRIGGERS"]) [self viewTriggers:self]; + + [self updateWindowTitle:self]; + }); - // dealloc spfSession data - SPClear(spfSession); + // dealloc spfSession data + SPClear(spfSession); - // End the task - [self endTask]; - [taskPool drain]; + // End the task + [self endTask]; + } } #endif @@ -6231,112 +6226,109 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; */ - (void)_selectDatabaseAndItem:(NSDictionary *)selectionDetails { - NSAutoreleasePool *taskPool = [[NSAutoreleasePool alloc] init]; - NSString *targetDatabaseName = [selectionDetails objectForKey:@"database"]; + @autoreleasepool { + NSString *targetDatabaseName = [selectionDetails objectForKey:@"database"]; #ifndef SP_CODA /* update history controller */ - NSString *targetItemName = [selectionDetails objectForKey:@"item"]; + NSString *targetItemName = [selectionDetails objectForKey:@"item"]; - // Save existing scroll position and details, and ensure no duplicate entries are created as table list changes - BOOL historyStateChanging = [spHistoryControllerInstance modifyingState]; - - if (!historyStateChanging) { - [spHistoryControllerInstance updateHistoryEntries]; - [spHistoryControllerInstance setModifyingState:YES]; - } -#endif + // Save existing scroll position and details, and ensure no duplicate entries are created as table list changes + BOOL historyStateChanging = [spHistoryControllerInstance modifyingState]; - if (![targetDatabaseName isEqualToString:selectedDatabase]) { + if (!historyStateChanging) { + [spHistoryControllerInstance updateHistoryEntries]; + [spHistoryControllerInstance setModifyingState:YES]; + } +#endif - // Attempt to select the specified database, and abort on failure + if (![targetDatabaseName isEqualToString:selectedDatabase]) { + // Attempt to select the specified database, and abort on failure #ifndef SP_CODA /* patch */ - if ([[chooseDatabaseButton onMainThread] indexOfItemWithTitle:targetDatabaseName] == NSNotFound || ![mySQLConnection selectDatabase:targetDatabaseName]) + if ([[chooseDatabaseButton onMainThread] indexOfItemWithTitle:targetDatabaseName] == NSNotFound || ![mySQLConnection selectDatabase:targetDatabaseName]) #else - if ( ![mySQLConnection selectDatabase:targetDatabaseName] ) + if ( ![mySQLConnection selectDatabase:targetDatabaseName] ) #endif - { - // End the task first to ensure the database dropdown can be reselected - [self endTask]; + { + // End the task first to ensure the database dropdown can be reselected + [self endTask]; - if ([mySQLConnection isConnected]) { + if ([mySQLConnection isConnected]) { - // Update the database list - [[self onMainThread] setDatabases:self]; + // Update the database list + [[self onMainThread] setDatabases:self]; - SPOnewayAlertSheet( - NSLocalizedString(@"Error", @"error"), - parentWindow, - [NSString stringWithFormat:NSLocalizedString(@"Unable to select database %@.\nPlease check you have the necessary privileges to view the database, and that the database still exists.", @"message of panel when connection to db failed after selecting from popupbutton"), targetDatabaseName] - ); - } + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + parentWindow, + [NSString stringWithFormat:NSLocalizedString(@"Unable to select database %@.\nPlease check you have the necessary privileges to view the database, and that the database still exists.", @"message of panel when connection to db failed after selecting from popupbutton"), targetDatabaseName] + ); + } - [taskPool drain]; - return; - } + return; + } #ifndef SP_CODA /* chooseDatabaseButton selectItemWithTitle: */ - [[chooseDatabaseButton onMainThread] selectItemWithTitle:targetDatabaseName]; + [[chooseDatabaseButton onMainThread] selectItemWithTitle:targetDatabaseName]; #endif - if (selectedDatabase) SPClear(selectedDatabase); - selectedDatabase = [[NSString alloc] initWithString:targetDatabaseName]; + if (selectedDatabase) SPClear(selectedDatabase); + selectedDatabase = [[NSString alloc] initWithString:targetDatabaseName]; - [databaseDataInstance resetAllData]; + [databaseDataInstance resetAllData]; #ifndef SP_CODA /* update database encoding */ - // Update the stored database encoding, used for views, "default" table encodings, and to allow - // or disallow use of the "View using encoding" menu - [self detectDatabaseEncoding]; + // Update the stored database encoding, used for views, "default" table encodings, and to allow + // or disallow use of the "View using encoding" menu + [self detectDatabaseEncoding]; #endif - - // Set the connection of SPTablesList to reload tables in db - [tablesListInstance setConnection:mySQLConnection]; + + // Set the connection of SPTablesList to reload tables in db + [tablesListInstance setConnection:mySQLConnection]; #ifndef SP_CODA /* update history controller and ui manip */ - // Update the window title - [self updateWindowTitle:self]; + // Update the window title + [self updateWindowTitle:self]; - // Add a history entry - if (!historyStateChanging) { - [spHistoryControllerInstance setModifyingState:NO]; - [spHistoryControllerInstance updateHistoryEntries]; - } + // Add a history entry + if (!historyStateChanging) { + [spHistoryControllerInstance setModifyingState:NO]; + [spHistoryControllerInstance updateHistoryEntries]; + } #endif - } + } #ifndef SP_CODA /* update selected table in SPTablesList */ - SPMainQSync(^{ - BOOL focusOnFilter = YES; - if (targetItemName) focusOnFilter = NO; - - // If a the table has changed, update the selection - if (![targetItemName isEqualToString:[self table]] && targetItemName) { - focusOnFilter = ![tablesListInstance selectItemWithName:targetItemName]; - } - - // Ensure the window focus is on the table list or the filter as appropriate - [tablesListInstance setTableListSelectability:YES]; - if (focusOnFilter) { - [tablesListInstance makeTableListFilterHaveFocus]; - } else { - [tablesListInstance makeTableListHaveFocus]; - } - [tablesListInstance setTableListSelectability:NO]; - }); + SPMainQSync(^{ + BOOL focusOnFilter = YES; + if (targetItemName) focusOnFilter = NO; + + // If a the table has changed, update the selection + if (![targetItemName isEqualToString:[self table]] && targetItemName) { + focusOnFilter = ![tablesListInstance selectItemWithName:targetItemName]; + } + + // Ensure the window focus is on the table list or the filter as appropriate + [tablesListInstance setTableListSelectability:YES]; + if (focusOnFilter) { + [tablesListInstance makeTableListFilterHaveFocus]; + } else { + [tablesListInstance makeTableListHaveFocus]; + } + [tablesListInstance setTableListSelectability:NO]; + }); #endif - [self endTask]; + [self endTask]; #ifndef SP_CODA /* triggered commands */ - [self _processDatabaseChangedBundleTriggerActions]; + [self _processDatabaseChangedBundleTriggerActions]; #endif #ifdef SP_CODA /* glue */ - if (delegate && [delegate respondsToSelector:@selector(databaseDidChange:)]) { - [delegate performSelectorOnMainThread:@selector(databaseDidChange:) withObject:self waitUntilDone:NO]; - } + if (delegate && [delegate respondsToSelector:@selector(databaseDidChange:)]) { + [delegate performSelectorOnMainThread:@selector(databaseDidChange:) withObject:self waitUntilDone:NO]; + } #endif - - [taskPool drain]; + } } #ifndef SP_CODA @@ -6793,57 +6785,53 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; */ - (void)_loadTabTask:(NSNumber *)tabViewItemIndexNumber { - NSAutoreleasePool *tabLoadPool = [[NSAutoreleasePool alloc] init]; - - // If anything other than a single table or view is selected, don't proceed. - if (![self table] || ([tablesListInstance tableType] != SPTableTypeTable && [tablesListInstance tableType] != SPTableTypeView)) - { - [self endTask]; - [tabLoadPool drain]; - return; - } + @autoreleasepool { + // If anything other than a single table or view is selected, don't proceed. + if (![self table] || ([tablesListInstance tableType] != SPTableTypeTable && [tablesListInstance tableType] != SPTableTypeView)) { + [self endTask]; + return; + } - // Get the tab view index and ensure the associated view is loaded - SPTableViewType selectedTabViewIndex = [tabViewItemIndexNumber integerValue]; + // Get the tab view index and ensure the associated view is loaded + SPTableViewType selectedTabViewIndex = (SPTableViewType)[tabViewItemIndexNumber integerValue]; - switch (selectedTabViewIndex) { - case SPTableViewStructure: - if (!structureLoaded) { - [tableSourceInstance loadTable:selectedTableName]; - structureLoaded = YES; - } - break; - case SPTableViewContent: - if (!contentLoaded) { - [tableContentInstance loadTable:selectedTableName]; - contentLoaded = YES; - } - break; + switch (selectedTabViewIndex) { + case SPTableViewStructure: + if (!structureLoaded) { + [tableSourceInstance loadTable:selectedTableName]; + structureLoaded = YES; + } + break; + case SPTableViewContent: + if (!contentLoaded) { + [tableContentInstance loadTable:selectedTableName]; + contentLoaded = YES; + } + break; #ifndef SP_CODA /* case SPTableViewStatus: case SPTableViewTriggers: */ - case SPTableViewStatus: - if (!statusLoaded) { - [[extendedTableInfoInstance onMainThread] loadTable:selectedTableName]; - statusLoaded = YES; - } - break; - case SPTableViewTriggers: - if (!triggersLoaded) { - [[tableTriggersInstance onMainThread] loadTriggers]; - triggersLoaded = YES; - } - break; - case SPTableViewRelations: - if (!relationsLoaded) { - [[tableRelationsInstance onMainThread] refreshRelations:self]; - relationsLoaded = YES; - } - break; + case SPTableViewStatus: + if (!statusLoaded) { + [[extendedTableInfoInstance onMainThread] loadTable:selectedTableName]; + statusLoaded = YES; + } + break; + case SPTableViewTriggers: + if (!triggersLoaded) { + [[tableTriggersInstance onMainThread] loadTriggers]; + triggersLoaded = YES; + } + break; + case SPTableViewRelations: + if (!relationsLoaded) { + [[tableRelationsInstance onMainThread] refreshRelations:self]; + relationsLoaded = YES; + } + break; #endif - } - - [self endTask]; + } - [tabLoadPool drain]; + [self endTask]; + } } @@ -6852,169 +6840,169 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; */ - (void)_loadTableTask { - NSAutoreleasePool *loadPool = [[NSAutoreleasePool alloc] init]; - NSString *tableEncoding = nil; + @autoreleasepool { + NSString *tableEncoding = nil; #ifndef SP_CODA /* Update the window title */ - // Update the window title - [self updateWindowTitle:self]; + // Update the window title + [self updateWindowTitle:self]; #endif - // Reset table information caches and mark that all loaded views require their data reloading - [tableDataInstance resetAllData]; + // Reset table information caches and mark that all loaded views require their data reloading + [tableDataInstance resetAllData]; - structureLoaded = NO; - contentLoaded = NO; - statusLoaded = NO; - triggersLoaded = NO; - relationsLoaded = NO; + structureLoaded = NO; + contentLoaded = NO; + statusLoaded = NO; + triggersLoaded = NO; + relationsLoaded = NO; - // Ensure status and details are fetched using UTF8 - NSString *previousEncoding = [mySQLConnection encoding]; - BOOL changeEncoding = ![previousEncoding isEqualToString:@"utf8"]; + // Ensure status and details are fetched using UTF8 + NSString *previousEncoding = [mySQLConnection encoding]; + BOOL changeEncoding = ![previousEncoding isEqualToString:@"utf8"]; - if (changeEncoding) { - [mySQLConnection storeEncodingForRestoration]; - [mySQLConnection setEncoding:@"utf8"]; - } + if (changeEncoding) { + [mySQLConnection storeEncodingForRestoration]; + [mySQLConnection setEncoding:@"utf8"]; + } - // Cache status information on the working thread - [tableDataInstance updateStatusInformationForCurrentTable]; + // Cache status information on the working thread + [tableDataInstance updateStatusInformationForCurrentTable]; - // Check the current encoding against the table encoding to see whether - // an encoding change and reset is required. This also caches table information on - // the working thread. - if( selectedTableType == SPTableTypeView || selectedTableType == SPTableTypeTable) { + // Check the current encoding against the table encoding to see whether + // an encoding change and reset is required. This also caches table information on + // the working thread. + if( selectedTableType == SPTableTypeView || selectedTableType == SPTableTypeTable) { - // tableEncoding == nil indicates that there was an error while retrieving table data - tableEncoding = [tableDataInstance tableEncoding]; + // tableEncoding == nil indicates that there was an error while retrieving table data + tableEncoding = [tableDataInstance tableEncoding]; - // If encoding is set to Autodetect, update the connection character set encoding - // based on the newly selected table's encoding - but only if it differs from the current encoding. - if ([[[NSUserDefaults standardUserDefaults] objectForKey:SPDefaultEncoding] intValue] == SPEncodingAutodetect) { - if (tableEncoding != nil && ![tableEncoding isEqualToString:previousEncoding]) { - [self setConnectionEncoding:tableEncoding reloadingViews:NO]; - changeEncoding = NO; + // If encoding is set to Autodetect, update the connection character set encoding + // based on the newly selected table's encoding - but only if it differs from the current encoding. + if ([[[NSUserDefaults standardUserDefaults] objectForKey:SPDefaultEncoding] intValue] == SPEncodingAutodetect) { + if (tableEncoding != nil && ![tableEncoding isEqualToString:previousEncoding]) { + [self setConnectionEncoding:tableEncoding reloadingViews:NO]; + changeEncoding = NO; + } } } - } - if (changeEncoding) [mySQLConnection restoreStoredEncoding]; + if (changeEncoding) [mySQLConnection restoreStoredEncoding]; - // Notify listeners of the table change now that the state is fully set up. - [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:SPTableChangedNotification object:self]; + // Notify listeners of the table change now that the state is fully set up. + [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:SPTableChangedNotification object:self]; #ifndef SP_CODA /* [spHistoryControllerInstance restoreViewStates] */ - - // Restore view states as appropriate - [spHistoryControllerInstance restoreViewStates]; + // Restore view states as appropriate + [spHistoryControllerInstance restoreViewStates]; #endif - // Load the currently selected view if looking at a table or view - if (tableEncoding && (selectedTableType == SPTableTypeView || selectedTableType == SPTableTypeTable)) - { + // Load the currently selected view if looking at a table or view + if (tableEncoding && (selectedTableType == SPTableTypeView || selectedTableType == SPTableTypeTable)) + { #ifndef SP_CODA /* load everything */ - NSInteger selectedTabViewIndex = [[self onMainThread] currentlySelectedView]; + NSInteger selectedTabViewIndex = [[self onMainThread] currentlySelectedView]; - switch (selectedTabViewIndex) { - case SPTableViewStructure: + switch (selectedTabViewIndex) { + case SPTableViewStructure: #endif - [tableSourceInstance loadTable:selectedTableName]; - structureLoaded = YES; + [tableSourceInstance loadTable:selectedTableName]; + structureLoaded = YES; #ifndef SP_CODA /* load everything */ - break; - case SPTableViewContent: + break; + case SPTableViewContent: #endif - [tableContentInstance loadTable:selectedTableName]; - contentLoaded = YES; + [tableContentInstance loadTable:selectedTableName]; + contentLoaded = YES; #ifndef SP_CODA /* load everything */ - break; - case SPTableViewStatus: - [[extendedTableInfoInstance onMainThread] loadTable:selectedTableName]; - statusLoaded = YES; - break; - case SPTableViewTriggers: - [[tableTriggersInstance onMainThread] loadTriggers]; - triggersLoaded = YES; - break; - case SPTableViewRelations: - [[tableRelationsInstance onMainThread] refreshRelations:self]; - relationsLoaded = YES; - break; - } + break; + case SPTableViewStatus: + [[extendedTableInfoInstance onMainThread] loadTable:selectedTableName]; + statusLoaded = YES; + break; + case SPTableViewTriggers: + [[tableTriggersInstance onMainThread] loadTriggers]; + triggersLoaded = YES; + break; + case SPTableViewRelations: + [[tableRelationsInstance onMainThread] refreshRelations:self]; + relationsLoaded = YES; + break; + } #endif - } + } - // Clear any views which haven't been loaded as they weren't visible. Note - // that this should be done after reloading visible views, instead of clearing all - // views, to reduce UI operations and avoid resetting state unnecessarily. - // Some views (eg TableRelations) make use of the SPTableChangedNotification and - // so don't require manual clearing. - if (!structureLoaded) [tableSourceInstance loadTable:nil]; - if (!contentLoaded) [tableContentInstance loadTable:nil]; - if (!statusLoaded) [[extendedTableInfoInstance onMainThread] loadTable:nil]; - if (!triggersLoaded) [[tableTriggersInstance onMainThread] resetInterface]; + // Clear any views which haven't been loaded as they weren't visible. Note + // that this should be done after reloading visible views, instead of clearing all + // views, to reduce UI operations and avoid resetting state unnecessarily. + // Some views (eg TableRelations) make use of the SPTableChangedNotification and + // so don't require manual clearing. + if (!structureLoaded) [tableSourceInstance loadTable:nil]; + if (!contentLoaded) [tableContentInstance loadTable:nil]; + if (!statusLoaded) [[extendedTableInfoInstance onMainThread] loadTable:nil]; + if (!triggersLoaded) [[tableTriggersInstance onMainThread] resetInterface]; - // If the table row counts an inaccurate and require updating, trigger an update - no - // action will be performed if not necessary - [tableDataInstance updateAccurateNumberOfRowsForCurrentTableForcingUpdate:NO]; + // If the table row counts an inaccurate and require updating, trigger an update - no + // action will be performed if not necessary + [tableDataInstance updateAccurateNumberOfRowsForCurrentTableForcingUpdate:NO]; #ifndef SP_CODA /* show Create Table syntax */ - // Update the "Show Create Syntax" window if it's already opened - // according to the selected table/view/proc/func - if ([[[self onMainThread] getCreateTableSyntaxWindow] isVisible]) { - [[self onMainThread] showCreateTableSyntax:self]; - } + SPMainQSync(^{ + // Update the "Show Create Syntax" window if it's already opened + // according to the selected table/view/proc/func + if ([[self getCreateTableSyntaxWindow] isVisible]) { + [self showCreateTableSyntax:self]; + } + }); - // Add a history entry - [spHistoryControllerInstance updateHistoryEntries]; + // Add a history entry + [spHistoryControllerInstance updateHistoryEntries]; #endif - // Empty the loading pool and exit the thread - [self endTask]; + // Empty the loading pool and exit the thread + [self endTask]; #ifndef SP_CODA /* triggered commands */ - NSArray *triggeredCommands = [SPAppDelegate bundleCommandsForTrigger:SPBundleTriggerActionTableChanged]; - - for(NSString* cmdPath in triggeredCommands) - { - NSArray *data = [cmdPath componentsSeparatedByString:@"|"]; - NSMenuItem *aMenuItem = [[[NSMenuItem alloc] init] autorelease]; - [aMenuItem setTag:0]; - [aMenuItem setToolTip:[data objectAtIndex:0]]; + NSArray *triggeredCommands = [SPAppDelegate bundleCommandsForTrigger:SPBundleTriggerActionTableChanged]; - // For HTML output check if corresponding window already exists - BOOL stopTrigger = NO; - if([(NSString*)[data objectAtIndex:2] length]) { - BOOL correspondingWindowFound = NO; - NSString *uuid = [data objectAtIndex:2]; - for(id win in [NSApp windows]) { - if([[[[win delegate] class] description] isEqualToString:@"SPBundleHTMLOutputController"]) { - if([[[win delegate] windowUUID] isEqualToString:uuid]) { - correspondingWindowFound = YES; - break; + for(NSString* cmdPath in triggeredCommands) + { + NSArray *data = [cmdPath componentsSeparatedByString:@"|"]; + NSMenuItem *aMenuItem = [[[NSMenuItem alloc] init] autorelease]; + [aMenuItem setTag:0]; + [aMenuItem setToolTip:[data objectAtIndex:0]]; + + // For HTML output check if corresponding window already exists + BOOL stopTrigger = NO; + if([(NSString*)[data objectAtIndex:2] length]) { + BOOL correspondingWindowFound = NO; + NSString *uuid = [data objectAtIndex:2]; + for(id win in [NSApp windows]) { + if([[[[win delegate] class] description] isEqualToString:@"SPBundleHTMLOutputController"]) { + if([[[win delegate] windowUUID] isEqualToString:uuid]) { + correspondingWindowFound = YES; + break; + } } } + if(!correspondingWindowFound) stopTrigger = YES; } - if(!correspondingWindowFound) stopTrigger = YES; - } - if(!stopTrigger) { - id firstResponder = [[NSApp keyWindow] firstResponder]; - if([[data objectAtIndex:1] isEqualToString:SPBundleScopeGeneral]) { - [[SPAppDelegate onMainThread] executeBundleItemForApp:aMenuItem]; - } - else if([[data objectAtIndex:1] isEqualToString:SPBundleScopeDataTable]) { - if([[[firstResponder class] description] isEqualToString:@"SPCopyTable"]) - [[firstResponder onMainThread] executeBundleItemForDataTable:aMenuItem]; - } - else if([[data objectAtIndex:1] isEqualToString:SPBundleScopeInputField]) { - if([firstResponder isKindOfClass:[NSTextView class]]) - [[firstResponder onMainThread] executeBundleItemForInputField:aMenuItem]; + if(!stopTrigger) { + id firstResponder = [[NSApp keyWindow] firstResponder]; + if([[data objectAtIndex:1] isEqualToString:SPBundleScopeGeneral]) { + [[SPAppDelegate onMainThread] executeBundleItemForApp:aMenuItem]; + } + else if([[data objectAtIndex:1] isEqualToString:SPBundleScopeDataTable]) { + if([[[firstResponder class] description] isEqualToString:@"SPCopyTable"]) + [[firstResponder onMainThread] executeBundleItemForDataTable:aMenuItem]; + } + else if([[data objectAtIndex:1] isEqualToString:SPBundleScopeInputField]) { + if([firstResponder isKindOfClass:[NSTextView class]]) + [[firstResponder onMainThread] executeBundleItemForInputField:aMenuItem]; + } } } - } #endif - - [loadPool drain]; + } } #pragma mark - SPMySQLConnection delegate methods @@ -7329,149 +7317,144 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; */ - (void)generateHTMLForPrinting { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - NSMutableDictionary *connection = [NSMutableDictionary dictionary]; - NSMutableDictionary *printData = [NSMutableDictionary dictionary]; + @autoreleasepool { + NSMutableDictionary *connection = [NSMutableDictionary dictionary]; + NSMutableDictionary *printData = [NSMutableDictionary dictionary]; - SPMainQSync(^{ - [connection setDictionary:[self connectionInformation]]; - [printData setObject:[self columnNames] forKey:@"columns"]; - SPTableViewType view = [self currentlySelectedView]; + SPMainQSync(^{ + [connection setDictionary:[self connectionInformation]]; + [printData setObject:[self columnNames] forKey:@"columns"]; + SPTableViewType view = [self currentlySelectedView]; - NSString *heading = @""; + NSString *heading = @""; - // Table source view - if (view == SPTableViewStructure) { + // Table source view + if (view == SPTableViewStructure) { - NSDictionary *tableSource = [tableSourceInstance tableSourceForPrinting]; + NSDictionary *tableSource = [tableSourceInstance tableSourceForPrinting]; - NSInteger tableType = [tablesListInstance tableType]; + NSInteger tableType = [tablesListInstance tableType]; - switch (tableType) { - case SPTableTypeTable: - heading = NSLocalizedString(@"Table Structure", @"table structure print heading"); - break; - case SPTableTypeView: - heading = NSLocalizedString(@"View Structure", @"view structure print heading"); - break; - } + switch (tableType) { + case SPTableTypeTable: + heading = NSLocalizedString(@"Table Structure", @"table structure print heading"); + break; + case SPTableTypeView: + heading = NSLocalizedString(@"View Structure", @"view structure print heading"); + break; + } - NSArray *rows = [[NSArray alloc] initWithArray: + NSArray *rows = [[NSArray alloc] initWithArray: [[tableSource objectForKey:@"structure"] objectsAtIndexes: - [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, [[tableSource objectForKey:@"structure"] count] - 1)]] - ]; + [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, [[tableSource objectForKey:@"structure"] count] - 1)]] + ]; - NSArray *indexes = [[NSArray alloc] initWithArray: + NSArray *indexes = [[NSArray alloc] initWithArray: [[tableSource objectForKey:@"indexes"] objectsAtIndexes: - [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, [[tableSource objectForKey:@"indexes"] count] - 1)]] - ]; + [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, [[tableSource objectForKey:@"indexes"] count] - 1)]] + ]; - NSArray *indexColumns = [[tableSource objectForKey:@"indexes"] objectAtIndex:0]; + NSArray *indexColumns = [[tableSource objectForKey:@"indexes"] objectAtIndex:0]; - [printData setObject:rows forKey:@"rows"]; - [printData setObject:indexes forKey:@"indexes"]; - [printData setObject:indexColumns forKey:@"indexColumns"]; + [printData setObject:rows forKey:@"rows"]; + [printData setObject:indexes forKey:@"indexes"]; + [printData setObject:indexColumns forKey:@"indexColumns"]; - if ([indexes count]) [printData setObject:@1 forKey:@"hasIndexes"]; + if ([indexes count]) [printData setObject:@1 forKey:@"hasIndexes"]; - [rows release]; - [indexes release]; - } - // Table content view - else if (view == SPTableViewContent) { + [rows release]; + [indexes release]; + } + // Table content view + else if (view == SPTableViewContent) { - NSArray *data = [tableContentInstance currentDataResultWithNULLs:NO hideBLOBs:YES]; + NSArray *data = [tableContentInstance currentDataResultWithNULLs:NO hideBLOBs:YES]; - heading = NSLocalizedString(@"Table Content", @"table content print heading"); + heading = NSLocalizedString(@"Table Content", @"table content print heading"); - NSArray *rows = [[NSArray alloc] initWithArray: + NSArray *rows = [[NSArray alloc] initWithArray: [data objectsAtIndexes: - [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, [data count] - 1)]] - ]; + [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, [data count] - 1)]] + ]; - [printData setObject:rows forKey:@"rows"]; - [connection setValue:[tableContentInstance usedQuery] forKey:@"query"]; + [printData setObject:rows forKey:@"rows"]; + [connection setValue:[tableContentInstance usedQuery] forKey:@"query"]; - [rows release]; - } - // Custom query view - else if (view == SPTableViewCustomQuery) { + [rows release]; + } + // Custom query view + else if (view == SPTableViewCustomQuery) { - NSArray *data = [customQueryInstance currentResult]; + NSArray *data = [customQueryInstance currentResult]; - heading = NSLocalizedString(@"Query Result", @"query result print heading"); + heading = NSLocalizedString(@"Query Result", @"query result print heading"); - NSArray *rows = [[NSArray alloc] initWithArray: + NSArray *rows = [[NSArray alloc] initWithArray: [data objectsAtIndexes: - [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, [data count] - 1)]] - ]; + [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, [data count] - 1)]] + ]; - [printData setObject:rows forKey:@"rows"]; - [connection setValue:[customQueryInstance usedQuery] forKey:@"query"]; + [printData setObject:rows forKey:@"rows"]; + [connection setValue:[customQueryInstance usedQuery] forKey:@"query"]; - [rows release]; - } - // Table relations view - else if (view == SPTableViewRelations) { + [rows release]; + } + // Table relations view + else if (view == SPTableViewRelations) { - NSArray *data = [tableRelationsInstance relationDataForPrinting]; + NSArray *data = [tableRelationsInstance relationDataForPrinting]; - heading = NSLocalizedString(@"Table Relations", @"toolbar item label for switching to the Table Relations tab"); + heading = NSLocalizedString(@"Table Relations", @"toolbar item label for switching to the Table Relations tab"); - NSArray *rows = [[NSArray alloc] initWithArray: + NSArray *rows = [[NSArray alloc] initWithArray: [data objectsAtIndexes: - [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, ([data count] - 1))]] - ]; + [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, ([data count] - 1))]] + ]; - [printData setObject:rows forKey:@"rows"]; + [printData setObject:rows forKey:@"rows"]; - [rows release]; - } - // Table triggers view - else if (view == SPTableViewTriggers) { + [rows release]; + } + // Table triggers view + else if (view == SPTableViewTriggers) { - NSArray *data = [tableTriggersInstance triggerDataForPrinting]; + NSArray *data = [tableTriggersInstance triggerDataForPrinting]; - heading = NSLocalizedString(@"Table Triggers", @"toolbar item label for switching to the Table Triggers tab"); + heading = NSLocalizedString(@"Table Triggers", @"toolbar item label for switching to the Table Triggers tab"); - NSArray *rows = [[NSArray alloc] initWithArray: + NSArray *rows = [[NSArray alloc] initWithArray: [data objectsAtIndexes: - [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, ([data count] - 1))]] - ]; + [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, ([data count] - 1))]] + ]; - [printData setObject:rows forKey:@"rows"]; + [printData setObject:rows forKey:@"rows"]; - [rows release]; - } + [rows release]; + } - [printData setObject:heading forKey:@"heading"]; - }); + [printData setObject:heading forKey:@"heading"]; + }); - // Set up template engine with your chosen matcher - MGTemplateEngine *engine = [MGTemplateEngine templateEngine]; + // Set up template engine with your chosen matcher + MGTemplateEngine *engine = [MGTemplateEngine templateEngine]; - [engine setMatcher:[ICUTemplateMatcher matcherWithTemplateEngine:engine]]; + [engine setMatcher:[ICUTemplateMatcher matcherWithTemplateEngine:engine]]; - [engine setObject:connection forKey:@"c"]; + [engine setObject:connection forKey:@"c"]; - [printData setObject:([prefs boolForKey:SPUseMonospacedFonts]) ? SPDefaultMonospacedFontName : @"Lucida Grande" forKey:@"font"]; - [printData setObject:([prefs boolForKey:SPDisplayTableViewVerticalGridlines]) ? @"1px solid #CCCCCC" : @"none" forKey:@"gridlines"]; + [printData setObject:([prefs boolForKey:SPUseMonospacedFonts]) ? SPDefaultMonospacedFontName : @"Lucida Grande" forKey:@"font"]; + [printData setObject:([prefs boolForKey:SPDisplayTableViewVerticalGridlines]) ? @"1px solid #CCCCCC" : @"none" forKey:@"gridlines"]; - NSString *HTMLString = [engine processTemplateInFileAtPath:[[NSBundle mainBundle] pathForResource:SPHTMLPrintTemplate ofType:@"html"] withVariables:printData]; + NSString *HTMLString = [engine processTemplateInFileAtPath:[[NSBundle mainBundle] pathForResource:SPHTMLPrintTemplate ofType:@"html"] withVariables:printData]; - // Check if the operation has been cancelled - if ((printThread != nil) && (![NSThread isMainThread]) && ([printThread isCancelled])) { - [self endTask]; - [pool drain]; + // Check if the operation has been cancelled + if ((printThread != nil) && (![NSThread isMainThread]) && ([printThread isCancelled])) { + [self endTask]; + return; + } - [NSThread exit]; - return; + [self performSelectorOnMainThread:@selector(loadPrintWebViewWithHTMLString:) withObject:HTMLString waitUntilDone:NO]; } - - [self performSelectorOnMainThread:@selector(loadPrintWebViewWithHTMLString:) withObject:HTMLString waitUntilDone:NO]; - - [pool drain]; } /** @@ -7479,38 +7462,33 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; */ - (void)generateTableInfoHTMLForPrinting { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - // Set up template engine with your chosen matcher - MGTemplateEngine *engine = [MGTemplateEngine templateEngine]; + @autoreleasepool { + // Set up template engine with your chosen matcher + MGTemplateEngine *engine = [MGTemplateEngine templateEngine]; - [engine setMatcher:[ICUTemplateMatcher matcherWithTemplateEngine:engine]]; + [engine setMatcher:[ICUTemplateMatcher matcherWithTemplateEngine:engine]]; - NSMutableDictionary *connection = [self connectionInformation]; - NSMutableDictionary *printData = [NSMutableDictionary dictionary]; + NSMutableDictionary *connection = [self connectionInformation]; + NSMutableDictionary *printData = [NSMutableDictionary dictionary]; - NSString *heading = NSLocalizedString(@"Table Information", @"table information print heading"); + NSString *heading = NSLocalizedString(@"Table Information", @"table information print heading"); - [engine setObject:connection forKey:@"c"]; - [engine setObject:[[extendedTableInfoInstance onMainThread] tableInformationForPrinting] forKey:@"i"]; + [engine setObject:connection forKey:@"c"]; + [engine setObject:[[extendedTableInfoInstance onMainThread] tableInformationForPrinting] forKey:@"i"]; - [printData setObject:heading forKey:@"heading"]; - [printData setObject:[[NSUnarchiver unarchiveObjectWithData:[prefs objectForKey:SPCustomQueryEditorFont]] fontName] forKey:@"font"]; + [printData setObject:heading forKey:@"heading"]; + [printData setObject:[[NSUnarchiver unarchiveObjectWithData:[prefs objectForKey:SPCustomQueryEditorFont]] fontName] forKey:@"font"]; - NSString *HTMLString = [engine processTemplateInFileAtPath:[[NSBundle mainBundle] pathForResource:SPHTMLTableInfoPrintTemplate ofType:@"html"] withVariables:printData]; + NSString *HTMLString = [engine processTemplateInFileAtPath:[[NSBundle mainBundle] pathForResource:SPHTMLTableInfoPrintTemplate ofType:@"html"] withVariables:printData]; - // Check if the operation has been cancelled - if ((printThread != nil) && (![NSThread isMainThread]) && ([printThread isCancelled])) { - [self endTask]; - [pool drain]; + // Check if the operation has been cancelled + if ((printThread != nil) && (![NSThread isMainThread]) && ([printThread isCancelled])) { + [self endTask]; + return; + } - [NSThread exit]; - return; + [self performSelectorOnMainThread:@selector(loadPrintWebViewWithHTMLString:) withObject:HTMLString waitUntilDone:NO]; } - - [self performSelectorOnMainThread:@selector(loadPrintWebViewWithHTMLString:) withObject:HTMLString waitUntilDone:NO]; - - [pool drain]; } /** diff --git a/Source/SPDatabaseStructure.m b/Source/SPDatabaseStructure.m index c2b0bb59..97d5f172 100644 --- a/Source/SPDatabaseStructure.m +++ b/Source/SPDatabaseStructure.m @@ -149,259 +149,260 @@ */ - (void)queryDbStructureWithUserInfo:(NSDictionary *)userInfo { - NSAutoreleasePool *queryPool = [[NSAutoreleasePool alloc] init]; - BOOL structureWasUpdated = NO; - - [self _addToListAndWaitForFrontCancellingOtherThreads:[[userInfo objectForKey:@"cancelQuerying"] boolValue]]; - if([[NSThread currentThread] isCancelled]) goto cleanup_thread_and_pool; - - // This thread is now first on the stack, and about to process the structure. - [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureIsUpdating" object:self]; - - NSString *connectionID = ([delegate respondsToSelector:@selector(connectionID)])? [NSString stringWithString:[delegate connectionID]] : @"_"; - - // Re-init with already cached data from navigator controller - NSMutableDictionary *queriedStructure = [NSMutableDictionary dictionary]; - NSDictionary *dbstructure = [delegate getDbStructure]; - if (dbstructure) [queriedStructure setDictionary:[NSMutableDictionary dictionaryWithDictionary:dbstructure]]; - - NSMutableArray *queriedStructureKeys = [NSMutableArray array]; - NSArray *dbStructureKeys = [delegate allSchemaKeys]; - if (dbStructureKeys) [queriedStructureKeys setArray:dbStructureKeys]; - - // Retrieve all the databases known of by the delegate - NSMutableArray *connectionDatabases = [NSMutableArray array]; - [connectionDatabases addObjectsFromArray:[delegate allSystemDatabaseNames]]; - [connectionDatabases addObjectsFromArray:[delegate allDatabaseNames]]; - - // Add all known databases coming from connection if they aren't parsed yet - for (id db in connectionDatabases) { - NSString *dbid = [NSString stringWithFormat:@"%@%@%@", connectionID, SPUniqueSchemaDelimiter, db]; - if(![queriedStructure objectForKey:dbid]) { - structureWasUpdated = YES; - [queriedStructure setObject:db forKey:dbid]; - [queriedStructureKeys addObject:dbid]; + @autoreleasepool { + BOOL structureWasUpdated = NO; + + [self _addToListAndWaitForFrontCancellingOtherThreads:[[userInfo objectForKey:@"cancelQuerying"] boolValue]]; + if([[NSThread currentThread] isCancelled]) goto cleanup_thread_and_pool; + + // This thread is now first on the stack, and about to process the structure. + [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureIsUpdating" object:self]; + + NSString *connectionID = ([delegate respondsToSelector:@selector(connectionID)])? [NSString stringWithString:[delegate connectionID]] : @"_"; + + // Re-init with already cached data from navigator controller + NSMutableDictionary *queriedStructure = [NSMutableDictionary dictionary]; + NSDictionary *dbstructure = [delegate getDbStructure]; + if (dbstructure) [queriedStructure setDictionary:[NSMutableDictionary dictionaryWithDictionary:dbstructure]]; + + NSMutableArray *queriedStructureKeys = [NSMutableArray array]; + NSArray *dbStructureKeys = [delegate allSchemaKeys]; + if (dbStructureKeys) [queriedStructureKeys setArray:dbStructureKeys]; + + // Retrieve all the databases known of by the delegate + NSMutableArray *connectionDatabases = [NSMutableArray array]; + [connectionDatabases addObjectsFromArray:[delegate allSystemDatabaseNames]]; + [connectionDatabases addObjectsFromArray:[delegate allDatabaseNames]]; + + // Add all known databases coming from connection if they aren't parsed yet + for (id db in connectionDatabases) { + NSString *dbid = [NSString stringWithFormat:@"%@%@%@", connectionID, SPUniqueSchemaDelimiter, db]; + if(![queriedStructure objectForKey:dbid]) { + structureWasUpdated = YES; + [queriedStructure setObject:db forKey:dbid]; + [queriedStructureKeys addObject:dbid]; + } } - } - // Check the existing databases in the 'structure' and 'allKeysOfDbStructure' stores, - // and remove any that are no longer found in the connectionDatabases list (indicating deletion). - // Iterate through extracted keys to avoid <NSCFDictionary> mutation while being enumerated. - NSArray *keys = [queriedStructure allKeys]; - for(id key in keys) { - NSString *db = [[key componentsSeparatedByString:SPUniqueSchemaDelimiter] objectAtIndex:1]; - if(![connectionDatabases containsObject:db]) { - structureWasUpdated = YES; - [queriedStructure removeObjectForKey:key]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT SELF BEGINSWITH %@", [NSString stringWithFormat:@"%@%@", key, SPUniqueSchemaDelimiter]]; - [queriedStructureKeys filterUsingPredicate:predicate]; - [queriedStructureKeys removeObject:key]; + // Check the existing databases in the 'structure' and 'allKeysOfDbStructure' stores, + // and remove any that are no longer found in the connectionDatabases list (indicating deletion). + // Iterate through extracted keys to avoid <NSCFDictionary> mutation while being enumerated. + NSArray *keys = [queriedStructure allKeys]; + for(id key in keys) { + NSString *db = [[key componentsSeparatedByString:SPUniqueSchemaDelimiter] objectAtIndex:1]; + if(![connectionDatabases containsObject:db]) { + structureWasUpdated = YES; + [queriedStructure removeObjectForKey:key]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT SELF BEGINSWITH %@", [NSString stringWithFormat:@"%@%@", key, SPUniqueSchemaDelimiter]]; + [queriedStructureKeys filterUsingPredicate:predicate]; + [queriedStructureKeys removeObject:key]; + } } - } - NSString *currentDatabase = ([delegate respondsToSelector:@selector(database)])? [delegate database] : nil; + NSString *currentDatabase = ([delegate respondsToSelector:@selector(database)])? [delegate database] : nil; - // Determine whether the database details need to be queried. - BOOL shouldQueryStructure = YES; - NSString *db_id = nil; + // Determine whether the database details need to be queried. + BOOL shouldQueryStructure = YES; + NSString *db_id = nil; - // If no database is selected, no need to check further - if(![currentDatabase length]) { - shouldQueryStructure = NO; - } - // Otherwise, build up the schema key for the database to be retrieved. - else { - db_id = [NSString stringWithFormat:@"%@%@%@", connectionID, SPUniqueSchemaDelimiter, currentDatabase]; - - // Check to see if a cache already exists for the database. - if ([[queriedStructure objectForKey:db_id] isKindOfClass:[NSDictionary class]]) { - - // The cache is available. If the `mysql` or `information_schema` databases are being queried, - // never requery as their structure will never change. - // 5.5.3+ also has performance_schema meta database - if ([currentDatabase isInArray:@[@"mysql",@"information_schema",@"performance_schema"]]) { - shouldQueryStructure = NO; - } - // Otherwise, if the forceUpdate flag wasn't supplied or evaluates to false, also don't update. - else if (![[userInfo objectForKey:@"forceUpdate"] boolValue]) { - shouldQueryStructure = NO; + // If no database is selected, no need to check further + if(![currentDatabase length]) { + shouldQueryStructure = NO; + } + // Otherwise, build up the schema key for the database to be retrieved. + else { + db_id = [NSString stringWithFormat:@"%@%@%@", connectionID, SPUniqueSchemaDelimiter, currentDatabase]; + + // Check to see if a cache already exists for the database. + if ([[queriedStructure objectForKey:db_id] isKindOfClass:[NSDictionary class]]) { + + // The cache is available. If the `mysql` or `information_schema` databases are being queried, + // never requery as their structure will never change. + // 5.5.3+ also has performance_schema meta database + if ([currentDatabase isInArray:@[@"mysql",@"information_schema",@"performance_schema"]]) { + shouldQueryStructure = NO; + } + // Otherwise, if the forceUpdate flag wasn't supplied or evaluates to false, also don't update. + else if (![[userInfo objectForKey:@"forceUpdate"] boolValue]) { + shouldQueryStructure = NO; + } } } - } - // If it has been determined that no new structure needs to be retrieved, clean up and return. - if (!shouldQueryStructure) { - goto update_globals_and_cleanup; - } + // If it has been determined that no new structure needs to be retrieved, clean up and return. + if (!shouldQueryStructure) { + goto update_globals_and_cleanup; + } - // Retrieve the tables and views for this database from SPTablesList - NSMutableArray *tablesAndViews = [NSMutableArray array]; - for (id aTable in [[delegate valueForKeyPath:@"tablesListInstance"] allTableNames]) { - NSDictionary *aTableDict = [NSDictionary dictionaryWithObjectsAndKeys: - aTable, @"name", - @"0", @"type", - nil]; - [tablesAndViews addObject:aTableDict]; - } - for (id aView in [[delegate valueForKeyPath:@"tablesListInstance"] allViewNames]) { - NSDictionary *aViewDict = [NSDictionary dictionaryWithObjectsAndKeys: - aView, @"name", - @"1", @"type", - nil]; - [tablesAndViews addObject:aViewDict]; - } + // Retrieve the tables and views for this database from SPTablesList + NSMutableArray *tablesAndViews = [NSMutableArray array]; + for (id aTable in [[delegate valueForKeyPath:@"tablesListInstance"] allTableNames]) { + NSDictionary *aTableDict = [NSDictionary dictionaryWithObjectsAndKeys: + aTable, @"name", + @"0", @"type", + nil]; + [tablesAndViews addObject:aTableDict]; + } + for (id aView in [[delegate valueForKeyPath:@"tablesListInstance"] allViewNames]) { + NSDictionary *aViewDict = [NSDictionary dictionaryWithObjectsAndKeys: + aView, @"name", + @"1", @"type", + nil]; + [tablesAndViews addObject:aViewDict]; + } - // Do not parse more than 2000 tables/views per db - if ([tablesAndViews count] > 2000) { - NSLog(@"%lu items in database %@. Only 2000 items can be parsed. Stopped parsing.", (unsigned long)[tablesAndViews count], currentDatabase); + // Do not parse more than 2000 tables/views per db + if ([tablesAndViews count] > 2000) { + NSLog(@"%lu items in database %@. Only 2000 items can be parsed. Stopped parsing.", (unsigned long)[tablesAndViews count], currentDatabase); - goto cleanup_thread_and_pool; - } + goto cleanup_thread_and_pool; + } #if 0 - // For future usage - currently unused - // If the affected item name and type - for example, table type and table name - were supplied, extract it. - NSString *affectedItem = nil; - NSInteger affectedItemType = -1; - if([userInfo objectForKey:@"affectedItem"]) { - affectedItem = [userInfo objectForKey:@"affectedItem"]; - if([userInfo objectForKey:@"affectedItemType"]) - affectedItemType = [[userInfo objectForKey:@"affectedItemType"] intValue]; - else - affectedItem = nil; - } + // For future usage - currently unused + // If the affected item name and type - for example, table type and table name - were supplied, extract it. + NSString *affectedItem = nil; + NSInteger affectedItemType = -1; + if([userInfo objectForKey:@"affectedItem"]) { + affectedItem = [userInfo objectForKey:@"affectedItem"]; + if([userInfo objectForKey:@"affectedItemType"]) + affectedItemType = [[userInfo objectForKey:@"affectedItemType"] intValue]; + else + affectedItem = nil; + } #endif - // Delete all stored data for the database to be updated, leaving the structure key - [queriedStructure removeObjectForKey:db_id]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT SELF BEGINSWITH %@", [NSString stringWithFormat:@"%@%@", db_id, SPUniqueSchemaDelimiter]]; - [queriedStructureKeys filterUsingPredicate:predicate]; - - // Set up the database as an empty mutable dictionary ready for tables, and store a reference - [queriedStructure setObject:[NSMutableDictionary dictionary] forKey:db_id]; - NSMutableDictionary *databaseStructure = [queriedStructure objectForKey:db_id]; - structureWasUpdated = YES; + // Delete all stored data for the database to be updated, leaving the structure key + [queriedStructure removeObjectForKey:db_id]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT SELF BEGINSWITH %@", [NSString stringWithFormat:@"%@%@", db_id, SPUniqueSchemaDelimiter]]; + [queriedStructureKeys filterUsingPredicate:predicate]; - NSUInteger uniqueCounter = 0; // used to make field data unique - SPMySQLResult *theResult; + // Set up the database as an empty mutable dictionary ready for tables, and store a reference + [queriedStructure setObject:[NSMutableDictionary dictionary] forKey:db_id]; + NSMutableDictionary *databaseStructure = [queriedStructure objectForKey:db_id]; + structureWasUpdated = YES; - // Loop through the known tables and views, retrieving details for each - for (NSDictionary *aTableDict in tablesAndViews) { + NSUInteger uniqueCounter = 0; // used to make field data unique + SPMySQLResult *theResult; - // Extract the name - NSString *aTableName = [aTableDict objectForKey:@"name"]; + // Loop through the known tables and views, retrieving details for each + for (NSDictionary *aTableDict in tablesAndViews) { - if(![aTableName isKindOfClass:[NSString class]] || ![aTableName length]) continue; + // Extract the name + NSString *aTableName = [aTableDict objectForKey:@"name"]; - // check the connection. - // also NO if thread is cancelled which is fine, too (same consequence). - if(![self _checkConnection]) { - goto cleanup_thread_and_pool; - } + if(![aTableName isKindOfClass:[NSString class]] || ![aTableName length]) continue; - // Retrieve the column details - theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW FULL COLUMNS FROM %@ FROM %@", [aTableName backtickQuotedString], [currentDatabase backtickQuotedString]]]; - if (!theResult) { - continue; - } - [theResult setDefaultRowReturnType:SPMySQLResultRowAsArray]; - [theResult setReturnDataAsStrings:YES]; - - // Add a structure key for this table - NSString *table_id = [NSString stringWithFormat:@"%@%@%@", db_id, SPUniqueSchemaDelimiter, aTableName]; - [queriedStructureKeys addObject:table_id]; - - // Add a mutable dictionary to the structure and store a reference - NSMutableDictionary *tableStructure = [NSMutableDictionary dictionary]; - [databaseStructure setObject:tableStructure forKey:table_id]; - - // Loop through the fields, extracting details for each - for (NSArray *row in theResult) { - NSString *field = [row objectAtIndex:0]; - NSString *type = [row objectAtIndex:1]; - NSString *type_display = [type stringByReplacingOccurrencesOfRegex:@"\\(.*?,.*?\\)" withString:@"(…)"]; - NSString *collation = [row objectAtIndex:2]; - NSString *isnull = [row objectAtIndex:3]; - NSString *key = [row objectAtIndex:4]; - NSString *def = [row objectAtIndex:5]; - NSString *extra = [row objectAtIndex:6]; - NSString *priv = @""; - NSString *comment = @""; - if ([row count] > 7) priv = [row objectAtIndex:7]; - if ([row count] > 8) comment = [row objectAtIndex:8]; - - NSString *charset = @""; - if (![collation isNSNull]) { - NSArray *a = [collation componentsSeparatedByString:@"_"]; - charset = [a objectAtIndex:0]; + // check the connection. + // also NO if thread is cancelled which is fine, too (same consequence). + if(![self _checkConnection]) { + goto cleanup_thread_and_pool; } - // Add a structure key for this field - NSString *field_id = [NSString stringWithFormat:@"%@%@%@", table_id, SPUniqueSchemaDelimiter, field]; - [queriedStructureKeys addObject:field_id]; + // Retrieve the column details + theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW FULL COLUMNS FROM %@ FROM %@", [aTableName backtickQuotedString], [currentDatabase backtickQuotedString]]]; + if (!theResult) { + continue; + } + [theResult setDefaultRowReturnType:SPMySQLResultRowAsArray]; + [theResult setReturnDataAsStrings:YES]; - [tableStructure setObject:[NSArray arrayWithObjects:type, def, isnull, charset, collation, key, extra, priv, comment, type_display, [NSNumber numberWithUnsignedLongLong:uniqueCounter], nil] forKey:field_id]; - [tableStructure setObject:[aTableDict objectForKey:@"type"] forKey:@" struct_type "]; - uniqueCounter++; - } + // Add a structure key for this table + NSString *table_id = [NSString stringWithFormat:@"%@%@%@", db_id, SPUniqueSchemaDelimiter, aTableName]; + [queriedStructureKeys addObject:table_id]; - // Allow a tiny pause between iterations - usleep(10); - } + // Add a mutable dictionary to the structure and store a reference + NSMutableDictionary *tableStructure = [NSMutableDictionary dictionary]; + [databaseStructure setObject:tableStructure forKey:table_id]; + + // Loop through the fields, extracting details for each + for (NSArray *row in theResult) { + NSString *field = [row objectAtIndex:0]; + NSString *type = [row objectAtIndex:1]; + NSString *type_display = [type stringByReplacingOccurrencesOfRegex:@"\\(.*?,.*?\\)" withString:@"(…)"]; + NSString *collation = [row objectAtIndex:2]; + NSString *isnull = [row objectAtIndex:3]; + NSString *key = [row objectAtIndex:4]; + NSString *def = [row objectAtIndex:5]; + NSString *extra = [row objectAtIndex:6]; + NSString *priv = @""; + NSString *comment = @""; + if ([row count] > 7) priv = [row objectAtIndex:7]; + if ([row count] > 8) comment = [row objectAtIndex:8]; + + NSString *charset = @""; + if (![collation isNSNull]) { + NSArray *a = [collation componentsSeparatedByString:@"_"]; + charset = [a objectAtIndex:0]; + } + + // Add a structure key for this field + NSString *field_id = [NSString stringWithFormat:@"%@%@%@", table_id, SPUniqueSchemaDelimiter, field]; + [queriedStructureKeys addObject:field_id]; + + [tableStructure setObject:[NSArray arrayWithObjects:type, def, isnull, charset, collation, key, extra, priv, comment, type_display, [NSNumber numberWithUnsignedLongLong:uniqueCounter], nil] forKey:field_id]; + [tableStructure setObject:[aTableDict objectForKey:@"type"] forKey:@" struct_type "]; + uniqueCounter++; + } - // If the MySQL version is higher than 5, also retrieve function/procedure details via the information_schema table - if ([mySQLConnection serverMajorVersion] >= 5) { - // check the connection. - // also NO if thread is cancelled which is fine, too (same consequence). - if(![self _checkConnection]) { - goto cleanup_thread_and_pool; + // Allow a tiny pause between iterations + usleep(10); } - // Retrieve the column details (only those we need so we don't fetch the whole function body which might be huge) - theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SELECT SPECIFIC_NAME, ROUTINE_TYPE, DTD_IDENTIFIER, IS_DETERMINISTIC, SQL_DATA_ACCESS, SECURITY_TYPE, DEFINER FROM `information_schema`.`ROUTINES` WHERE `ROUTINE_SCHEMA` = %@", [currentDatabase tickQuotedString]]]; - [theResult setDefaultRowReturnType:SPMySQLResultRowAsArray]; - - // Loop through the rows and extract the function details - for (NSArray *row in theResult) { - NSString *fname = [row objectAtIndex:0]; - NSString *type = ([[row objectAtIndex:1] isEqualToString:@"FUNCTION"]) ? @"3" : @"2"; - NSString *dtd = [row objectAtIndex:2]; - NSString *det = [row objectAtIndex:3]; - NSString *dataaccess = [row objectAtIndex:4]; - NSString *security_type = [row objectAtIndex:5]; - NSString *definer = [row objectAtIndex:6]; - - // Generate "table" and "field" names and add to structure key store - NSString *table_id = [NSString stringWithFormat:@"%@%@%@", db_id, SPUniqueSchemaDelimiter, fname]; - NSString *field_id = [NSString stringWithFormat:@"%@%@%@", table_id, SPUniqueSchemaDelimiter, fname]; - [queriedStructureKeys addObject:table_id]; - [queriedStructureKeys addObject:field_id]; - - // Ensure that a dictionary exists for this "table" name - if(![[queriedStructure valueForKey:db_id] valueForKey:table_id]) - [[queriedStructure valueForKey:db_id] setObject:[NSMutableDictionary dictionary] forKey:table_id]; + // If the MySQL version is higher than 5, also retrieve function/procedure details via the information_schema table + if ([mySQLConnection serverMajorVersion] >= 5) { + // check the connection. + // also NO if thread is cancelled which is fine, too (same consequence). + if(![self _checkConnection]) { + goto cleanup_thread_and_pool; + } - // Add the "field" details - [[[queriedStructure valueForKey:db_id] valueForKey:table_id] setObject: - [NSArray arrayWithObjects:dtd, dataaccess, det, security_type, definer, [NSNumber numberWithUnsignedLongLong:uniqueCounter], nil] forKey:field_id]; - [[[queriedStructure valueForKey:db_id] valueForKey:table_id] setObject:type forKey:@" struct_type "]; - uniqueCounter++; + // Retrieve the column details (only those we need so we don't fetch the whole function body which might be huge) + theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SELECT SPECIFIC_NAME, ROUTINE_TYPE, DTD_IDENTIFIER, IS_DETERMINISTIC, SQL_DATA_ACCESS, SECURITY_TYPE, DEFINER FROM `information_schema`.`ROUTINES` WHERE `ROUTINE_SCHEMA` = %@", [currentDatabase tickQuotedString]]]; + [theResult setDefaultRowReturnType:SPMySQLResultRowAsArray]; + + // Loop through the rows and extract the function details + for (NSArray *row in theResult) { + NSString *fname = [row objectAtIndex:0]; + NSString *type = ([[row objectAtIndex:1] isEqualToString:@"FUNCTION"]) ? @"3" : @"2"; + NSString *dtd = [row objectAtIndex:2]; + NSString *det = [row objectAtIndex:3]; + NSString *dataaccess = [row objectAtIndex:4]; + NSString *security_type = [row objectAtIndex:5]; + NSString *definer = [row objectAtIndex:6]; + + // Generate "table" and "field" names and add to structure key store + NSString *table_id = [NSString stringWithFormat:@"%@%@%@", db_id, SPUniqueSchemaDelimiter, fname]; + NSString *field_id = [NSString stringWithFormat:@"%@%@%@", table_id, SPUniqueSchemaDelimiter, fname]; + [queriedStructureKeys addObject:table_id]; + [queriedStructureKeys addObject:field_id]; + + // Ensure that a dictionary exists for this "table" name + if(![[queriedStructure valueForKey:db_id] valueForKey:table_id]) { + [[queriedStructure valueForKey:db_id] setObject:[NSMutableDictionary dictionary] forKey:table_id]; + } + + // Add the "field" details + [[[queriedStructure valueForKey:db_id] valueForKey:table_id] setObject: + [NSArray arrayWithObjects:dtd, dataaccess, det, security_type, definer, [NSNumber numberWithUnsignedLongLong:uniqueCounter], nil] forKey:field_id]; + [[[queriedStructure valueForKey:db_id] valueForKey:table_id] setObject:type forKey:@" struct_type "]; + uniqueCounter++; + } } - } update_globals_and_cleanup: - // Update the global variables - [self _updateGlobalVariablesWithStructure:queriedStructure keys:queriedStructureKeys]; + // Update the global variables + [self _updateGlobalVariablesWithStructure:queriedStructure keys:queriedStructureKeys]; - if(structureWasUpdated) { - // Notify that the structure querying has been performed - [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureWasUpdated" object:self]; - } + if(structureWasUpdated) { + // Notify that the structure querying has been performed + [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureWasUpdated" object:self]; + } cleanup_thread_and_pool: - // Remove this thread from the processing stack - [self _removeThreadFromList]; - [queryPool release]; + // Remove this thread from the processing stack + [self _removeThreadFromList]; + } } - (BOOL)isQueryingDatabaseStructure @@ -509,29 +510,27 @@ cleanup_thread_and_pool: */ - (void)_cloneConnectionFromConnection:(SPMySQLConnection *)aConnection { - NSAutoreleasePool *connectionPool = [[NSAutoreleasePool alloc] init]; - - pthread_mutex_lock(&connectionCheckLock); + @autoreleasepool { + pthread_mutex_lock(&connectionCheckLock); - // If a connection is already set, ensure it's idle before releasing it - if (mySQLConnection) { - [self _cancelAllThreadsAndWait]; + // If a connection is already set, ensure it's idle before releasing it + if (mySQLConnection) { + [self _cancelAllThreadsAndWait]; - [mySQLConnection autorelease], mySQLConnection = nil; // note: aConnection could be == mySQLConnection - } + [mySQLConnection autorelease], mySQLConnection = nil; // note: aConnection could be == mySQLConnection + } - // Create a copy of the supplied connection - mySQLConnection = [aConnection copy]; + // Create a copy of the supplied connection + mySQLConnection = [aConnection copy]; - // Set the delegate to this instance - [mySQLConnection setDelegate:self]; + // Set the delegate to this instance + [mySQLConnection setDelegate:self]; - // Trigger the connection - [self _ensureConnectionUnsafe]; + // Trigger the connection + [self _ensureConnectionUnsafe]; - pthread_mutex_unlock(&connectionCheckLock); - - [connectionPool drain]; + pthread_mutex_unlock(&connectionCheckLock); + } } /** diff --git a/Source/SPExportController.m b/Source/SPExportController.m index c6e82baa..1ec9557e 100644 --- a/Source/SPExportController.m +++ b/Source/SPExportController.m @@ -1070,66 +1070,64 @@ set_input: */ - (void)_toggleExportButton:(id)uiStateDict { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - BOOL enable = NO; - - BOOL isSQL = (exportType == SPSQLExport); - BOOL isCSV = (exportType == SPCSVExport); - BOOL isXML = (exportType == SPXMLExport); - BOOL isHTML = (exportType == SPHTMLExport); - BOOL isPDF = (exportType == SPPDFExport); - - BOOL structureEnabled = [[uiStateDict objectForKey:SPSQLExportStructureEnabled] boolValue]; - BOOL contentEnabled = [[uiStateDict objectForKey:SPSQLExportContentEnabled] boolValue]; - BOOL dropEnabled = [[uiStateDict objectForKey:SPSQLExportDropEnabled] boolValue]; - - if (isCSV || isXML || isHTML || isPDF || (isSQL && ((!structureEnabled) || (!dropEnabled)))) { - enable = NO; - - // Only enable the button if at least one table is selected - for (NSArray *table in tables) - { - if ([NSArrayObjectAtIndex(table, 2) boolValue]) { - enable = YES; - break; - } - } - } - else if (isSQL) { - - // Disable if all are unchecked - if ((!contentEnabled) && (!structureEnabled) && (!dropEnabled)) { + @autoreleasepool { + BOOL enable = NO; + + BOOL isSQL = (exportType == SPSQLExport); + BOOL isCSV = (exportType == SPCSVExport); + BOOL isXML = (exportType == SPXMLExport); + BOOL isHTML = (exportType == SPHTMLExport); + BOOL isPDF = (exportType == SPPDFExport); + + BOOL structureEnabled = [[uiStateDict objectForKey:SPSQLExportStructureEnabled] boolValue]; + BOOL contentEnabled = [[uiStateDict objectForKey:SPSQLExportContentEnabled] boolValue]; + BOOL dropEnabled = [[uiStateDict objectForKey:SPSQLExportDropEnabled] boolValue]; + + if (isCSV || isXML || isHTML || isPDF || (isSQL && ((!structureEnabled) || (!dropEnabled)))) { enable = NO; - } - // If they are all checked, check to see if any of the tables are checked - else if (contentEnabled && structureEnabled && dropEnabled) { - + // Only enable the button if at least one table is selected for (NSArray *table in tables) { - if ([NSArrayObjectAtIndex(table, 1) boolValue] || - [NSArrayObjectAtIndex(table, 2) boolValue] || - [NSArrayObjectAtIndex(table, 3) boolValue]) - { + if ([NSArrayObjectAtIndex(table, 2) boolValue]) { enable = YES; break; } } } - // Disable if structure is unchecked, but content and drop are as dropping a - // table then trying to insert into it is obviously an error. - else if (contentEnabled && (!structureEnabled) && (dropEnabled)) { - enable = NO; - } - else { - enable = (contentEnabled || (structureEnabled || dropEnabled)); + else if (isSQL) { + + // Disable if all are unchecked + if ((!contentEnabled) && (!structureEnabled) && (!dropEnabled)) { + enable = NO; + } + // If they are all checked, check to see if any of the tables are checked + else if (contentEnabled && structureEnabled && dropEnabled) { + + // Only enable the button if at least one table is selected + for (NSArray *table in tables) + { + if ([NSArrayObjectAtIndex(table, 1) boolValue] || + [NSArrayObjectAtIndex(table, 2) boolValue] || + [NSArrayObjectAtIndex(table, 3) boolValue]) + { + enable = YES; + break; + } + } + } + // Disable if structure is unchecked, but content and drop are as dropping a + // table then trying to insert into it is obviously an error. + else if (contentEnabled && (!structureEnabled) && (dropEnabled)) { + enable = NO; + } + else { + enable = (contentEnabled || (structureEnabled || dropEnabled)); + } } + + [self performSelectorOnMainThread:@selector(_toggleExportButtonWithBool:) withObject:@(enable) waitUntilDone:NO]; } - - [self performSelectorOnMainThread:@selector(_toggleExportButtonWithBool:) withObject:[NSNumber numberWithBool:enable] waitUntilDone:NO]; - - [pool release]; } /** diff --git a/Source/SPExporter.m b/Source/SPExporter.m index ff90853c..83dff699 100644 --- a/Source/SPExporter.m +++ b/Source/SPExporter.m @@ -72,17 +72,13 @@ */ - (void)main { - NSAutoreleasePool *pool = nil; - @try { - pool = [[NSAutoreleasePool alloc] init]; - - [self exportOperation]; - } - @catch(NSException *e) { - [[NSApp onMainThread] reportException:e]; // will be caught by FeedbackReporter - } - @finally { - [pool release]; + @autoreleasepool { + @try { + [self exportOperation]; + } + @catch(NSException *e) { + [[NSApp onMainThread] reportException:e]; // will be caught by FeedbackReporter + } } } diff --git a/Source/SPFavoritesController.m b/Source/SPFavoritesController.m index e152a27f..c944bfe2 100644 --- a/Source/SPFavoritesController.m +++ b/Source/SPFavoritesController.m @@ -384,84 +384,82 @@ end_cleanup: */ - (void)_saveFavoritesData:(NSDictionary *)data { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - pthread_mutex_lock(&writeLock); - - if (!favoritesTree) { - goto end_cleanup; - } - - NSError *error = nil; + @autoreleasepool { + pthread_mutex_lock(&writeLock); - // Before starting the file actions, attempt to create a dictionary - // from the current favourites tree and convert it to a dictionary representation - // to create the plist data. This is done before file changes as it can sometimes - // be terminated during shutdown. - NSDictionary *dictionary = @{SPFavoritesRootKey : data}; - - NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:dictionary - format:NSPropertyListXMLFormat_v1_0 - options:0 - error:&error]; - if (error) { - NSLog(@"Error converting favorites data to plist format: %@", error); - goto end_cleanup; - } + if (!favoritesTree) { + goto end_cleanup; + } - NSFileManager *fileManager = [NSFileManager defaultManager]; - - NSString *dataPath = [fileManager applicationSupportDirectoryForSubDirectory:SPDataSupportFolder error:&error]; - - if (error) { - NSLog(@"Error retrieving data directory path: %@", [error localizedDescription]); - goto end_cleanup; - } - - NSString *favoritesFile = [dataPath stringByAppendingPathComponent:SPFavoritesDataFile]; - NSString *favoritesBackupFile = [dataPath stringByAppendingPathComponent:[NSString stringWithNewUUID]]; - - // If the favorites data file already exists, attempt to move it to keep as a backup - if ([fileManager fileExistsAtPath:favoritesFile]) { - [fileManager moveItemAtPath:favoritesFile toPath:favoritesBackupFile error:&error]; - } - - if (error) { - NSLog(@"Unable to backup (move) existing favorites data file during save. Deleting instead: %@", [error localizedDescription]); - - error = nil; - - // We can't move it so try and delete it - if (![fileManager removeItemAtPath:favoritesFile error:&error] && error) { - NSLog(@"Unable to delete existing favorites data file during save. Something is wrong, permissions perhaps: %@", [error localizedDescription]); + NSError *error = nil; + + // Before starting the file actions, attempt to create a dictionary + // from the current favourites tree and convert it to a dictionary representation + // to create the plist data. This is done before file changes as it can sometimes + // be terminated during shutdown. + NSDictionary *dictionary = @{SPFavoritesRootKey : data}; + + NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:dictionary + format:NSPropertyListXMLFormat_v1_0 + options:0 + error:&error]; + if (error) { + NSLog(@"Error converting favorites data to plist format: %@", error); goto end_cleanup; } - } - // Write the converted data to the favourites file - [plistData writeToFile:favoritesFile options:NSAtomicWrite error:&error]; + NSFileManager *fileManager = [NSFileManager defaultManager]; + + NSString *dataPath = [fileManager applicationSupportDirectoryForSubDirectory:SPDataSupportFolder error:&error]; - if (error) { - NSLog(@"Error writing favorites data. Restoring backup if available: %@", [error localizedDescription]); - - // Restore the original data file - error = nil; - - [fileManager moveItemAtPath:favoritesBackupFile toPath:favoritesFile error:&error]; - if (error) { - NSLog(@"Could not restore backup; favorites.plist left renamed as %@ due to error (%@)", favoritesBackupFile, [error localizedDescription]); + NSLog(@"Error retrieving data directory path: %@", [error localizedDescription]); + goto end_cleanup; } - } - else { - // Remove the original backup - [fileManager removeItemAtPath:favoritesBackupFile error:NULL]; - } - + + NSString *favoritesFile = [dataPath stringByAppendingPathComponent:SPFavoritesDataFile]; + NSString *favoritesBackupFile = [dataPath stringByAppendingPathComponent:[NSString stringWithNewUUID]]; + + // If the favorites data file already exists, attempt to move it to keep as a backup + if ([fileManager fileExistsAtPath:favoritesFile]) { + [fileManager moveItemAtPath:favoritesFile toPath:favoritesBackupFile error:&error]; + } + + if (error) { + NSLog(@"Unable to backup (move) existing favorites data file during save. Deleting instead: %@", [error localizedDescription]); + + error = nil; + + // We can't move it so try and delete it + if (![fileManager removeItemAtPath:favoritesFile error:&error] && error) { + NSLog(@"Unable to delete existing favorites data file during save. Something is wrong, permissions perhaps: %@", [error localizedDescription]); + goto end_cleanup; + } + } + + // Write the converted data to the favourites file + [plistData writeToFile:favoritesFile options:NSAtomicWrite error:&error]; + + if (error) { + NSLog(@"Error writing favorites data. Restoring backup if available: %@", [error localizedDescription]); + + // Restore the original data file + error = nil; + + [fileManager moveItemAtPath:favoritesBackupFile toPath:favoritesFile error:&error]; + + if (error) { + NSLog(@"Could not restore backup; favorites.plist left renamed as %@ due to error (%@)", favoritesBackupFile, [error localizedDescription]); + } + } + else { + // Remove the original backup + [fileManager removeItemAtPath:favoritesBackupFile error:NULL]; + } + end_cleanup: - pthread_mutex_unlock(&writeLock); - - [pool release]; + pthread_mutex_unlock(&writeLock); + } } /** diff --git a/Source/SPFavoritesExporter.m b/Source/SPFavoritesExporter.m index 419a24c3..4446c132 100644 --- a/Source/SPFavoritesExporter.m +++ b/Source/SPFavoritesExporter.m @@ -64,44 +64,42 @@ */ - (void)_writeFavoritesInBackground { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - NSMutableArray *favorites = [[NSMutableArray alloc] init]; - - // Get a dictionary representation of all favorites - for (SPTreeNode *node in [self exportFavorites]) - { - // The selection could contain a group as well as items in that group. - // So we skip those items, as their group will already export them. - if(![node isDescendantOfNodes:[self exportFavorites]]) - [favorites addObject:[node dictionaryRepresentation]]; - } - - NSDictionary *dictionary = @{SPFavoritesDataRootKey : favorites}; - - [favorites release]; - - // Convert the current favorites tree to a dictionary representation to create the plist data - NSError *error = nil; - NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:dictionary - format:NSPropertyListXMLFormat_v1_0 - options:0 - error:&error]; - - if (error) { - NSLog(@"Error converting favorites data to plist format: %@", error); - } - else if (plistData) { - [plistData writeToFile:[self exportPath] options:NSAtomicWrite error:&error]; - + @autoreleasepool { + NSMutableArray *favorites = [[NSMutableArray alloc] init]; + + // Get a dictionary representation of all favorites + for (SPTreeNode *node in [self exportFavorites]) + { + // The selection could contain a group as well as items in that group. + // So we skip those items, as their group will already export them. + if(![node isDescendantOfNodes:[self exportFavorites]]) + [favorites addObject:[node dictionaryRepresentation]]; + } + + NSDictionary *dictionary = @{SPFavoritesDataRootKey : favorites}; + + [favorites release]; + + // Convert the current favorites tree to a dictionary representation to create the plist data + NSError *error = nil; + NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:dictionary + format:NSPropertyListXMLFormat_v1_0 + options:0 + error:&error]; + if (error) { - NSLog(@"Error writing favorites data: %@", error); + NSLog(@"Error converting favorites data to plist format: %@", error); } + else if (plistData) { + [plistData writeToFile:[self exportPath] options:NSAtomicWrite error:&error]; + + if (error) { + NSLog(@"Error writing favorites data: %@", error); + } + } + + [self _informDelegateOfExportCompletion:error]; } - - [self _informDelegateOfExportCompletion:error]; - - [pool release]; } /** diff --git a/Source/SPFavoritesImporter.m b/Source/SPFavoritesImporter.m index f80d6cff..30d21603 100644 --- a/Source/SPFavoritesImporter.m +++ b/Source/SPFavoritesImporter.m @@ -67,36 +67,33 @@ static NSString *SPOldPreferenceFileFavoritesKey = @"favorites"; */ - (void)_importFavoritesInBackground { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - NSDictionary *importData; - NSFileManager *fileManager = [NSFileManager defaultManager]; - - if ([fileManager fileExistsAtPath:[self importPath]]) { - importData = [[[NSDictionary alloc] initWithContentsOfFile:[self importPath]] autorelease]; - - NSArray *favorites = [importData valueForKey:SPFavoritesDataRootKey]; - - if (favorites) { - [self _informDelegateOfImportDataAvailable:favorites]; - } - else { + @autoreleasepool { + NSDictionary *importData; + NSFileManager *fileManager = [NSFileManager defaultManager]; + + if ([fileManager fileExistsAtPath:[self importPath]]) { + importData = [[[NSDictionary alloc] initWithContentsOfFile:[self importPath]] autorelease]; - // Check to see whether we're importing favorites from an old preferences file - if ([importData valueForKey:SPOldPreferenceFileFavoritesKey]) { - [self _informDelegateOfImportDataAvailable:[importData valueForKey:SPOldPreferenceFileFavoritesKey]]; - } else { - [self _informDelegateOfErrorCode:NSFileReadUnknownError - description:NSLocalizedString(@"Error reading import file.", @"error reading import file")]; + NSArray *favorites = [importData valueForKey:SPFavoritesDataRootKey]; + + if (favorites) { + [self _informDelegateOfImportDataAvailable:favorites]; + } + else { + // Check to see whether we're importing favorites from an old preferences file + if ([importData valueForKey:SPOldPreferenceFileFavoritesKey]) { + [self _informDelegateOfImportDataAvailable:[importData valueForKey:SPOldPreferenceFileFavoritesKey]]; + } else { + [self _informDelegateOfErrorCode:NSFileReadUnknownError + description:NSLocalizedString(@"Error reading import file.", @"error reading import file")]; + } } } + else { + [self _informDelegateOfErrorCode:NSFileReadNoSuchFileError + description:NSLocalizedString(@"Import file does not exist.", @"import file does not exist message")]; + } } - else { - [self _informDelegateOfErrorCode:NSFileReadNoSuchFileError - description:NSLocalizedString(@"Import file does not exist.", @"import file does not exist message")]; - } - - [pool release]; } /** diff --git a/Source/SPFileHandle.m b/Source/SPFileHandle.m index af6f413f..21faaf4a 100644 --- a/Source/SPFileHandle.m +++ b/Source/SPFileHandle.m @@ -368,63 +368,61 @@ struct SPRawFileHandles { */ - (void)_writeBufferToData { - NSAutoreleasePool *writePool = [[NSAutoreleasePool alloc] init]; + @autoreleasepool { + // Process the buffer in a loop into the file, until cancelled + while (!fileIsClosed && ![processingThread isCancelled]) { - // Process the buffer in a loop into the file, until cancelled - while (!fileIsClosed && ![processingThread isCancelled]) { + // Check whether any data in the buffer needs to be written out - using thread locks for safety + pthread_mutex_lock(&bufferLock); + + if (!bufferDataLength) { + pthread_mutex_unlock(&bufferLock); + usleep(1000); + continue; + } + + // Copy the data into a local buffer + NSData *dataToBeWritten = [buffer copy]; + + [buffer setLength:0]; + bufferDataLength = 0; - // Check whether any data in the buffer needs to be written out - using thread locks for safety - pthread_mutex_lock(&bufferLock); - - if (!bufferDataLength) { pthread_mutex_unlock(&bufferLock); - usleep(1000); - continue; - } - // Copy the data into a local buffer - NSData *dataToBeWritten = [buffer copy]; - - [buffer setLength:0]; - bufferDataLength = 0; - - pthread_mutex_unlock(&bufferLock); + // Write out the data + long bufferLengthWrittenOut = 0; + + switch (compressionFormat) { + case SPGzipCompression: + bufferLengthWrittenOut = gzwrite(wrappedFile->gzfile, [dataToBeWritten bytes], (unsigned)[dataToBeWritten length]); + break; + case SPBzip2Compression: + bufferLengthWrittenOut = BZ2_bzwrite(wrappedFile->bzfile, (void *)[dataToBeWritten bytes], (int)[dataToBeWritten length]); + break; + default: + bufferLengthWrittenOut = fwrite([dataToBeWritten bytes], 1, [dataToBeWritten length], wrappedFile->file); + } - // Write out the data - long bufferLengthWrittenOut = 0; - - switch (compressionFormat) { - case SPGzipCompression: - bufferLengthWrittenOut = gzwrite(wrappedFile->gzfile, [dataToBeWritten bytes], (unsigned)[dataToBeWritten length]); - break; - case SPBzip2Compression: - bufferLengthWrittenOut = BZ2_bzwrite(wrappedFile->bzfile, (void *)[dataToBeWritten bytes], (int)[dataToBeWritten length]); - break; - default: - bufferLengthWrittenOut = fwrite([dataToBeWritten bytes], 1, [dataToBeWritten length], wrappedFile->file); - } + // Restore data to the buffer if it wasn't written out + pthread_mutex_lock(&bufferLock); - // Restore data to the buffer if it wasn't written out - pthread_mutex_lock(&bufferLock); - - if (bufferLengthWrittenOut < (NSInteger)[dataToBeWritten length]) { - if ([buffer length]) { - long dataLengthToRestore = [dataToBeWritten length] - bufferLengthWrittenOut; - [buffer replaceBytesInRange:NSMakeRange(0, 0) withBytes:[[dataToBeWritten subdataWithRange:NSMakeRange(bufferLengthWrittenOut, dataLengthToRestore)] bytes] length:dataLengthToRestore]; - bufferDataLength += dataLengthToRestore; + if (bufferLengthWrittenOut < (NSInteger)[dataToBeWritten length]) { + if ([buffer length]) { + long dataLengthToRestore = [dataToBeWritten length] - bufferLengthWrittenOut; + [buffer replaceBytesInRange:NSMakeRange(0, 0) withBytes:[[dataToBeWritten subdataWithRange:NSMakeRange(bufferLengthWrittenOut, dataLengthToRestore)] bytes] length:dataLengthToRestore]; + bufferDataLength += dataLengthToRestore; + } + } + // Otherwise, mark all data as written if it has been - allows synching to hard disk. + else if (![buffer length]) { + allDataWritten = YES; } - } - // Otherwise, mark all data as written if it has been - allows synching to hard disk. - else if (![buffer length]) { - allDataWritten = YES; - } - - pthread_mutex_unlock(&bufferLock); - [dataToBeWritten release]; - } + pthread_mutex_unlock(&bufferLock); - [writePool drain]; + [dataToBeWritten release]; + } + } } /** diff --git a/Source/SPHistoryController.h b/Source/SPHistoryController.h index 6ba86d58..7dc4028a 100644 --- a/Source/SPHistoryController.h +++ b/Source/SPHistoryController.h @@ -65,7 +65,6 @@ // Loading history entries - (void) loadEntryAtPosition:(NSUInteger)position; - (void) loadEntryTaskWithPosition:(NSNumber *)positionNumber; -- (void) abortEntryLoadWithPool:(NSAutoreleasePool *)pool; - (void) loadEntryFromMenuItem:(id)theMenuItem; // Restoring view states diff --git a/Source/SPHistoryController.m b/Source/SPHistoryController.m index 2abac6bb..aab54d2b 100644 --- a/Source/SPHistoryController.m +++ b/Source/SPHistoryController.m @@ -365,99 +365,98 @@ } - (void) loadEntryTaskWithPosition:(NSNumber *)positionNumber { - NSAutoreleasePool *loadPool = [[NSAutoreleasePool alloc] init]; - NSUInteger position = [positionNumber unsignedIntegerValue]; - - modifyingState = YES; - - // Update the position and extract the history entry - historyPosition = position; - NSDictionary *historyEntry = [history objectAtIndex:historyPosition]; - - // Set table content details for restore - [tableContentInstance setSortColumnNameToRestore:[historyEntry objectForKey:@"contentSortCol"] isAscending:[[historyEntry objectForKey:@"contentSortColIsAsc"] boolValue]]; - [tableContentInstance setPageToRestore:[[historyEntry objectForKey:@"contentPageNumber"] integerValue]]; - [tableContentInstance setSelectionToRestore:[historyEntry objectForKey:@"contentSelection"]]; - [tableContentInstance setViewportToRestore:[[historyEntry objectForKey:@"contentViewport"] rectValue]]; - [tableContentInstance setFiltersToRestore:[historyEntry objectForKey:@"contentFilter"]]; - - // If the database, table, and view are the same and content - just trigger a table reload (filters) - if ([[theDocument database] isEqualToString:[historyEntry objectForKey:@"database"]] - && [historyEntry objectForKey:@"table"] && [[theDocument table] isEqualToString:[historyEntry objectForKey:@"table"]] - && [[historyEntry objectForKey:@"view"] integerValue] == [theDocument currentlySelectedView] - && [[historyEntry objectForKey:@"view"] integerValue] == SPTableViewContent) - { - [tableContentInstance loadTable:[historyEntry objectForKey:@"table"]]; - modifyingState = NO; - [[self onMainThread] updateToolbarItem]; - [theDocument endTask]; - [loadPool drain]; - return; - } - - // If the same table was selected, mark the content as requiring a reload - if ([historyEntry objectForKey:@"table"] && [[theDocument table] isEqualToString:[historyEntry objectForKey:@"table"]]) { - [theDocument setContentRequiresReload:YES]; - } - - // Update the database and table name if necessary - [theDocument selectDatabase:[historyEntry objectForKey:@"database"] item:[historyEntry objectForKey:@"table"]]; + @autoreleasepool { + NSUInteger position = [positionNumber unsignedIntegerValue]; + + modifyingState = YES; + + // Update the position and extract the history entry + historyPosition = position; + NSDictionary *historyEntry = [history objectAtIndex:historyPosition]; + + // Set table content details for restore + [tableContentInstance setSortColumnNameToRestore:[historyEntry objectForKey:@"contentSortCol"] isAscending:[[historyEntry objectForKey:@"contentSortColIsAsc"] boolValue]]; + [tableContentInstance setPageToRestore:[[historyEntry objectForKey:@"contentPageNumber"] integerValue]]; + [tableContentInstance setSelectionToRestore:[historyEntry objectForKey:@"contentSelection"]]; + [tableContentInstance setViewportToRestore:[[historyEntry objectForKey:@"contentViewport"] rectValue]]; + [tableContentInstance setFiltersToRestore:[historyEntry objectForKey:@"contentFilter"]]; + + // If the database, table, and view are the same and content - just trigger a table reload (filters) + if ( + [[theDocument database] isEqualToString:[historyEntry objectForKey:@"database"]] + && [historyEntry objectForKey:@"table"] + && [[theDocument table] isEqualToString:[historyEntry objectForKey:@"table"]] + && [[historyEntry objectForKey:@"view"] integerValue] == [theDocument currentlySelectedView] + && [[historyEntry objectForKey:@"view"] integerValue] == SPTableViewContent + ) { + [tableContentInstance loadTable:[historyEntry objectForKey:@"table"]]; + modifyingState = NO; + [[self onMainThread] updateToolbarItem]; + [theDocument endTask]; + return; + } - // If the database or table couldn't be selected, error. - if ((![[theDocument database] isEqualToString:[historyEntry objectForKey:@"database"]] - && ([theDocument database] || [historyEntry objectForKey:@"database"])) - || - (![[theDocument table] isEqualToString:[historyEntry objectForKey:@"table"]] - && ([theDocument table] || [historyEntry objectForKey:@"table"]))) - { - return [self abortEntryLoadWithPool:loadPool]; - } + // If the same table was selected, mark the content as requiring a reload + if ([historyEntry objectForKey:@"table"] && [[theDocument table] isEqualToString:[historyEntry objectForKey:@"table"]]) { + [theDocument setContentRequiresReload:YES]; + } - // Check and set the view - if ([theDocument currentlySelectedView] != [[historyEntry objectForKey:@"view"] integerValue]) { - switch ([[historyEntry objectForKey:@"view"] integerValue]) { - case SPTableViewStructure: - [theDocument viewStructure:self]; - break; - case SPTableViewContent: - [theDocument viewContent:self]; - break; - case SPTableViewCustomQuery: - [theDocument viewQuery:self]; - break; - case SPTableViewStatus: - [theDocument viewStatus:self]; - break; - case SPTableViewRelations: - [theDocument viewRelations:self]; - break; - case SPTableViewTriggers: - [theDocument viewTriggers:self]; - break; + // Update the database and table name if necessary + [theDocument selectDatabase:[historyEntry objectForKey:@"database"] item:[historyEntry objectForKey:@"table"]]; + + // If the database or table couldn't be selected, error. + if ( + ( + ![[theDocument database] isEqualToString:[historyEntry objectForKey:@"database"]] && + ([theDocument database] || [historyEntry objectForKey:@"database"]) + ) || + ( + ![[theDocument table] isEqualToString:[historyEntry objectForKey:@"table"]] && + ([theDocument table] || [historyEntry objectForKey:@"table"]) + ) + ) { + goto abort_entry_load; } + + // Check and set the view if ([theDocument currentlySelectedView] != [[historyEntry objectForKey:@"view"] integerValue]) { - return [self abortEntryLoadWithPool:loadPool]; + switch ([[historyEntry objectForKey:@"view"] integerValue]) { + case SPTableViewStructure: + [theDocument viewStructure:self]; + break; + case SPTableViewContent: + [theDocument viewContent:self]; + break; + case SPTableViewCustomQuery: + [theDocument viewQuery:self]; + break; + case SPTableViewStatus: + [theDocument viewStatus:self]; + break; + case SPTableViewRelations: + [theDocument viewRelations:self]; + break; + case SPTableViewTriggers: + [theDocument viewTriggers:self]; + break; + } + if ([theDocument currentlySelectedView] != [[historyEntry objectForKey:@"view"] integerValue]) { + goto abort_entry_load; + } } - } - modifyingState = NO; - [[self onMainThread] updateToolbarItem]; + modifyingState = NO; + [[self onMainThread] updateToolbarItem]; - // End the task - [theDocument endTask]; - [loadPool drain]; -} + // End the task + [theDocument endTask]; + return; -/** - * Convenience method for aborting history load - could at some point - * clean up the history list, show an alert, etc - */ -- (void) abortEntryLoadWithPool:(NSAutoreleasePool *)pool -{ - NSBeep(); - modifyingState = NO; - [theDocument endTask]; - if (pool) [pool drain]; +abort_entry_load: + NSBeep(); + modifyingState = NO; + [theDocument endTask]; + } } /** diff --git a/Source/SPIndexesController.m b/Source/SPIndexesController.m index c5b5d14c..ace6d882 100644 --- a/Source/SPIndexesController.m +++ b/Source/SPIndexesController.m @@ -777,109 +777,106 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; */ - (void)_addIndexUsingDetails:(NSDictionary *)indexDetails { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + @autoreleasepool { + // Check whether a save of the current fields row is required. + if (![[tableStructure onMainThread] saveRowOnDeselect]) return; - // Check whether a save of the current fields row is required. - if (![[tableStructure onMainThread] saveRowOnDeselect]) return; - - // Retrieve index details - NSString *indexName = [indexDetails objectForKey:SPNewIndexIndexName]; - NSString *indexType = [indexDetails objectForKey:SPNewIndexIndexType]; - NSString *indexStorageType = [indexDetails objectForKey:SPNewIndexStorageType]; - NSNumber *indexKeyBlockSize = [indexDetails objectForKey:SPNewIndexKeyBlockSize]; - NSArray *indexedColumns = [indexDetails objectForKey:SPNewIndexIndexedColumns]; + // Retrieve index details + NSString *indexName = [indexDetails objectForKey:SPNewIndexIndexName]; + NSString *indexType = [indexDetails objectForKey:SPNewIndexIndexType]; + NSString *indexStorageType = [indexDetails objectForKey:SPNewIndexStorageType]; + NSNumber *indexKeyBlockSize = [indexDetails objectForKey:SPNewIndexKeyBlockSize]; + NSArray *indexedColumns = [indexDetails objectForKey:SPNewIndexIndexedColumns]; - // Interface validation should prevent this, but just to be safe - if ([indexedColumns count] > 0) { + // Interface validation should prevent this, but just to be safe + if ([indexedColumns count] > 0) { - NSMutableArray *tempIndexedColumns = [[NSMutableArray alloc] init]; + NSMutableArray *tempIndexedColumns = [[NSMutableArray alloc] init]; - if ([indexType isEqualToString:@"PRIMARY KEY"]) { - indexName = @""; - } - else { - indexName = ([indexName isEqualToString:@""]) ? @"" : [indexName backtickQuotedString]; - } + if ([indexType isEqualToString:@"PRIMARY KEY"]) { + indexName = @""; + } + else { + indexName = ([indexName isEqualToString:@""]) ? @"" : [indexName backtickQuotedString]; + } - // For each column add it to the temp array and check if size is required - for (NSDictionary *column in indexedColumns) - { - NSString *columnName = [column objectForKey:@"name"]; - NSString *columnType = [column objectForKey:@"type"]; + // For each column add it to the temp array and check if size is required + for (NSDictionary *column in indexedColumns) + { + NSString *columnName = [column objectForKey:@"name"]; + NSString *columnType = [column objectForKey:@"type"]; - if ((![columnName length]) || (![columnType length])) continue; + if ((![columnName length]) || (![columnType length])) continue; - BOOL isFullTextType = [indexType isEqualToString:@"FULLTEXT"]; - - // If this field type requires a length and one hasn't been specified (interface validation - // should ensure this doesn't happen), then skip it. - if ([requiresLength containsObject:[columnType uppercaseString]] && (![(NSString *)[column objectForKey:@"Size"] length]) && !isFullTextType) continue; + BOOL isFullTextType = [indexType isEqualToString:@"FULLTEXT"]; - if ([column objectForKey:@"Size"] && [supportsLength containsObject:columnType] && !isFullTextType) { + // If this field type requires a length and one hasn't been specified (interface validation + // should ensure this doesn't happen), then skip it. + if ([requiresLength containsObject:[columnType uppercaseString]] && (![(NSString *)[column objectForKey:@"Size"] length]) && !isFullTextType) continue; - [tempIndexedColumns addObject:[NSString stringWithFormat:@"%@ (%@)", [columnName backtickQuotedString], [column objectForKey:@"Size"]]]; - } - else { - [tempIndexedColumns addObject:[columnName backtickQuotedString]]; + if ([column objectForKey:@"Size"] && [supportsLength containsObject:columnType] && !isFullTextType) { + [tempIndexedColumns addObject:[NSString stringWithFormat:@"%@ (%@)", [columnName backtickQuotedString], [column objectForKey:@"Size"]]]; + } + else { + [tempIndexedColumns addObject:[columnName backtickQuotedString]]; + } } - } - if ([tempIndexedColumns count]) { + if ([tempIndexedColumns count]) { - if ((![indexType isEqualToString:@"INDEX"]) && (![indexType isEqualToString:@"PRIMARY KEY"])) indexType = [indexType stringByAppendingFormat:@" INDEX"]; + if ((![indexType isEqualToString:@"INDEX"]) && (![indexType isEqualToString:@"PRIMARY KEY"])) indexType = [indexType stringByAppendingFormat:@" INDEX"]; - // Build the query - NSMutableString *query = [NSMutableString stringWithFormat:@"ALTER TABLE %@ ADD %@", [table backtickQuotedString], indexType]; + // Build the query + NSMutableString *query = [NSMutableString stringWithFormat:@"ALTER TABLE %@ ADD %@", [table backtickQuotedString], indexType]; - // If supplied specify the index's name - if ([indexName length]) { - [query appendString:@" "]; - [query appendString:indexName]; - } + // If supplied specify the index's name + if ([indexName length]) { + [query appendString:@" "]; + [query appendString:indexName]; + } - // If supplied specify the index's storage type - if (indexStorageType) { - [query appendString:@" USING "]; - [query appendString:indexStorageType]; - } + // If supplied specify the index's storage type + if (indexStorageType) { + [query appendString:@" USING "]; + [query appendString:indexStorageType]; + } - // Add the columns - [query appendFormat:@" (%@)", [tempIndexedColumns componentsJoinedByCommas]]; + // Add the columns + [query appendFormat:@" (%@)", [tempIndexedColumns componentsJoinedByCommas]]; - // If supplied specify the index's key block size - if (indexKeyBlockSize) { - [query appendFormat:@" KEY_BLOCK_SIZE = %ld", (long)[indexKeyBlockSize integerValue]]; - } + // If supplied specify the index's key block size + if (indexKeyBlockSize) { + [query appendFormat:@" KEY_BLOCK_SIZE = %ld", (long)[indexKeyBlockSize integerValue]]; + } - // Execute the query - [connection queryString:query]; + // Execute the query + [connection queryString:query]; - // Check for errors, but only if the query wasn't cancelled - if ([connection queryErrored] && ![connection lastQueryWasCancelled]) { - SPOnewayAlertSheet( - NSLocalizedString(@"Unable to add index", @"add index error message"), - [dbDocument parentWindow], - [NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to add the index.\n\nMySQL said: %@", @"add index error informative message"), [connection lastErrorMessage]] - ); - } - else { - [tableData resetAllData]; - [dbDocument setStatusRequiresReload:YES]; - - [tableStructure loadTable:table]; - } - } + // Check for errors, but only if the query wasn't cancelled + if ([connection queryErrored] && ![connection lastQueryWasCancelled]) { + SPOnewayAlertSheet( + NSLocalizedString(@"Unable to add index", @"add index error message"), + [dbDocument parentWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to add the index.\n\nMySQL said: %@", @"add index error informative message"), [connection lastErrorMessage]] + ); + } + else { + [tableData resetAllData]; + [dbDocument setStatusRequiresReload:YES]; - SPClear(tempIndexedColumns); - } + [tableStructure loadTable:table]; + } + } - // Reset indexed fields to default - [indexedFields removeAllObjects]; - [indexedFields addObject:[[[fields objectAtIndex:0] mutableCopy] autorelease]]; + [tempIndexedColumns release]; + } - [dbDocument endTask]; + // Reset indexed fields to default + [indexedFields removeAllObjects]; + [indexedFields addObject:[[[fields objectAtIndex:0] mutableCopy] autorelease]]; - [pool drain]; + [dbDocument endTask]; + } } /** @@ -889,61 +886,62 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; */ - (void)_removeIndexUsingDetails:(NSDictionary *)indexDetails { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + @autoreleasepool { + NSString *index = [indexDetails objectForKey:@"Key_name"]; + NSString *fkName = [indexDetails objectForKey:@"ForeignKey"]; - NSString *index = [indexDetails objectForKey:@"Key_name"]; - NSString *fkName = [indexDetails objectForKey:@"ForeignKey"]; + // Remove the foreign key dependency before the index if required + if ([fkName length]) { - // Remove the foreign key dependency before the index if required - if ([fkName length]) { + [connection queryString:[NSString stringWithFormat:@"ALTER TABLE %@ DROP FOREIGN KEY %@", [table backtickQuotedString], [fkName backtickQuotedString]]]; - [connection queryString:[NSString stringWithFormat:@"ALTER TABLE %@ DROP FOREIGN KEY %@", [table backtickQuotedString], [fkName backtickQuotedString]]]; + // Check for errors, but only if the query wasn't cancelled + if ([connection queryErrored] && ![connection lastQueryWasCancelled]) { + NSMutableDictionary *errorDictionary = [NSMutableDictionary dictionary]; - // Check for errors, but only if the query wasn't cancelled - if ([connection queryErrored] && ![connection lastQueryWasCancelled]) { - NSMutableDictionary *errorDictionary = [NSMutableDictionary dictionary]; + [errorDictionary setObject:NSLocalizedString(@"Unable to delete relation", @"error deleting relation message") forKey:@"title"]; + [errorDictionary setObject:[NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to delete the relation '%@'.\n\nMySQL said: %@", @"error deleting relation informative message"), fkName, [connection lastErrorMessage]] forKey:@"message"]; - [errorDictionary setObject:NSLocalizedString(@"Unable to delete relation", @"error deleting relation message") forKey:@"title"]; - [errorDictionary setObject:[NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to delete the relation '%@'.\n\nMySQL said: %@", @"error deleting relation informative message"), fkName, [connection lastErrorMessage]] forKey:@"message"]; + [[tableStructure onMainThread] showErrorSheetWith:errorDictionary]; + } + } - [(SPTableStructure*)[tableStructure onMainThread] showErrorSheetWith:errorDictionary]; + if ([index isEqualToString:@"PRIMARY"]) { + [connection queryString:[NSString stringWithFormat:@"ALTER TABLE %@ DROP PRIMARY KEY", [table backtickQuotedString]]]; + } + else { + [connection queryString:[NSString stringWithFormat:@"ALTER TABLE %@ DROP INDEX %@", + [table backtickQuotedString], [index backtickQuotedString]]]; } - } - if ([index isEqualToString:@"PRIMARY"]) { - [connection queryString:[NSString stringWithFormat:@"ALTER TABLE %@ DROP PRIMARY KEY", [table backtickQuotedString]]]; - } - else { - [connection queryString:[NSString stringWithFormat:@"ALTER TABLE %@ DROP INDEX %@", - [table backtickQuotedString], [index backtickQuotedString]]]; - } + // Check for errors, but only if the query wasn't cancelled + if ([connection queryErrored] && ![connection lastQueryWasCancelled]) { + //if the last error was 1553 and we did not already try to remove a FK beforehand, we have to request to remove the foreign key before we can remove the index + if([connection lastErrorID] == 1553 /* ER_DROP_INDEX_FK */ && ![fkName length]) { + NSDictionary *details = @{ + @"Key_name": index, + @"error": SPBoxNil([connection lastErrorMessage]) + }; + [self performSelectorOnMainThread:@selector(_removingIndexFailedWithForeignKeyError:) withObject:details waitUntilDone:NO]; + } + else { + NSMutableDictionary *errorDictionary = [NSMutableDictionary dictionary]; - // Check for errors, but only if the query wasn't cancelled - if ([connection queryErrored] && ![connection lastQueryWasCancelled]) { - //if the last error was 1553 and we did not already try to remove a FK beforehand, we have to request to remove the foreign key before we can remove the index - if([connection lastErrorID] == 1553 /* ER_DROP_INDEX_FK */ && ![fkName length]) { - NSDictionary *details = @{@"Key_name": index, @"error": SPBoxNil([connection lastErrorMessage])}; - [self performSelectorOnMainThread:@selector(_removingIndexFailedWithForeignKeyError:) withObject:details waitUntilDone:NO]; + [errorDictionary setObject:NSLocalizedString(@"Unable to delete index", @"error deleting index message") forKey:@"title"]; + [errorDictionary setObject:[NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to delete the index.\n\nMySQL said: %@", @"error deleting index informative message"), [connection lastErrorMessage]] forKey:@"message"]; + + [[tableStructure onMainThread] showErrorSheetWith:errorDictionary]; + } } else { - NSMutableDictionary *errorDictionary = [NSMutableDictionary dictionary]; - - [errorDictionary setObject:NSLocalizedString(@"Unable to delete index", @"error deleting index message") forKey:@"title"]; - [errorDictionary setObject:[NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to delete the index.\n\nMySQL said: %@", @"error deleting index informative message"), [connection lastErrorMessage]] forKey:@"message"]; - - [(SPTableStructure*)[tableStructure onMainThread] showErrorSheetWith:errorDictionary]; + [tableData resetAllData]; + [dbDocument setStatusRequiresReload:YES]; + + [tableStructure loadTable:table]; } - } - else { - [tableData resetAllData]; - [dbDocument setStatusRequiresReload:YES]; - [tableStructure loadTable:table]; + [dbDocument endTask]; } - - [dbDocument endTask]; - - [pool drain]; } /** diff --git a/Source/SPNavigatorController.m b/Source/SPNavigatorController.m index a7d8b61b..aaa3c720 100644 --- a/Source/SPNavigatorController.m +++ b/Source/SPNavigatorController.m @@ -720,12 +720,12 @@ static NSComparisonResult compareStrings(NSString *s1, NSString *s2, void* conte - (void)reloadAfterFiltering { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - isFiltering = YES; - [outlineSchema2 reloadData]; - [outlineSchema2 expandItem:[outlineSchema2 itemAtRow:0] expandChildren:YES]; - isFiltering = NO; - [pool release]; + @autoreleasepool { + isFiltering = YES; + [outlineSchema2 reloadData]; + [outlineSchema2 expandItem:[outlineSchema2 itemAtRow:0] expandChildren:YES]; + isFiltering = NO; + } } - (IBAction)syncButtonAction:(id)sender diff --git a/Source/SPProcessListController.m b/Source/SPProcessListController.m index a256835c..a27b40ca 100644 --- a/Source/SPProcessListController.m +++ b/Source/SPProcessListController.m @@ -633,54 +633,51 @@ static NSString * const SPKillIdKey = @"SPKillId"; */ - (void)_getDatabaseProcessListInBackground:(id)object; { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - NSUInteger i = 0; - - // Get processes - if ([connection isConnected]) { - - SPMySQLResult *processList = (showFullProcessList) ? [connection queryString:@"SHOW FULL PROCESSLIST"] : [connection listProcesses]; - - [processList setReturnDataAsStrings:YES]; - - [[processes onMainThread] removeAllObjects]; - - for (i = 0; i < [processList numberOfRows]; i++) - { - //SPMySQL.framewokr currently returns numbers as NSString, which will break sorting of numbers in this case. - NSMutableDictionary *rowsFixed = [[processList getRowAsDictionary] mutableCopy]; - - // The ID can be a 64-bit value on 64-bit servers - id idColumn = [rowsFixed objectForKey:@"Id"]; - if (idColumn != nil && [idColumn isKindOfClass:[NSString class]]) { - long long numRaw = [(NSString *)idColumn longLongValue]; - NSNumber *num = [NSNumber numberWithLongLong:numRaw]; - [rowsFixed setObject:num forKey:@"Id"]; - } - - // Time is a signed int(7) - this is a 32 bit int value - id timeColumn = [rowsFixed objectForKey:@"Time"]; - if(timeColumn != nil && [timeColumn isKindOfClass:[NSString class]]) { - int numRaw = [(NSString *)timeColumn intValue]; - NSNumber *num = [NSNumber numberWithInt:numRaw]; - [rowsFixed setObject:num forKey:@"Time"]; + @autoreleasepool { + NSUInteger i = 0; + + // Get processes + if ([connection isConnected]) { + + SPMySQLResult *processList = (showFullProcessList) ? [connection queryString:@"SHOW FULL PROCESSLIST"] : [connection listProcesses]; + + [processList setReturnDataAsStrings:YES]; + + [[processes onMainThread] removeAllObjects]; + + for (i = 0; i < [processList numberOfRows]; i++) + { + //SPMySQL.framewokr currently returns numbers as NSString, which will break sorting of numbers in this case. + NSMutableDictionary *rowsFixed = [[processList getRowAsDictionary] mutableCopy]; + + // The ID can be a 64-bit value on 64-bit servers + id idColumn = [rowsFixed objectForKey:@"Id"]; + if (idColumn != nil && [idColumn isKindOfClass:[NSString class]]) { + long long numRaw = [(NSString *)idColumn longLongValue]; + NSNumber *num = [NSNumber numberWithLongLong:numRaw]; + [rowsFixed setObject:num forKey:@"Id"]; + } + + // Time is a signed int(7) - this is a 32 bit int value + id timeColumn = [rowsFixed objectForKey:@"Time"]; + if(timeColumn != nil && [timeColumn isKindOfClass:[NSString class]]) { + int numRaw = [(NSString *)timeColumn intValue]; + NSNumber *num = [NSNumber numberWithInt:numRaw]; + [rowsFixed setObject:num forKey:@"Time"]; + } + + // This is pretty bad from a performance standpoint, but we must not + // interfere with the NSTableView's reload cycle and there is no way + // to know when it starts/ends. We only know it will happen on the + // main thread, so we have to interlock with that. + [[processes onMainThread] addObject:[[rowsFixed copy] autorelease]]; + [rowsFixed release]; } - - // This is pretty bad from a performance standpoint, but we must not - // interfere with the NSTableView's reload cycle and there is no way - // to know when it starts/ends. We only know it will happen on the - // main thread, so we have to interlock with that. - [[processes onMainThread] addObject:[[rowsFixed copy] autorelease]]; - [rowsFixed release]; } + // Update the UI on the main thread + [self performSelectorOnMainThread:@selector(_processListRefreshed) withObject:nil waitUntilDone:NO]; } - - // Update the UI on the main thread - [self performSelectorOnMainThread:@selector(_processListRefreshed) withObject:nil waitUntilDone:NO]; - - [pool release]; } /** diff --git a/Source/SPSSHTunnel.m b/Source/SPSSHTunnel.m index cc95c82f..0246c002 100644 --- a/Source/SPSSHTunnel.m +++ b/Source/SPSSHTunnel.m @@ -260,251 +260,249 @@ static unsigned short getRandomPort(); - (void)launchTask:(id) dummy { if (connectionState != SPMySQLProxyIdle || task) return; - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSMutableArray *taskArguments; - NSMutableDictionary *taskEnvironment; - NSString *authenticationAppPath; - connectionState = SPMySQLProxyConnecting; - if (delegate) [delegate performSelectorOnMainThread:stateChangeSelector withObject:self waitUntilDone:NO]; + @autoreleasepool { + NSMutableArray *taskArguments; + NSMutableDictionary *taskEnvironment; + NSString *authenticationAppPath; - // Enforce a parent window being present for dialogs - if (!parentWindow) { - connectionState = SPMySQLProxyIdle; + connectionState = SPMySQLProxyConnecting; if (delegate) [delegate performSelectorOnMainThread:stateChangeSelector withObject:self waitUntilDone:NO]; - [self setLastError:@"SSH Tunnel started without a parent window. A parent window must be present."]; - [pool release]; - return; - } - - NSInteger connectionTimeout = [[[NSUserDefaults standardUserDefaults] objectForKey:SPConnectionTimeoutValue] integerValue]; - if (!connectionTimeout) connectionTimeout = 10; - BOOL useKeepAlive = [[[NSUserDefaults standardUserDefaults] objectForKey:SPUseKeepAlive] doubleValue]; - double keepAliveInterval = [[[NSUserDefaults standardUserDefaults] objectForKey:SPKeepAliveInterval] doubleValue]; - if (!keepAliveInterval) keepAliveInterval = 0; - // If no local port has yet been chosen, choose one - if (!localPort) { - localPort = getRandomPort(); - - if (useHostFallback) { - localPortFallback = getRandomPort(); - } - - // Abort if no local free port could be allocated - if (!localPort || (useHostFallback && !localPortFallback)) { + // Enforce a parent window being present for dialogs + if (!parentWindow) { connectionState = SPMySQLProxyIdle; if (delegate) [delegate performSelectorOnMainThread:stateChangeSelector withObject:self waitUntilDone:NO]; - [self setLastError:NSLocalizedString(@"No local port could be allocated for the SSH Tunnel.", @"SSH tunnel could not be created because no local port could be allocated")]; - [pool release]; + [self setLastError:@"SSH Tunnel started without a parent window. A parent window must be present."]; return; } - } - // Set up the NSTask - task = [[NSTask alloc] init]; - NSString *launchPath = @"/usr/bin/ssh"; - NSString *userSSHPath = [[NSUserDefaults standardUserDefaults] stringForKey:SPSSHClientPath]; + NSInteger connectionTimeout = [[[NSUserDefaults standardUserDefaults] objectForKey:SPConnectionTimeoutValue] integerValue]; + if (!connectionTimeout) connectionTimeout = 10; + BOOL useKeepAlive = [[[NSUserDefaults standardUserDefaults] objectForKey:SPUseKeepAlive] doubleValue]; + double keepAliveInterval = [[[NSUserDefaults standardUserDefaults] objectForKey:SPKeepAliveInterval] doubleValue]; + if (!keepAliveInterval) keepAliveInterval = 0; - if([userSSHPath length]) { - launchPath = userSSHPath; - // And I'm sure we will get issue reports about it anyway! - [debugMessagesLock lock]; - [debugMessages addObject:@"################################################################"]; - [debugMessages addObject:[NSString stringWithFormat:@"# %@",NSLocalizedString(@"Custom SSH binary enabled. Disable in Preferences to rule out incompatibilities!", @"SSH connection : debug header with user-defined ssh binary")]]; - [debugMessages addObject:@"################################################################"]; - [debugMessagesLock unlock]; - } - - [task setLaunchPath:launchPath]; + // If no local port has yet been chosen, choose one + if (!localPort) { + localPort = getRandomPort(); - // Prepare to set up the arguments for the task - taskArguments = [[NSMutableArray alloc] init]; - void (^TA)(NSString *, NSString *) = ^(NSString *_name, NSString *_value) { - [taskArguments addObjectsFromArray:@[_name,_value]]; - }; - - // Enable verbose mode for message parsing - [taskArguments addObject:@"-v"]; - - // Ensure that the connection can be used for only tunnels, not interactive - [taskArguments addObject:@"-N"]; - - // If explicitly enabled, activate connection multiplexing - note that this can cause connection - // instability on some setups, so is currently disabled by default. - if (connectionMuxingEnabled) { - // Enable automatic connection muxing/sharing, for faster connections - TA(@"-o",@"ControlMaster=auto"); - - // Set a custom control path to isolate connection sharing to Sequel Pro, to prevent picking up - // existing masters without forwarding enabled and to isolate from interactive sessions. Use a short - // hashed path to aid length limit issues. - unsigned char hashedPathResult[16]; - NSString *pathString = [NSString stringWithFormat:@"%@@%@:%ld", sshLogin?sshLogin:@"", sshHost, (long)(sshPort?sshPort:0)]; - CC_MD5([pathString UTF8String], (unsigned int)strlen([pathString UTF8String]), hashedPathResult); - NSString *hashedString = [[[NSData dataWithBytes:hashedPathResult length:16] dataToHexString] substringToIndex:8]; - TA(@"-o",([NSString stringWithFormat:@"ControlPath=%@/SPSSH-%@", [NSFileManager temporaryDirectory], hashedString])); - } - else { - // Disable muxing if requested - TA(@"-S", @"none"); - TA(@"-o", @"ControlMaster=no"); - } + if (useHostFallback) { + localPortFallback = getRandomPort(); + } - // If the port forwarding fails, exit - as this is the primary use case for the instance - TA(@"-o",@"ExitOnForwardFailure=yes"); + // Abort if no local free port could be allocated + if (!localPort || (useHostFallback && !localPortFallback)) { + connectionState = SPMySQLProxyIdle; + if (delegate) [delegate performSelectorOnMainThread:stateChangeSelector withObject:self waitUntilDone:NO]; + [self setLastError:NSLocalizedString(@"No local port could be allocated for the SSH Tunnel.", @"SSH tunnel could not be created because no local port could be allocated")]; + return; + } + } - // Specify a connection timeout based on the preferences value - TA(@"-o",([NSString stringWithFormat:@"ConnectTimeout=%ld", (long)connectionTimeout])); + // Set up the NSTask + task = [[NSTask alloc] init]; + NSString *launchPath = @"/usr/bin/ssh"; + NSString *userSSHPath = [[NSUserDefaults standardUserDefaults] stringForKey:SPSSHClientPath]; - // Allow three password prompts - TA(@"-o",@"NumberOfPasswordPrompts=3"); + if([userSSHPath length]) { + launchPath = userSSHPath; + // And I'm sure we will get issue reports about it anyway! + [debugMessagesLock lock]; + [debugMessages addObject:@"################################################################"]; + [debugMessages addObject:[NSString stringWithFormat:@"# %@",NSLocalizedString(@"Custom SSH binary enabled. Disable in Preferences to rule out incompatibilities!", @"SSH connection : debug header with user-defined ssh binary")]]; + [debugMessages addObject:@"################################################################"]; + [debugMessagesLock unlock]; + } - // Specify an identity file if available - if (identityFilePath) { - TA(@"-i", identityFilePath); - } + [task setLaunchPath:launchPath]; + + // Prepare to set up the arguments for the task + taskArguments = [[NSMutableArray alloc] init]; + void (^TA)(NSString *, NSString *) = ^(NSString *_name, NSString *_value) { + [taskArguments addObjectsFromArray:@[_name,_value]]; + }; + + // Enable verbose mode for message parsing + [taskArguments addObject:@"-v"]; + + // Ensure that the connection can be used for only tunnels, not interactive + [taskArguments addObject:@"-N"]; + + // If explicitly enabled, activate connection multiplexing - note that this can cause connection + // instability on some setups, so is currently disabled by default. + if (connectionMuxingEnabled) { + // Enable automatic connection muxing/sharing, for faster connections + TA(@"-o",@"ControlMaster=auto"); + + // Set a custom control path to isolate connection sharing to Sequel Pro, to prevent picking up + // existing masters without forwarding enabled and to isolate from interactive sessions. Use a short + // hashed path to aid length limit issues. + unsigned char hashedPathResult[16]; + NSString *pathString = [NSString stringWithFormat:@"%@@%@:%ld", sshLogin?sshLogin:@"", sshHost, (long)(sshPort?sshPort:0)]; + CC_MD5([pathString UTF8String], (unsigned int)strlen([pathString UTF8String]), hashedPathResult); + NSString *hashedString = [[[NSData dataWithBytes:hashedPathResult length:16] dataToHexString] substringToIndex:8]; + TA(@"-o",([NSString stringWithFormat:@"ControlPath=%@/SPSSH-%@", [NSFileManager temporaryDirectory], hashedString])); + } + else { + // Disable muxing if requested + TA(@"-S", @"none"); + TA(@"-o", @"ControlMaster=no"); + } - // If keepalive is set in the preferences, use the same value for the SSH tunnel - if (useKeepAlive && keepAliveInterval) { - TA(@"-o", @"TCPKeepAlive=no"); - TA(@"-o", ([NSString stringWithFormat:@"ServerAliveInterval=%ld", (long)ceil(keepAliveInterval)])); - TA(@"-o", @"ServerAliveCountMax=1"); - } + // If the port forwarding fails, exit - as this is the primary use case for the instance + TA(@"-o",@"ExitOnForwardFailure=yes"); - // Specify the port, host, and authentication details - if (sshPort) { - TA(@"-p", ([NSString stringWithFormat:@"%ld", (long)sshPort])); - } - if ([sshLogin length]) { - [taskArguments addObject:[NSString stringWithFormat:@"%@@%@", sshLogin, sshHost]]; - } - else { - [taskArguments addObject:sshHost]; - } - if (useHostFallback) { - TA(@"-L",([NSString stringWithFormat:@"%ld:127.0.0.1:%ld", (long)localPort, (long)remotePort])); - TA(@"-L",([NSString stringWithFormat:@"%ld:%@:%ld", (long)localPortFallback, remoteHost, (long)remotePort])); - } - else { - TA(@"-L", ([NSString stringWithFormat:@"%ld:%@:%ld", (long)localPort, remoteHost, (long)remotePort])); - } + // Specify a connection timeout based on the preferences value + TA(@"-o",([NSString stringWithFormat:@"ConnectTimeout=%ld", (long)connectionTimeout])); - [task setArguments:taskArguments]; - - // Set up the environment for the task - authenticationAppPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"SequelProTunnelAssistant"]; - taskEnvironment = [[NSMutableDictionary alloc] initWithDictionary:[[NSProcessInfo processInfo] environment]]; - [taskEnvironment setObject:authenticationAppPath forKey:@"SSH_ASKPASS"]; - [taskEnvironment setObject:@":0" forKey:@"DISPLAY"]; - [taskEnvironment setObject:tunnelConnectionName forKey:@"SP_CONNECTION_NAME"]; - [taskEnvironment setObject:tunnelConnectionVerifyHash forKey:@"SP_CONNECTION_VERIFY_HASH"]; - if (passwordInKeychain) { - [taskEnvironment setObject:[[NSNumber numberWithInteger:SPSSHPasswordUsesKeychain] stringValue] forKey:@"SP_PASSWORD_METHOD"]; - [taskEnvironment setObject:[keychainName stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] forKey:@"SP_KEYCHAIN_ITEM_NAME"]; - [taskEnvironment setObject:[keychainAccount stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] forKey:@"SP_KEYCHAIN_ITEM_ACCOUNT"]; - } else if (password) { - [taskEnvironment setObject:[[NSNumber numberWithInteger:SPSSHPasswordAsksUI] stringValue] forKey:@"SP_PASSWORD_METHOD"]; - } else { - [taskEnvironment setObject:[[NSNumber numberWithInteger:SPSSHPasswordNone] stringValue] forKey:@"SP_PASSWORD_METHOD"]; - } - [task setEnvironment:taskEnvironment]; + // Allow three password prompts + TA(@"-o",@"NumberOfPasswordPrompts=3"); - // Add the connection details to the debug messages - [debugMessagesLock lock]; - [debugMessages addObject:[NSString stringWithFormat:@"Used command: %@ %@\n", [task launchPath], [[task arguments] componentsJoinedByString:@" "]]]; - [debugMessagesLock unlock]; + // Specify an identity file if available + if (identityFilePath) { + TA(@"-i", identityFilePath); + } - // Set up the standard error pipe - standardError = [[NSPipe alloc] init]; - [task setStandardError:standardError]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(standardErrorHandler:) - name:NSFileHandleDataAvailableNotification - object:[standardError fileHandleForReading]]; - [[standardError fileHandleForReading] waitForDataInBackgroundAndNotify]; - - { - static BOOL hasCheckedTTY = NO; - if(!hasCheckedTTY) { - int fd = open("/dev/tty", O_RDWR); - if(fd >= 0) { - close(fd); - fprintf(stderr, ( - "!!!\n" - "!!! You are running Sequel Pro from a TTY.\n" - "!!! Any SSH connections that require user input (e.g. a password/passphrase) will fail\n" - "!!! and appear stalled indefinitely.\n" - "!!! Sorry!\n" - "!!!\n" - )); - fflush(stderr); - // Explanation: - // OpenSSH by default requests passwords AND yes/no questions directly from the TTY, - // if it is part of a session group that has a controlling terminal (which is the case for - // processes created by Terminal.app). - // - // But this won't work, because only the foreground process group can read from /dev/tty and - // NSTask will create a new (background) process group for OpenSSH on launch. - // Side note: The internal method called from -[NSTask launch] - // -[NSConcreteTask launchWithDictionary:] accepts key @"_NSTaskNoNewProcessGroup" to skip that. - // - // Now, there are two preconditions for OpenSSH to use our SSH_ASKPASS utility instead: - // 1) The "DISPLAY" envvar has to be set - // 2) There must be no controlling terminal (ie. open("/dev/tty") fails) - // (See readpass.c#read_passphrase() in OpenSSH for the relevant code) - // - // -[NSTask launch] internally uses posix_spawn() and according to its documentation - // "The new process also inherits the following attributes from the calling - // process: [...] control terminal [...]" - // So if we wanted to avoid that, we would have to reimplement the whole NSTask class - // and use fork()+exec*()+setsid() instead (or use GNUStep's NSTask which already does this). - // - // We could also do ioctl(fd, TIOCNOTTY, 0); before launching the child process, but - // changing our own controlling terminal does not seem like a good idea in the middle - // of the application lifecycle, when we don't know what other Cocoa code may use it... - } - hasCheckedTTY = YES; + // If keepalive is set in the preferences, use the same value for the SSH tunnel + if (useKeepAlive && keepAliveInterval) { + TA(@"-o", @"TCPKeepAlive=no"); + TA(@"-o", ([NSString stringWithFormat:@"ServerAliveInterval=%ld", (long)ceil(keepAliveInterval)])); + TA(@"-o", @"ServerAliveCountMax=1"); } - } - - @try { - // Launch and run the tunnel - [task launch]; //throws for invalid paths, missing +x permission - - // Listen for output - [task waitUntilExit]; - } - @catch (NSException *e) { - connectionState = SPMySQLProxyLaunchFailed; - // Log the exception. Could be improved by showing a dedicated alert instead + + // Specify the port, host, and authentication details + if (sshPort) { + TA(@"-p", ([NSString stringWithFormat:@"%ld", (long)sshPort])); + } + if ([sshLogin length]) { + [taskArguments addObject:[NSString stringWithFormat:@"%@@%@", sshLogin, sshHost]]; + } + else { + [taskArguments addObject:sshHost]; + } + if (useHostFallback) { + TA(@"-L",([NSString stringWithFormat:@"%ld:127.0.0.1:%ld", (long)localPort, (long)remotePort])); + TA(@"-L",([NSString stringWithFormat:@"%ld:%@:%ld", (long)localPortFallback, remoteHost, (long)remotePort])); + } + else { + TA(@"-L", ([NSString stringWithFormat:@"%ld:%@:%ld", (long)localPort, remoteHost, (long)remotePort])); + } + + [task setArguments:taskArguments]; + + // Set up the environment for the task + authenticationAppPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"SequelProTunnelAssistant"]; + taskEnvironment = [[NSMutableDictionary alloc] initWithDictionary:[[NSProcessInfo processInfo] environment]]; + [taskEnvironment setObject:authenticationAppPath forKey:@"SSH_ASKPASS"]; + [taskEnvironment setObject:@":0" forKey:@"DISPLAY"]; + [taskEnvironment setObject:tunnelConnectionName forKey:@"SP_CONNECTION_NAME"]; + [taskEnvironment setObject:tunnelConnectionVerifyHash forKey:@"SP_CONNECTION_VERIFY_HASH"]; + if (passwordInKeychain) { + [taskEnvironment setObject:[[NSNumber numberWithInteger:SPSSHPasswordUsesKeychain] stringValue] forKey:@"SP_PASSWORD_METHOD"]; + [taskEnvironment setObject:[keychainName stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] forKey:@"SP_KEYCHAIN_ITEM_NAME"]; + [taskEnvironment setObject:[keychainAccount stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] forKey:@"SP_KEYCHAIN_ITEM_ACCOUNT"]; + } else if (password) { + [taskEnvironment setObject:[[NSNumber numberWithInteger:SPSSHPasswordAsksUI] stringValue] forKey:@"SP_PASSWORD_METHOD"]; + } else { + [taskEnvironment setObject:[[NSNumber numberWithInteger:SPSSHPasswordNone] stringValue] forKey:@"SP_PASSWORD_METHOD"]; + } + [task setEnvironment:taskEnvironment]; + + // Add the connection details to the debug messages [debugMessagesLock lock]; - [debugMessages addObject:[NSString stringWithFormat:@"%@: %@\n", [e name], [e reason]]]; + [debugMessages addObject:[NSString stringWithFormat:@"Used command: %@ %@\n", [task launchPath], [[task arguments] componentsJoinedByString:@" "]]]; [debugMessagesLock unlock]; - } - // On tunnel close, clean up, ready for re-use if the delegate reconnects. - SPClear(task); - SPClear(standardError); - [[NSNotificationCenter defaultCenter] removeObserver:self - name:NSFileHandleDataAvailableNotification - object:nil]; - - // If the task closed unexpectedly, alert appropriately - if (connectionState != SPMySQLProxyIdle) { - connectionState = SPMySQLProxyIdle; - taskExitedUnexpectedly = YES; - [self setLastError:NSLocalizedString(@"The SSH Tunnel has unexpectedly closed.", @"SSH tunnel unexpectedly closed")]; - if (delegate) [delegate performSelectorOnMainThread:stateChangeSelector withObject:self waitUntilDone:NO]; - } + // Set up the standard error pipe + standardError = [[NSPipe alloc] init]; + [task setStandardError:standardError]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(standardErrorHandler:) + name:NSFileHandleDataAvailableNotification + object:[standardError fileHandleForReading]]; + [[standardError fileHandleForReading] waitForDataInBackgroundAndNotify]; + + { + static BOOL hasCheckedTTY = NO; + if(!hasCheckedTTY) { + int fd = open("/dev/tty", O_RDWR); + if(fd >= 0) { + close(fd); + fprintf(stderr, ( + "!!!\n" + "!!! You are running Sequel Pro from a TTY.\n" + "!!! Any SSH connections that require user input (e.g. a password/passphrase) will fail\n" + "!!! and appear stalled indefinitely.\n" + "!!! Sorry!\n" + "!!!\n" + )); + fflush(stderr); + // Explanation: + // OpenSSH by default requests passwords AND yes/no questions directly from the TTY, + // if it is part of a session group that has a controlling terminal (which is the case for + // processes created by Terminal.app). + // + // But this won't work, because only the foreground process group can read from /dev/tty and + // NSTask will create a new (background) process group for OpenSSH on launch. + // Side note: The internal method called from -[NSTask launch] + // -[NSConcreteTask launchWithDictionary:] accepts key @"_NSTaskNoNewProcessGroup" to skip that. + // + // Now, there are two preconditions for OpenSSH to use our SSH_ASKPASS utility instead: + // 1) The "DISPLAY" envvar has to be set + // 2) There must be no controlling terminal (ie. open("/dev/tty") fails) + // (See readpass.c#read_passphrase() in OpenSSH for the relevant code) + // + // -[NSTask launch] internally uses posix_spawn() and according to its documentation + // "The new process also inherits the following attributes from the calling + // process: [...] control terminal [...]" + // So if we wanted to avoid that, we would have to reimplement the whole NSTask class + // and use fork()+exec*()+setsid() instead (or use GNUStep's NSTask which already does this). + // + // We could also do ioctl(fd, TIOCNOTTY, 0); before launching the child process, but + // changing our own controlling terminal does not seem like a good idea in the middle + // of the application lifecycle, when we don't know what other Cocoa code may use it... + } + hasCheckedTTY = YES; + } + } + + @try { + // Launch and run the tunnel + [task launch]; //throws for invalid paths, missing +x permission + + // Listen for output + [task waitUntilExit]; + } + @catch (NSException *e) { + connectionState = SPMySQLProxyLaunchFailed; + // Log the exception. Could be improved by showing a dedicated alert instead + [debugMessagesLock lock]; + [debugMessages addObject:[NSString stringWithFormat:@"%@: %@\n", [e name], [e reason]]]; + [debugMessagesLock unlock]; + } - // Run the run loop for a short time to ensure all task/pipe callbacks are dealt with - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; + // On tunnel close, clean up, ready for re-use if the delegate reconnects. + SPClear(task); + SPClear(standardError); + [[NSNotificationCenter defaultCenter] removeObserver:self + name:NSFileHandleDataAvailableNotification + object:nil]; - SPClear(taskEnvironment); - SPClear(taskArguments); + // If the task closed unexpectedly, alert appropriately + if (connectionState != SPMySQLProxyIdle) { + connectionState = SPMySQLProxyIdle; + taskExitedUnexpectedly = YES; + [self setLastError:NSLocalizedString(@"The SSH Tunnel has unexpectedly closed.", @"SSH tunnel unexpectedly closed")]; + if (delegate) [delegate performSelectorOnMainThread:stateChangeSelector withObject:self waitUntilDone:NO]; + } - [pool release]; + // Run the run loop for a short time to ensure all task/pipe callbacks are dealt with + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; + + SPClear(taskEnvironment); + SPClear(taskArguments); + } } /* diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m index d2c3550b..b29ebf7a 100644 --- a/Source/SPTableContent.m +++ b/Source/SPTableContent.m @@ -1335,27 +1335,25 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper - (void)reloadTableTask { - NSAutoreleasePool *reloadPool = [[NSAutoreleasePool alloc] init]; + @autoreleasepool { + // Check whether a save of the current row is required, abort if pending changes couldn't be saved. + if ([[self onMainThread] saveRowOnDeselect]) { - // Check whether a save of the current row is required, abort if pending changes couldn't be saved. - if ([[self onMainThread] saveRowOnDeselect]) { + // Save view details to restore safely if possible (except viewport, which will be + // preserved automatically, and can then be scrolled as the table loads) + [[self onMainThread] storeCurrentDetailsForRestoration]; + [self setViewportToRestore:NSZeroRect]; - // Save view details to restore safely if possible (except viewport, which will be - // preserved automatically, and can then be scrolled as the table loads) - [[self onMainThread] storeCurrentDetailsForRestoration]; - [self setViewportToRestore:NSZeroRect]; + // Clear the table data column cache and status (including counts) + [tableDataInstance resetColumnData]; + [tableDataInstance resetStatusData]; - // Clear the table data column cache and status (including counts) - [tableDataInstance resetColumnData]; - [tableDataInstance resetStatusData]; + // Load the table's data + [self loadTable:[tablesListInstance tableName]]; + } - // Load the table's data - [self loadTable:[tablesListInstance tableName]]; + [tableDocumentInstance endTask]; } - - [tableDocumentInstance endTask]; - - [reloadPool drain]; } /** @@ -1447,22 +1445,21 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper } - (void)filterTableTask { - NSAutoreleasePool *filterPool = [[NSAutoreleasePool alloc] init]; - + @autoreleasepool { #ifndef SP_CODA - // Update history - [spHistoryControllerInstance updateHistoryEntries]; + // Update history + [spHistoryControllerInstance updateHistoryEntries]; #endif - // Reset and reload data using the new filter settings - [self setSelectionToRestore:[self selectionDetailsAllowingIndexSelection:NO]]; - previousTableRowsCount = 0; - [self clearTableValues]; - [self loadTableValues]; - [[tableContentView onMainThread] scrollPoint:NSMakePoint(0.0f, 0.0f)]; + // Reset and reload data using the new filter settings + [self setSelectionToRestore:[self selectionDetailsAllowingIndexSelection:NO]]; + previousTableRowsCount = 0; + [self clearTableValues]; + [self loadTableValues]; + [[tableContentView onMainThread] scrollPoint:NSMakePoint(0.0f, 0.0f)]; - [tableDocumentInstance endTask]; - [filterPool drain]; + [tableDocumentInstance endTask]; + } } /** @@ -1530,89 +1527,86 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper - (void)sortTableTaskWithColumn:(NSTableColumn *)tableColumn { - NSAutoreleasePool *sortPool = [[NSAutoreleasePool alloc] init]; - - // Check whether a save of the current row is required. - if (![[self onMainThread] saveRowOnDeselect]) { + @autoreleasepool { + // Check whether a save of the current row is required. + if (![[self onMainThread] saveRowOnDeselect]) { + // If the save failed, cancel the sort task and return + [tableDocumentInstance endTask]; + return; + } - // If the save failed, cancel the sort task and return - [tableDocumentInstance endTask]; - [sortPool drain]; - return; - } - - NSUInteger modifierFlags = [[NSApp currentEvent] modifierFlags]; - - // Sets column order as tri-state descending, ascending, no sort, descending, ascending etc. order if the same - // header is clicked several times - if (sortCol && [[tableColumn identifier] integerValue] == [sortCol integerValue]) { - BOOL invert = NO; - if (modifierFlags & NSShiftKeyMask) { - invert = YES; - } - - // this is the same as saying (isDesc && !invert) || (!isDesc && invert) - if (isDesc != invert) { - SPClear(sortCol); - } - else { - isDesc = !isDesc; + NSUInteger modifierFlags = [[NSApp currentEvent] modifierFlags]; + + // Sets column order as tri-state descending, ascending, no sort, descending, ascending etc. order if the same + // header is clicked several times + if (sortCol && [[tableColumn identifier] integerValue] == [sortCol integerValue]) { + BOOL invert = NO; + if (modifierFlags & NSShiftKeyMask) { + invert = YES; + } + + // this is the same as saying (isDesc && !invert) || (!isDesc && invert) + if (isDesc != invert) { + SPClear(sortCol); + } + else { + isDesc = !isDesc; + } } - } - else { - // When the column is not sorted, allow to sort in reverse order using Shift+click - if (modifierFlags & NSShiftKeyMask) { - isDesc = YES; - } else { - isDesc = NO; - } - - [[tableContentView onMainThread] setIndicatorImage:nil inTableColumn:[tableContentView tableColumnWithIdentifier:[NSString stringWithFormat:@"%lld", (long long)[sortCol integerValue]]]]; - - if (sortCol) [sortCol release]; - - sortCol = [[NSNumber alloc] initWithInteger:[[tableColumn identifier] integerValue]]; - } - - if (sortCol) { - // Set the highlight and indicatorImage - [[tableContentView onMainThread] setHighlightedTableColumn:tableColumn]; - - if (isDesc) { - [[tableContentView onMainThread] setIndicatorImage:[NSImage imageNamed:@"NSDescendingSortIndicator"] inTableColumn:tableColumn]; - } else { - [[tableContentView onMainThread] setIndicatorImage:[NSImage imageNamed:@"NSAscendingSortIndicator"] inTableColumn:tableColumn]; + // When the column is not sorted, allow to sort in reverse order using Shift+click + if (modifierFlags & NSShiftKeyMask) { + isDesc = YES; + } else { + isDesc = NO; + } + + [[tableContentView onMainThread] setIndicatorImage:nil inTableColumn:[tableContentView tableColumnWithIdentifier:[NSString stringWithFormat:@"%lld", (long long)[sortCol integerValue]]]]; + + if (sortCol) [sortCol release]; + + sortCol = [[NSNumber alloc] initWithInteger:[[tableColumn identifier] integerValue]]; } - } - else { - // If no sort order deselect column header and - // remove indicator image - [[tableContentView onMainThread] setHighlightedTableColumn:nil]; - [[tableContentView onMainThread] setIndicatorImage:nil inTableColumn:tableColumn]; - } - - // Update data using the new sort order - previousTableRowsCount = tableRowsCount; - [self setSelectionToRestore:[self selectionDetailsAllowingIndexSelection:NO]]; - [[tableContentView onMainThread] selectRowIndexes:[NSIndexSet indexSet] byExtendingSelection:NO]; - [self loadTableValues]; - - if ([mySQLConnection queryErrored] && ![mySQLConnection lastQueryWasCancelled]) { - SPOnewayAlertSheet( - NSLocalizedString(@"Error", @"error"), - [tableDocumentInstance parentWindow], - [NSString stringWithFormat:NSLocalizedString(@"Couldn't sort table. MySQL said: %@", @"message of panel when sorting of table failed"), [mySQLConnection lastErrorMessage]] - ); - + + SPMainQSync(^{ + if (sortCol) { + // Set the highlight and indicatorImage + [tableContentView setHighlightedTableColumn:tableColumn]; + + if (isDesc) { + [tableContentView setIndicatorImage:[NSImage imageNamed:@"NSDescendingSortIndicator"] inTableColumn:tableColumn]; + } + else { + [tableContentView setIndicatorImage:[NSImage imageNamed:@"NSAscendingSortIndicator"] inTableColumn:tableColumn]; + } + } + else { + // If no sort order deselect column header and + // remove indicator image + [tableContentView setHighlightedTableColumn:nil]; + [tableContentView setIndicatorImage:nil inTableColumn:tableColumn]; + } + }); + + // Update data using the new sort order + previousTableRowsCount = tableRowsCount; + [self setSelectionToRestore:[self selectionDetailsAllowingIndexSelection:NO]]; + [[tableContentView onMainThread] selectRowIndexes:[NSIndexSet indexSet] byExtendingSelection:NO]; + [self loadTableValues]; + + if ([mySQLConnection queryErrored] && ![mySQLConnection lastQueryWasCancelled]) { + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [tableDocumentInstance parentWindow], + [NSString stringWithFormat:NSLocalizedString(@"Couldn't sort table. MySQL said: %@", @"message of panel when sorting of table failed"), [mySQLConnection lastErrorMessage]] + ); + + [tableDocumentInstance endTask]; + return; + } + [tableDocumentInstance endTask]; - [sortPool drain]; - - return; } - - [tableDocumentInstance endTask]; - [sortPool drain]; } #pragma mark - @@ -2522,83 +2516,80 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper - (void)clickLinkArrowTask:(SPTextAndLinkCell *)theArrowCell { - NSAutoreleasePool *linkPool = [[NSAutoreleasePool alloc] init]; - NSUInteger dataColumnIndex = [[[[tableContentView tableColumns] objectAtIndex:[theArrowCell getClickedColumn]] identifier] integerValue]; - BOOL tableFilterRequired = NO; - - // Ensure the clicked cell has foreign key details available - NSDictionary *columnDefinition = [dataColumns objectAtIndex:dataColumnIndex]; - NSDictionary *refDictionary = [columnDefinition objectForKey:@"foreignkeyreference"]; - if (!refDictionary) { - [linkPool release]; - return; - } + @autoreleasepool { + NSUInteger dataColumnIndex = [[[[tableContentView tableColumns] objectAtIndex:[theArrowCell getClickedColumn]] identifier] integerValue]; + BOOL tableFilterRequired = NO; + + // Ensure the clicked cell has foreign key details available + NSDictionary *columnDefinition = [dataColumns objectAtIndex:dataColumnIndex]; + NSDictionary *refDictionary = [columnDefinition objectForKey:@"foreignkeyreference"]; + if (!refDictionary) { + return; + } #ifndef SP_CODA - // Save existing scroll position and details and mark that state is being modified - [spHistoryControllerInstance updateHistoryEntries]; - [spHistoryControllerInstance setModifyingState:YES]; + // Save existing scroll position and details and mark that state is being modified + [spHistoryControllerInstance updateHistoryEntries]; + [spHistoryControllerInstance setModifyingState:YES]; #endif - NSString *targetFilterValue = [tableValues cellDataAtRow:[theArrowCell getClickedRow] column:dataColumnIndex]; + NSString *targetFilterValue = [tableValues cellDataAtRow:[theArrowCell getClickedRow] column:dataColumnIndex]; - //when navigating binary relations (eg. raw UUID) do so via a hex-encoded value for charset safety - BOOL navigateAsHex = ([targetFilterValue isKindOfClass:[NSData class]] && [[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"binary"]); - if(navigateAsHex) targetFilterValue = [mySQLConnection escapeData:(NSData *)targetFilterValue includingQuotes:NO]; - - // If the link is within the current table, apply filter settings manually - if ([[refDictionary objectForKey:@"table"] isEqualToString:selectedTable]) { - SPMainQSync(^{ - [fieldField selectItemWithTitle:[refDictionary objectForKey:@"column"]]; - [self setCompareTypes:self]; - if ([targetFilterValue isNSNull]) { - [compareField selectItemWithTitle:@"IS NULL"]; - } - else { - if(navigateAsHex) [compareField selectItemWithTitle:@"= (Hex String)"]; - [argumentField setStringValue:targetFilterValue]; - } - }); - tableFilterRequired = YES; - } else { - NSString *filterComparison = nil; - if([targetFilterValue isNSNull]) filterComparison = @"IS NULL"; - else if(navigateAsHex) filterComparison = @"= (Hex String)"; - - // Store the filter details to use when loading the target table - NSDictionary *filterSettings = @{ - @"filterField": [refDictionary objectForKey:@"column"], - @"filterValue": targetFilterValue, - @"filterComparison": SPBoxNil(filterComparison) - }; - SPMainQSync(^{ - [self setFiltersToRestore:filterSettings]; - - // Attempt to switch to the target table - if (![tablesListInstance selectItemWithName:[refDictionary objectForKey:@"table"]]) { - NSBeep(); - [self setFiltersToRestore:nil]; - } - }); - } + //when navigating binary relations (eg. raw UUID) do so via a hex-encoded value for charset safety + BOOL navigateAsHex = ([targetFilterValue isKindOfClass:[NSData class]] && [[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"binary"]); + if(navigateAsHex) targetFilterValue = [mySQLConnection escapeData:(NSData *)targetFilterValue includingQuotes:NO]; + + // If the link is within the current table, apply filter settings manually + if ([[refDictionary objectForKey:@"table"] isEqualToString:selectedTable]) { + SPMainQSync(^{ + [fieldField selectItemWithTitle:[refDictionary objectForKey:@"column"]]; + [self setCompareTypes:self]; + if ([targetFilterValue isNSNull]) { + [compareField selectItemWithTitle:@"IS NULL"]; + } + else { + if(navigateAsHex) [compareField selectItemWithTitle:@"= (Hex String)"]; + [argumentField setStringValue:targetFilterValue]; + } + }); + tableFilterRequired = YES; + } + else { + NSString *filterComparison = nil; + if([targetFilterValue isNSNull]) filterComparison = @"IS NULL"; + else if(navigateAsHex) filterComparison = @"= (Hex String)"; + + // Store the filter details to use when loading the target table + NSDictionary *filterSettings = @{ + @"filterField": [refDictionary objectForKey:@"column"], + @"filterValue": targetFilterValue, + @"filterComparison": SPBoxNil(filterComparison) + }; + SPMainQSync(^{ + [self setFiltersToRestore:filterSettings]; + + // Attempt to switch to the target table + if (![tablesListInstance selectItemWithName:[refDictionary objectForKey:@"table"]]) { + NSBeep(); + [self setFiltersToRestore:nil]; + } + }); + } #ifndef SP_CODA - // End state and ensure a new history entry - [spHistoryControllerInstance setModifyingState:NO]; - [spHistoryControllerInstance updateHistoryEntries]; + // End state and ensure a new history entry + [spHistoryControllerInstance setModifyingState:NO]; + [spHistoryControllerInstance updateHistoryEntries]; #endif - // End the task - [tableDocumentInstance endTask]; + // End the task + [tableDocumentInstance endTask]; #ifndef SP_CODA - // If the same table is the target, trigger a filter task on the main thread - if (tableFilterRequired) - [self performSelectorOnMainThread:@selector(filterTable:) withObject:self waitUntilDone:NO]; + // If the same table is the target, trigger a filter task on the main thread + if (tableFilterRequired) [self performSelectorOnMainThread:@selector(filterTable:) withObject:self waitUntilDone:NO]; #endif - - // Empty the loading pool and exit the thread - [linkPool drain]; + } } - (void)contentFiltersHaveBeenUpdated:(NSNotification *)notification diff --git a/Source/SPTableStructure.m b/Source/SPTableStructure.m index d3723f86..29be2fee 100644 --- a/Source/SPTableStructure.m +++ b/Source/SPTableStructure.m @@ -1473,68 +1473,65 @@ static void _BuildMenuWithPills(NSMenu *menu,struct _cmpMap *map,size_t mapEntri */ - (void)_removeFieldAndForeignKey:(NSNumber *)removeForeignKey { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - // Remove the foreign key before the field if required - if ([removeForeignKey boolValue]) { - - NSString *relationName = @""; - NSString *field = [[tableFields objectAtIndex:[tableSourceView selectedRow]] objectForKey:@"name"]; - - // Get the foreign key name - for (NSDictionary *constraint in [tableDataInstance getConstraints]) - { - for (NSString *column in [constraint objectForKey:@"columns"]) + @autoreleasepool { + // Remove the foreign key before the field if required + if ([removeForeignKey boolValue]) { + NSString *relationName = @""; + NSString *field = [[tableFields objectAtIndex:[tableSourceView selectedRow]] objectForKey:@"name"]; + + // Get the foreign key name + for (NSDictionary *constraint in [tableDataInstance getConstraints]) { - if ([column isEqualToString:field]) { - relationName = [constraint objectForKey:@"name"]; - break; + for (NSString *column in [constraint objectForKey:@"columns"]) + { + if ([column isEqualToString:field]) { + relationName = [constraint objectForKey:@"name"]; + break; + } } } + + [mySQLConnection queryString:[NSString stringWithFormat:@"ALTER TABLE %@ DROP FOREIGN KEY %@", [selectedTable backtickQuotedString], [relationName backtickQuotedString]]]; + + // Check for errors, but only if the query wasn't cancelled + if ([mySQLConnection queryErrored] && ![mySQLConnection lastQueryWasCancelled]) { + NSMutableDictionary *errorDictionary = [NSMutableDictionary dictionary]; + [errorDictionary setObject:NSLocalizedString(@"Unable to delete relation", @"error deleting relation message") forKey:@"title"]; + [errorDictionary setObject:[NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to delete the relation '%@'.\n\nMySQL said: %@", @"error deleting relation informative message"), relationName, [mySQLConnection lastErrorMessage]] forKey:@"message"]; + [[self onMainThread] showErrorSheetWith:errorDictionary]; + } } - [mySQLConnection queryString:[NSString stringWithFormat:@"ALTER TABLE %@ DROP FOREIGN KEY %@", [selectedTable backtickQuotedString], [relationName backtickQuotedString]]]; + // Remove field + [mySQLConnection queryString:[NSString stringWithFormat:@"ALTER TABLE %@ DROP %@", + [selectedTable backtickQuotedString], [[[tableFields objectAtIndex:[tableSourceView selectedRow]] objectForKey:@"name"] backtickQuotedString]]]; // Check for errors, but only if the query wasn't cancelled if ([mySQLConnection queryErrored] && ![mySQLConnection lastQueryWasCancelled]) { NSMutableDictionary *errorDictionary = [NSMutableDictionary dictionary]; - [errorDictionary setObject:NSLocalizedString(@"Unable to delete relation", @"error deleting relation message") forKey:@"title"]; - [errorDictionary setObject:[NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to delete the relation '%@'.\n\nMySQL said: %@", @"error deleting relation informative message"), relationName, [mySQLConnection lastErrorMessage]] forKey:@"message"]; + [errorDictionary setObject:NSLocalizedString(@"Error", @"error") forKey:@"title"]; + [errorDictionary setObject:[NSString stringWithFormat:NSLocalizedString(@"Couldn't delete field %@.\nMySQL said: %@", @"message of panel when field cannot be deleted"), + [[tableFields objectAtIndex:[tableSourceView selectedRow]] objectForKey:@"name"], + [mySQLConnection lastErrorMessage]] forKey:@"message"]; + [[self onMainThread] showErrorSheetWith:errorDictionary]; } - } + else { + [tableDataInstance resetAllData]; - // Remove field - [mySQLConnection queryString:[NSString stringWithFormat:@"ALTER TABLE %@ DROP %@", - [selectedTable backtickQuotedString], [[[tableFields objectAtIndex:[tableSourceView selectedRow]] objectForKey:@"name"] backtickQuotedString]]]; - - // Check for errors, but only if the query wasn't cancelled - if ([mySQLConnection queryErrored] && ![mySQLConnection lastQueryWasCancelled]) { - NSMutableDictionary *errorDictionary = [NSMutableDictionary dictionary]; - [errorDictionary setObject:NSLocalizedString(@"Error", @"error") forKey:@"title"]; - [errorDictionary setObject:[NSString stringWithFormat:NSLocalizedString(@"Couldn't delete field %@.\nMySQL said: %@", @"message of panel when field cannot be deleted"), - [[tableFields objectAtIndex:[tableSourceView selectedRow]] objectForKey:@"name"], - [mySQLConnection lastErrorMessage]] forKey:@"message"]; - - [[self onMainThread] showErrorSheetWith:errorDictionary]; - } - else { - [tableDataInstance resetAllData]; - - // Refresh relevant views - [tableDocumentInstance setStatusRequiresReload:YES]; - [tableDocumentInstance setContentRequiresReload:YES]; - [tableDocumentInstance setRelationsRequiresReload:YES]; - - [self loadTable:selectedTable]; - } + // Refresh relevant views + [tableDocumentInstance setStatusRequiresReload:YES]; + [tableDocumentInstance setContentRequiresReload:YES]; + [tableDocumentInstance setRelationsRequiresReload:YES]; - [tableDocumentInstance endTask]; + [self loadTable:selectedTable]; + } - // Preserve focus on table for keyboard navigation - [[tableDocumentInstance parentWindow] makeFirstResponder:tableSourceView]; + [tableDocumentInstance endTask]; - [pool drain]; + // Preserve focus on table for keyboard navigation + [[tableDocumentInstance parentWindow] makeFirstResponder:tableSourceView]; + } } #pragma mark - diff --git a/Source/SPTablesList.m b/Source/SPTablesList.m index eceb57f2..ab620645 100644 --- a/Source/SPTablesList.m +++ b/Source/SPTablesList.m @@ -2350,117 +2350,121 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; return; } - NSAutoreleasePool *tableAdditionPool = [[NSAutoreleasePool alloc] init]; - - [tableDocumentInstance startTaskWithDescription:[NSString stringWithFormat:NSLocalizedString(@"Creating %@...", @"Creating table task string"), [tableNameField stringValue]]]; + @autoreleasepool { + [tableDocumentInstance startTaskWithDescription:[NSString stringWithFormat:NSLocalizedString(@"Creating %@...", @"Creating table task string"), [tableNameField stringValue]]]; - NSString *charSetStatement = @""; - NSString *collationStatement = @""; - NSString *engineStatement = @""; + NSString *charSetStatement = @""; + NSString *collationStatement = @""; + NSString *engineStatement = @""; - NSString *tableType = [tableTypeButton title]; - NSString *tableName = [tableNameField stringValue]; + NSString *tableType = [tableTypeButton title]; + NSString *tableName = [tableNameField stringValue]; - // Ensure the use of UTF8 when creating new tables - BOOL changeEncoding = ![[mySQLConnection encoding] isEqualToString:@"utf8"]; - - if (changeEncoding) { - [mySQLConnection storeEncodingForRestoration]; - [mySQLConnection setEncoding:@"utf8"]; - } + // Ensure the use of UTF8 when creating new tables + BOOL changeEncoding = ![[mySQLConnection encoding] isEqualToString:@"utf8"]; - // If there is an encoding selected other than the default we must specify it in CREATE TABLE statement - NSString *encodingName = [addTableCharsetHelper selectedCharset]; - if (encodingName) - charSetStatement = [NSString stringWithFormat:@"DEFAULT CHARACTER SET %@", [encodingName backtickQuotedString]]; - - // If there is a collation selected other than the default we must specify it in the CREATE TABLE statement - NSString *collationName = [addTableCharsetHelper selectedCollation]; - if (collationName) - collationStatement = [NSString stringWithFormat:@"DEFAULT COLLATE %@",[collationName backtickQuotedString]]; + if (changeEncoding) { + [mySQLConnection storeEncodingForRestoration]; + [mySQLConnection setEncoding:@"utf8"]; + } - // If there is a type selected other than the default we must specify it in CREATE TABLE statement - if ([tableTypeButton indexOfSelectedItem] > 0) { - engineStatement = [NSString stringWithFormat:@"%@ = %@", [[tableDocumentInstance serverSupport] engineTypeQueryName], [[tableDocumentInstance serverSupport] supportsQuotingEngineTypeInCreateSyntax] ? [tableType backtickQuotedString] : tableType]; - } + // If there is an encoding selected other than the default we must specify it in CREATE TABLE statement + NSString *encodingName = [addTableCharsetHelper selectedCharset]; + if (encodingName) charSetStatement = [NSString stringWithFormat:@"DEFAULT CHARACTER SET %@", [encodingName backtickQuotedString]]; - NSString *createStatement = [NSString stringWithFormat:@"CREATE TABLE %@ (id INT(11) UNSIGNED NOT NULL%@) %@ %@ %@", [tableName backtickQuotedString], [tableType isEqualToString:@"CSV"] ? @"" : @" PRIMARY KEY AUTO_INCREMENT", charSetStatement, collationStatement, engineStatement]; + // If there is a collation selected other than the default we must specify it in the CREATE TABLE statement + NSString *collationName = [addTableCharsetHelper selectedCollation]; + if (collationName) collationStatement = [NSString stringWithFormat:@"DEFAULT COLLATE %@",[collationName backtickQuotedString]]; - // Create the table - [mySQLConnection queryString:createStatement]; + // If there is a type selected other than the default we must specify it in CREATE TABLE statement + if ([tableTypeButton indexOfSelectedItem] > 0) { + engineStatement = [NSString stringWithFormat:@"%@ = %@", [[tableDocumentInstance serverSupport] engineTypeQueryName], [[tableDocumentInstance serverSupport] supportsQuotingEngineTypeInCreateSyntax] ? [tableType backtickQuotedString] : tableType]; + } - if (![mySQLConnection queryErrored]) { + NSString *createStatement = [NSString stringWithFormat:@"CREATE TABLE %@ (id INT(11) UNSIGNED NOT NULL%@) %@ %@ %@", [tableName backtickQuotedString], [tableType isEqualToString:@"CSV"] ? @"" : @" PRIMARY KEY AUTO_INCREMENT", charSetStatement, collationStatement, engineStatement]; - // Table creation was successful - insert the new item into the tables list and select it. - NSInteger addItemAtIndex = NSNotFound; + // Create the table + [mySQLConnection queryString:createStatement]; - for (NSUInteger i = 0; i < [tables count]; i++) - { - NSInteger eachTableType = [[tableTypes objectAtIndex:i] integerValue]; + if (![mySQLConnection queryErrored]) { - if (eachTableType == SPTableTypeNone) continue; - if (eachTableType == SPTableTypeProc || eachTableType == SPTableTypeFunc) { - addItemAtIndex = (i - 1); - break; + // Table creation was successful - insert the new item into the tables list and select it. + NSInteger addItemAtIndex = NSNotFound; + + for (NSUInteger i = 0; i < [tables count]; i++) + { + NSInteger eachTableType = [[tableTypes objectAtIndex:i] integerValue]; + + if (eachTableType == SPTableTypeNone) continue; + if (eachTableType == SPTableTypeProc || eachTableType == SPTableTypeFunc) { + addItemAtIndex = (i - 1); + break; + } + + if ([tableName localizedCompare:[tables objectAtIndex:i]] == NSOrderedAscending) { + addItemAtIndex = i; + break; + } } - if ([tableName localizedCompare:[tables objectAtIndex:i]] == NSOrderedAscending) { - addItemAtIndex = i; - break; + if (addItemAtIndex == NSNotFound) { + [tables addObject:tableName]; + [tableTypes addObject:[NSNumber numberWithInteger:SPTableTypeTable]]; + } + else { + [tables insertObject:tableName atIndex:addItemAtIndex]; + [tableTypes insertObject:[NSNumber numberWithInteger:SPTableTypeTable] atIndex:addItemAtIndex]; } - } - if (addItemAtIndex == NSNotFound) { - [tables addObject:tableName]; - [tableTypes addObject:[NSNumber numberWithInteger:SPTableTypeTable]]; - } - else { - [tables insertObject:tableName atIndex:addItemAtIndex]; - [tableTypes insertObject:[NSNumber numberWithInteger:SPTableTypeTable] atIndex:addItemAtIndex]; - } + // Set the selected table name and type, and then update the filter list and the + // selection. + if (selectedTableName) [selectedTableName release]; - // Set the selected table name and type, and then update the filter list and the - // selection. - if (selectedTableName) [selectedTableName release]; - - selectedTableName = [[NSString alloc] initWithString:tableName]; - selectedTableType = SPTableTypeTable; + selectedTableName = [[NSString alloc] initWithString:tableName]; + selectedTableType = SPTableTypeTable; - [[self onMainThread] updateFilter:self]; - [[tablesListView onMainThread] scrollRowToVisible:[tablesListView selectedRow]]; + [[self onMainThread] updateFilter:self]; + [[tablesListView onMainThread] scrollRowToVisible:[tablesListView selectedRow]]; - // Select the newly created table and switch to the table structure view for easier setup - [tableDocumentInstance loadTable:selectedTableName ofType:selectedTableType]; + // Select the newly created table and switch to the table structure view for easier setup + [tableDocumentInstance loadTable:selectedTableName ofType:selectedTableType]; #ifndef SP_CODA - [tableDocumentInstance viewStructure:self]; + [tableDocumentInstance viewStructure:self]; #endif #ifdef SP_CODA - [sidebarViewController setTableNames:[self allTableNames] selectedTableName:selectedTableName]; + [sidebarViewController setTableNames:[self allTableNames] selectedTableName:selectedTableName]; #endif - // Query the structure of all databases in the background (mainly for completion) - [[tableDocumentInstance databaseStructureRetrieval] queryDbStructureInBackgroundWithUserInfo:@{@"forceUpdate" : @YES}]; - } - else { - // Error while creating new table - alertSheetOpened = YES; + // Query the structure of all databases in the background (mainly for completion) + [[tableDocumentInstance databaseStructureRetrieval] queryDbStructureInBackgroundWithUserInfo:@{@"forceUpdate" : @YES}]; + } + else { + // Error while creating new table + alertSheetOpened = YES; + + SPBeginAlertSheet( + NSLocalizedString(@"Error adding new table", @"error adding new table message"), + NSLocalizedString(@"OK", @"OK button"), + nil, + nil, + [tableDocumentInstance parentWindow], + self, + @selector(sheetDidEnd:returnCode:contextInfo:), + SPAddRow, + [NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to add the new table '%@'.\n\nMySQL said: %@", @"error adding new table informative message"), tableName, [mySQLConnection lastErrorMessage]] + ); - SPBeginAlertSheet(NSLocalizedString(@"Error adding new table", @"error adding new table message"), - NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, - @selector(sheetDidEnd:returnCode:contextInfo:), SPAddRow, - [NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to add the new table '%@'.\n\nMySQL said: %@", @"error adding new table informative message"), tableName, [mySQLConnection lastErrorMessage]]); + if (changeEncoding) [mySQLConnection restoreStoredEncoding]; - if (changeEncoding) [mySQLConnection restoreStoredEncoding]; - - [[tablesListView onMainThread] reloadData]; - } + [[tablesListView onMainThread] reloadData]; + } - // Clear table name - [[tableNameField onMainThread] setStringValue:@""]; + // Clear table name + [[tableNameField onMainThread] setStringValue:@""]; - [tableDocumentInstance endTask]; - [tableAdditionPool release]; + [tableDocumentInstance endTask]; + } } #ifndef SP_CODA diff --git a/Source/SPUserManager.m b/Source/SPUserManager.m index 397275cb..27d25a72 100644 --- a/Source/SPUserManager.m +++ b/Source/SPUserManager.m @@ -158,66 +158,64 @@ static NSString *SPSchemaPrivilegesTabIdentifier = @"Schema Privileges"; { isInitializing = YES; // Don't want to do some of the notifications if initializing - NSMutableString *privKey; - NSArray *privRow; - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSMutableArray *usersResultArray = [NSMutableArray array]; - - // Select users from the mysql.user table - SPMySQLResult *result = [connection queryString:@"SELECT * FROM mysql.user ORDER BY user"]; - [result setReturnDataAsStrings:YES]; - //TODO: improve user feedback - NSAssert(([[result fieldNames] firstObjectCommonWithArray:@[@"Password",@"authentication_string"]] != nil), @"Resultset from mysql.user contains neither 'Password' nor 'authentication_string' column!?"); - requiresPost576PasswordHandling = ![[result fieldNames] containsObject:@"Password"]; - [usersResultArray addObjectsFromArray:[result getAllRows]]; - - [self _initializeTree:usersResultArray]; + @autoreleasepool { + NSArray *privRow; + NSMutableArray *usersResultArray = [NSMutableArray array]; - // Set up the array of privs supported by this server. - [[self privsSupportedByServer] removeAllObjects]; - - result = nil; - - // Attempt to obtain user privileges if supported - if ([serverSupport supportsShowPrivileges]) { - - result = [connection queryString:@"SHOW PRIVILEGES"]; + // Select users from the mysql.user table + SPMySQLResult *result = [connection queryString:@"SELECT * FROM mysql.user ORDER BY user"]; [result setReturnDataAsStrings:YES]; - } - - if (result && [result numberOfRows]) { - while ((privRow = [result getRowAsArray])) - { - privKey = [NSMutableString stringWithString:[[privRow objectAtIndex:0] lowercaseString]]; + //TODO: improve user feedback + NSAssert(([[result fieldNames] firstObjectCommonWithArray:@[@"Password",@"authentication_string"]] != nil), @"Resultset from mysql.user contains neither 'Password' nor 'authentication_string' column!?"); + requiresPost576PasswordHandling = ![[result fieldNames] containsObject:@"Password"]; + [usersResultArray addObjectsFromArray:[result getAllRows]]; - // Skip the special "Usage" key - if ([privKey isEqualToString:@"usage"]) continue; - - [privKey replaceOccurrencesOfString:@" " withString:@"_" options:NSLiteralSearch range:NSMakeRange(0, [privKey length])]; - [privKey appendString:@"_priv"]; - - [[self privsSupportedByServer] setValue:@YES forKey:privKey]; + [self _initializeTree:usersResultArray]; + + // Set up the array of privs supported by this server. + [[self privsSupportedByServer] removeAllObjects]; + + result = nil; + + // Attempt to obtain user privileges if supported + if ([serverSupport supportsShowPrivileges]) { + + result = [connection queryString:@"SHOW PRIVILEGES"]; + [result setReturnDataAsStrings:YES]; } - } - // If that fails, base privilege support on the mysql.users columns - else { - result = [connection queryString:@"SHOW COLUMNS FROM mysql.user"]; - - [result setReturnDataAsStrings:YES]; - - while ((privRow = [result getRowAsArray])) - { - privKey = [NSMutableString stringWithString:[privRow objectAtIndex:0]]; - - if (![privKey hasSuffix:@"_priv"]) continue; - - if ([privColumnToGrantMap objectForKey:privKey]) privKey = [privColumnToGrantMap objectForKey:privKey]; - - [[self privsSupportedByServer] setValue:@YES forKey:[privKey lowercaseString]]; + + if (result && [result numberOfRows]) { + while ((privRow = [result getRowAsArray])) + { + NSMutableString *privKey = [NSMutableString stringWithString:[[privRow objectAtIndex:0] lowercaseString]]; + + // Skip the special "Usage" key + if ([privKey isEqualToString:@"usage"]) continue; + + [privKey replaceOccurrencesOfString:@" " withString:@"_" options:NSLiteralSearch range:NSMakeRange(0, [privKey length])]; + [privKey appendString:@"_priv"]; + + [[self privsSupportedByServer] setValue:@YES forKey:privKey]; + } } - } + // If that fails, base privilege support on the mysql.users columns + else { + result = [connection queryString:@"SHOW COLUMNS FROM mysql.user"]; + + [result setReturnDataAsStrings:YES]; + + while ((privRow = [result getRowAsArray])) + { + NSMutableString *privKey = [NSMutableString stringWithString:[privRow objectAtIndex:0]]; + + if (![privKey hasSuffix:@"_priv"]) continue; - [pool release]; + if ([privColumnToGrantMap objectForKey:privKey]) privKey = [privColumnToGrantMap objectForKey:privKey]; + + [[self privsSupportedByServer] setValue:@YES forKey:[privKey lowercaseString]]; + } + } + } isInitializing = NO; } diff --git a/Source/SequelProTunnelAssistant.m b/Source/SequelProTunnelAssistant.m index f014de31..78e5a726 100644 --- a/Source/SequelProTunnelAssistant.m +++ b/Source/SequelProTunnelAssistant.m @@ -37,196 +37,174 @@ int main(int argc, const char *argv[]) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - NSDictionary *environment = [[NSProcessInfo processInfo] environment]; - NSString *argument = nil; - SPSSHTunnel *sequelProTunnel; - NSString *connectionName = [environment objectForKey:@"SP_CONNECTION_NAME"]; - NSString *verificationHash = [environment objectForKey:@"SP_CONNECTION_VERIFY_HASH"]; - - if (![environment objectForKey:@"SP_PASSWORD_METHOD"]) { - [pool release]; - return 1; - } - - if (argc > 1) { - argument = [[[NSString alloc] initWithCString:argv[1] encoding:NSUTF8StringEncoding] autorelease]; - } - - // Check if we're being asked a question and respond if so - if (argument && [argument rangeOfString:@" (yes/no)?"].location != NSNotFound) { - - sequelProTunnel = (SPSSHTunnel *)[NSConnection rootProxyForConnectionWithRegisteredName:connectionName host:nil]; - - if (!sequelProTunnel) { - NSLog(@"SSH Tunnel: unable to connect to Sequel Pro to show SSH question"); - [pool release]; + @autoreleasepool { + NSDictionary *environment = [[NSProcessInfo processInfo] environment]; + NSString *argument = nil; + SPSSHTunnel *sequelProTunnel; + NSString *connectionName = [environment objectForKey:@"SP_CONNECTION_NAME"]; + NSString *verificationHash = [environment objectForKey:@"SP_CONNECTION_VERIFY_HASH"]; + + if (![environment objectForKey:@"SP_PASSWORD_METHOD"]) { return 1; - } - - BOOL response = [sequelProTunnel getResponseForQuestion:argument]; - - if (response) { - printf("yes\n"); - } - else { - printf("no\n"); + + if (argc > 1) { + argument = [[[NSString alloc] initWithCString:argv[1] encoding:NSUTF8StringEncoding] autorelease]; } - - [pool release]; - - return 0; - } - - // Check whether we're being asked for a standard SSH password - if so, use the app-entered value. - if (argument && [[argument lowercaseString] rangeOfString:@"password:"].location != NSNotFound ) { - - // If the password method is set to use the keychain, use the supplied keychain name to - // request the password - if ([[environment objectForKey:@"SP_PASSWORD_METHOD"] integerValue] == SPSSHPasswordUsesKeychain) { - SPKeychain *keychain; - NSString *keychainName = [[environment objectForKey:@"SP_KEYCHAIN_ITEM_NAME"] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; - NSString *keychainAccount = [[environment objectForKey:@"SP_KEYCHAIN_ITEM_ACCOUNT"] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; - - if (!keychainName || !keychainAccount) { - NSLog(@"SSH Tunnel: keychain authentication specified but insufficient internal details supplied"); - [pool release]; + + // Check if we're being asked a question and respond if so + if (argument && [argument rangeOfString:@" (yes/no)?"].location != NSNotFound) { + + sequelProTunnel = (SPSSHTunnel *)[NSConnection rootProxyForConnectionWithRegisteredName:connectionName host:nil]; + + if (!sequelProTunnel) { + NSLog(@"SSH Tunnel: unable to connect to Sequel Pro to show SSH question"); return 1; + } - keychain = [[SPKeychain alloc] init]; - - if ([keychain passwordExistsForName:keychainName account:keychainAccount]) { - printf("%s\n", [[keychain getPasswordForName:keychainName account:keychainAccount] UTF8String]); + BOOL response = [sequelProTunnel getResponseForQuestion:argument]; + + if (response) { + printf("yes\n"); + } + else { + printf("no\n"); + } + + return 0; + } + + // Check whether we're being asked for a standard SSH password - if so, use the app-entered value. + if (argument && [[argument lowercaseString] rangeOfString:@"password:"].location != NSNotFound ) { + + // If the password method is set to use the keychain, use the supplied keychain name to + // request the password + if ([[environment objectForKey:@"SP_PASSWORD_METHOD"] integerValue] == SPSSHPasswordUsesKeychain) { + SPKeychain *keychain; + NSString *keychainName = [[environment objectForKey:@"SP_KEYCHAIN_ITEM_NAME"] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + NSString *keychainAccount = [[environment objectForKey:@"SP_KEYCHAIN_ITEM_ACCOUNT"] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + + if (!keychainName || !keychainAccount) { + NSLog(@"SSH Tunnel: keychain authentication specified but insufficient internal details supplied"); + return 1; + } + + keychain = [[SPKeychain alloc] init]; + + if ([keychain passwordExistsForName:keychainName account:keychainAccount]) { + printf("%s\n", [[keychain getPasswordForName:keychainName account:keychainAccount] UTF8String]); + [keychain release]; + return 0; + } + [keychain release]; - [pool release]; - return 0; + + // If retrieving the password failed, log an error and fall back to requesting from the GUI + NSLog(@"SSH Tunnel: specified keychain password not found"); + + argument = [NSString stringWithFormat:NSLocalizedString(@"The SSH password could not be loaded from the keychain; please enter the SSH password for %@:", @"Prompt for SSH password when keychain fetch failed"), connectionName]; } - - [keychain release]; - // If retrieving the password failed, log an error and fall back to requesting from the GUI - NSLog(@"SSH Tunnel: specified keychain password not found"); - - argument = [NSString stringWithFormat:NSLocalizedString(@"The SSH password could not be loaded from the keychain; please enter the SSH password for %@:", @"Prompt for SSH password when keychain fetch failed"), connectionName]; + // If the password method is set to request the password from the tunnel instance, do so. + if ([[environment objectForKey:@"SP_PASSWORD_METHOD"] integerValue] == SPSSHPasswordAsksUI) { + NSString *password; + + if (!connectionName || !verificationHash) { + NSLog(@"SSH Tunnel: internal authentication specified but insufficient details supplied"); + return 1; + } + + sequelProTunnel = (SPSSHTunnel *)[NSConnection rootProxyForConnectionWithRegisteredName:connectionName host:nil]; + + if (!sequelProTunnel) { + NSLog(@"SSH Tunnel: unable to connect to Sequel Pro for internal authentication"); + return 1; + } + + password = [sequelProTunnel getPasswordWithVerificationHash:verificationHash]; + + if (password) { + printf("%s\n", [password UTF8String]); + return 0; + } + + // If retrieving the password failed, log an error and fall back to requesting from the GUI + NSLog(@"SSH Tunnel: unable to successfully request password from Sequel Pro for internal authentication"); + + argument = [NSString stringWithFormat:NSLocalizedString(@"The SSH password could not be loaded; please enter the SSH password for %@:", @"Prompt for SSH password when direct fetch failed"), connectionName]; + } } - // If the password method is set to request the password from the tunnel instance, do so. - if ([[environment objectForKey:@"SP_PASSWORD_METHOD"] integerValue] == SPSSHPasswordAsksUI) { - NSString *password; - - if (!connectionName || !verificationHash) { - NSLog(@"SSH Tunnel: internal authentication specified but insufficient details supplied"); - [pool release]; + // Check whether we're being asked for a SSH key passphrase + if (argument && [[argument lowercaseString] rangeOfString:@"enter passphrase for"].location != NSNotFound ) { + NSString *passphrase; + NSString *keyName = [argument stringByMatching:@"^\\s*Enter passphrase for key \\'(.*)\\':\\s*$" capture:1L]; + + if (keyName) { + + // Check whether the passphrase is in the keychain, using standard OS X sshagent name and account + SPKeychain *keychain = [[SPKeychain alloc] init]; + + if ([keychain passwordExistsForName:@"SSH" account:keyName]) { + printf("%s\n", [[keychain getPasswordForName:@"SSH" account:keyName] UTF8String]); + [keychain release]; + return 0; + } + + [keychain release]; + } + + // Not found in the keychain - we need to ask the GUI. + + if (!verificationHash) { + NSLog(@"SSH Tunnel: key passphrase authentication required but insufficient details supplied to connect to GUI"); return 1; } sequelProTunnel = (SPSSHTunnel *)[NSConnection rootProxyForConnectionWithRegisteredName:connectionName host:nil]; - + if (!sequelProTunnel) { - NSLog(@"SSH Tunnel: unable to connect to Sequel Pro for internal authentication"); - [pool release]; + NSLog(@"SSH Tunnel: unable to connect to Sequel Pro to show SSH question"); return 1; } - - password = [sequelProTunnel getPasswordWithVerificationHash:verificationHash]; - - if (password) { - printf("%s\n", [password UTF8String]); - [pool release]; - return 0; + passphrase = [sequelProTunnel getPasswordForQuery:argument verificationHash:verificationHash]; + + if (!passphrase) { + return 1; } - - // If retrieving the password failed, log an error and fall back to requesting from the GUI - NSLog(@"SSH Tunnel: unable to successfully request password from Sequel Pro for internal authentication"); - - argument = [NSString stringWithFormat:NSLocalizedString(@"The SSH password could not be loaded; please enter the SSH password for %@:", @"Prompt for SSH password when direct fetch failed"), connectionName]; + + printf("%s\n", [passphrase UTF8String]); + + return 0; } - } - // Check whether we're being asked for a SSH key passphrase - if (argument && [[argument lowercaseString] rangeOfString:@"enter passphrase for"].location != NSNotFound ) { - NSString *passphrase; - NSString *keyName = [argument stringByMatching:@"^\\s*Enter passphrase for key \\'(.*)\\':\\s*$" capture:1L]; - - if (keyName) { - - // Check whether the passphrase is in the keychain, using standard OS X sshagent name and account - SPKeychain *keychain = [[SPKeychain alloc] init]; - - if ([keychain passwordExistsForName:@"SSH" account:keyName]) { - printf("%s\n", [[keychain getPasswordForName:@"SSH" account:keyName] UTF8String]); - [keychain release]; - [pool release]; - return 0; + // SSH has some other question. Show that directly to the user. This is an attempt to support RSA SecurID + if (argument) { + NSString *passphrase; + + if (!verificationHash) { + NSLog(@"SSH Tunnel: key passphrase authentication required but insufficient details supplied to connect to GUI"); + return 1; } - - [keychain release]; - } - - // Not found in the keychain - we need to ask the GUI. - if (!verificationHash) { - NSLog(@"SSH Tunnel: key passphrase authentication required but insufficient details supplied to connect to GUI"); - [pool release]; - return 1; - } + sequelProTunnel = (SPSSHTunnel *)[NSConnection rootProxyForConnectionWithRegisteredName:connectionName host:nil]; - sequelProTunnel = (SPSSHTunnel *)[NSConnection rootProxyForConnectionWithRegisteredName:connectionName host:nil]; - - if (!sequelProTunnel) { - NSLog(@"SSH Tunnel: unable to connect to Sequel Pro to show SSH question"); - [pool release]; - return 1; - } - passphrase = [sequelProTunnel getPasswordForQuery:argument verificationHash:verificationHash]; - - if (!passphrase) { - [pool release]; - return 1; - } + if (!sequelProTunnel) { + NSLog(@"SSH Tunnel: unable to connect to Sequel Pro to show SSH question"); + return 1; + } - printf("%s\n", [passphrase UTF8String]); - - [pool release]; - - return 0; - } - - // SSH has some other question. Show that directly to the user. This is an attempt to support RSA SecurID - if (argument) { - NSString *passphrase; - - if (!verificationHash) { - NSLog(@"SSH Tunnel: key passphrase authentication required but insufficient details supplied to connect to GUI"); - [pool release]; - return 1; - } + passphrase = [sequelProTunnel getPasswordForQuery:argument verificationHash:verificationHash]; - sequelProTunnel = (SPSSHTunnel *)[NSConnection rootProxyForConnectionWithRegisteredName:connectionName host:nil]; - - if (!sequelProTunnel) { - NSLog(@"SSH Tunnel: unable to connect to Sequel Pro to show SSH question"); - [pool release]; - return 1; - } - - passphrase = [sequelProTunnel getPasswordForQuery:argument verificationHash:verificationHash]; - - if (!passphrase) { - [pool release]; - return 1; - } + if (!passphrase) { + return 1; + } - printf("%s\n", [passphrase UTF8String]); - [pool release]; - return 0; + printf("%s\n", [passphrase UTF8String]); + return 0; + } } - - - [pool release]; return 1; } diff --git a/Source/xibLocalizationPostprocessor.m b/Source/xibLocalizationPostprocessor.m index 1806d6b2..b75b3a9c 100644 --- a/Source/xibLocalizationPostprocessor.m +++ b/Source/xibLocalizationPostprocessor.m @@ -9,12 +9,12 @@ NSDictionary *load_kv_pairs(NSString *input); int main(int argc, const char *argv[]) { - NSAutoreleasePool *autoreleasePool = [[NSAutoreleasePool alloc] init]; { - if (argc != 3 && argc != 4) { - fprintf(stderr, "Usage: xibLocalizationPostprocessor inputfile outputfile (Replace IB keys with their English string value)\n"); + @autoreleasepool { + if (argc != 3 && argc != 4) { + fprintf(stderr, "Usage: xibLocalizationPostprocessor inputfile outputfile (Replace IB keys with their English string value)\n"); fprintf(stderr, " xibLocalizationPostprocessor transfile inputfile outputfile (Reverse mode: Change English keys back to IB keys in translation)\n"); - exit (-1); - } + exit(-1); + } NSUInteger inputFileIndex = 1; NSDictionary *translatedStrings = nil; @@ -31,38 +31,38 @@ int main(int argc, const char *argv[]) inputFileIndex++; } - NSError *error = nil; - NSStringEncoding usedEncoding; - NSString *rawXIBStrings = [NSString stringWithContentsOfFile:[NSString stringWithUTF8String:argv[inputFileIndex]] usedEncoding:&usedEncoding error:&error]; - if (error) { - fprintf(stderr, "Error reading inputfile %s: %s\n", argv[inputFileIndex], error.localizedDescription.UTF8String); - exit (-1); - } - - NSMutableString *outputStrings = [NSMutableString string]; - NSUInteger lineCount = 0; - NSString *lastComment = nil; - for (NSString *line in [rawXIBStrings componentsSeparatedByString:@"\n"]) { - lineCount++; - - if ([line hasPrefix:@"/*"]) { // eg: /* Class = "NSMenuItem"; title = "Quit Library"; ObjectID = "136"; */ - lastComment = line; - continue; + NSError *error = nil; + NSStringEncoding usedEncoding; + NSString *rawXIBStrings = [NSString stringWithContentsOfFile:[NSString stringWithUTF8String:argv[inputFileIndex]] usedEncoding:&usedEncoding error:&error]; + if (error) { + fprintf(stderr, "Error reading inputfile %s: %s\n", argv[inputFileIndex], error.localizedDescription.UTF8String); + exit(-1); + } + + NSMutableString *outputStrings = [NSMutableString string]; + NSUInteger lineCount = 0; + NSString *lastComment = nil; + for (NSString *line in [rawXIBStrings componentsSeparatedByString:@"\n"]) { + lineCount++; + + if ([line hasPrefix:@"/*"]) { // eg: /* Class = "NSMenuItem"; title = "Quit Library"; ObjectID = "136"; */ + lastComment = line; + continue; + + } else if (line.length == 0) { + lastComment = nil; + continue; - } else if (line.length == 0) { - lastComment = nil; - continue; + } else if ([line hasPrefix:@"\""] && [line hasSuffix:@"\";"]) { // eg: "136.title" = "Quit Library"; - } else if ([line hasPrefix:@"\""] && [line hasSuffix:@"\";"]) { // eg: "136.title" = "Quit Library"; - - NSRange quoteEqualsQuoteRange = [line rangeOfString:@"\" = \""]; - if (quoteEqualsQuoteRange.length && NSMaxRange(quoteEqualsQuoteRange) < line.length - 1) { - if (lastComment) { - [outputStrings appendString:lastComment]; - [outputStrings appendString:@"\n"]; - } + NSRange quoteEqualsQuoteRange = [line rangeOfString:@"\" = \""]; + if (quoteEqualsQuoteRange.length && NSMaxRange(quoteEqualsQuoteRange) < line.length - 1) { + if (lastComment) { + [outputStrings appendString:lastComment]; + [outputStrings appendString:@"\n"]; + } NSString *stringNeedingLocalization = [line substringFromIndex:NSMaxRange(quoteEqualsQuoteRange)]; // chop off leading: "blah" = " - stringNeedingLocalization = [stringNeedingLocalization substringToIndex:stringNeedingLocalization.length - 2]; // chop off trailing: "; + stringNeedingLocalization = [stringNeedingLocalization substringToIndex:stringNeedingLocalization.length - 2]; // chop off trailing: "; if(translatedStrings) { NSString *translation = [translatedStrings objectForKey:stringNeedingLocalization]; if(!translation) { @@ -74,19 +74,19 @@ int main(int argc, const char *argv[]) else { [outputStrings appendFormat:@"\"%@\" = \"%@\";\n\n", stringNeedingLocalization, stringNeedingLocalization]; } - - continue; - } - } - - NSLog(@"Warning: skipped garbage input line %lu, contents: \"%@\"", (unsigned long)lineCount, line); - } - - if (outputStrings.length && ![outputStrings writeToFile:[NSString stringWithUTF8String:argv[inputFileIndex+1]] atomically:NO encoding:usedEncoding error:&error]) { - fprintf(stderr, "Error writing %s: %s\n", argv[inputFileIndex+1], error.localizedDescription.UTF8String); - exit (-1); - } - } [autoreleasePool release]; + + continue; + } + } + + NSLog(@"Warning: skipped garbage input line %lu, contents: \"%@\"", (unsigned long) lineCount, line); + } + + if (outputStrings.length && ![outputStrings writeToFile:[NSString stringWithUTF8String:argv[inputFileIndex + 1]] atomically:NO encoding:usedEncoding error:&error]) { + fprintf(stderr, "Error writing %s: %s\n", argv[inputFileIndex + 1], error.localizedDescription.UTF8String); + exit(-1); + } + } } NSDictionary *load_kv_pairs(NSString *input) |