diff options
-rw-r--r-- | Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h | 3 | ||||
-rw-r--r-- | Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m | 6 | ||||
-rw-r--r-- | Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m | 6 | ||||
-rw-r--r-- | Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m | 3 | ||||
-rw-r--r-- | Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m | 61 | ||||
-rw-r--r-- | Resources/English.lproj/Localizable.strings | bin | 230192 -> 229994 bytes | |||
-rw-r--r-- | Source/SPEditSheetTextView.m | 17 | ||||
-rw-r--r-- | Source/SPExportFilenameUtilities.m | 28 | ||||
-rw-r--r-- | Source/SPExtendedTableInfo.m | 1 | ||||
-rw-r--r-- | Source/SPFieldEditorController.h | 3 | ||||
-rw-r--r-- | Source/SPFieldEditorController.m | 17 | ||||
-rw-r--r-- | Source/SPNarrowDownCompletion.m | 2 | ||||
-rw-r--r-- | Source/SPWindow.h | 5 | ||||
-rw-r--r-- | Source/SPWindow.m | 28 |
14 files changed, 137 insertions, 43 deletions
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h b/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h index 5772eb72..ee7f6039 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h +++ b/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h @@ -39,7 +39,6 @@ #import "Locking.h" #import "Conversion.h" - @interface SPMySQLConnection (PrivateAPI) - (MYSQL *)_makeRawMySQLConnectionWithEncoding:(NSString *)encodingName isMasterConnection:(BOOL)isMaster; @@ -47,6 +46,8 @@ - (void)_updateConnectionVariables; - (void)_restoreConnectionVariables; - (BOOL)_checkConnectionIfNecessary; +- (void)_validateThreadSetup; ++ (void)_removeThreadVariables:(NSNotification *)aNotification; @end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m index 3ce0c0cd..3201b55f 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m @@ -187,6 +187,9 @@ void _backgroundPingTask(void *ptr) // Set up a cleanup routine pthread_cleanup_push(_pingThreadCleanup, pingDetails); + // Initialise MySQL variables and handling on this thread + mysql_thread_init(); + // Set up a signal handler for SIGUSR1, to handle forced timeouts. signal(SIGUSR1, _forceThreadExit); @@ -209,6 +212,9 @@ void _pingThreadCleanup(void *pingDetails) { SPMySQLConnectionPingDetails *pingDetailsStruct = pingDetails; *(pingDetailsStruct->keepAlivePingActivePointer) = NO; + + // Clean up MySQL variables and handlers + mysql_thread_end(); } @end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m index 5df71e96..46615cae 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m @@ -76,6 +76,9 @@ } if (![self _checkConnectionIfNecessary]) return nil; + // Ensure per-thread variables are set up + [self _validateThreadSetup]; + // Perform a lossy conversion to bytes, using NSData to do the hard work. Preserves // nul characters correctly. NSData *cData = [theString dataUsingEncoding:stringEncoding allowLossyConversion:YES]; @@ -229,6 +232,9 @@ return nil; } + // Ensure per-thread variables are set up + [self _validateThreadSetup]; + // Check the connection if necessary, returning nil if the query couldn't be validated if (![self _checkConnectionIfNecessary]) return nil; diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m index f695d977..1022ccd1 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m @@ -137,6 +137,9 @@ // Lock the connection before using it [self _lockConnection]; + // Ensure per-thread variables are set up + [self _validateThreadSetup]; + // Get the process list MYSQL_RES *mysqlResult = mysql_list_processes(mySQLConnection); diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m index 800157ca..d42b82e6 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m @@ -36,6 +36,9 @@ #include <pthread.h> #include <SystemConfiguration/SCNetworkReachability.h> +// Thread flag constant +static pthread_key_t mySQLThreadInitFlagKey; +static void *mySQLThreadFlag; #pragma mark Class constants @@ -76,6 +79,24 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS #pragma mark Initialisation and teardown /** + * In the one-off class initialisation, set up MySQL as necessary + */ ++ (void)initialize +{ + + // Set up a pthread thread-specific data key to be used across all classes and threads + pthread_key_create(&mySQLThreadInitFlagKey, NULL); + mySQLThreadFlag = malloc(1); + + // MySQL requires mysql_library_init() to be called before any other MySQL + // functions are used; although mysql_init() will call it automatically, it + // won't do so in a thread-safe manner, so setting it up first is safer. + // No arguments are required. + // Note that this will install MySQL's SIGPIPE handler. + mysql_library_init(0, NULL, NULL); +} + +/** * Initialise the SPMySQLConnection object, setting up class defaults. * * Typically initialisation would be followed by setting the connection details @@ -631,6 +652,13 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS MYSQL *theConnection = mysql_init(NULL); if (!theConnection) return NULL; + // Calling mysql_init will have automatically installed per-thread variables if necessary, + // so track their installation for removal and to avoid recreating again. + if (!pthread_getspecific(mySQLThreadInitFlagKey)) { + pthread_setspecific(mySQLThreadInitFlagKey, &mySQLThreadFlag); + [(NSNotificationCenter *)[NSNotificationCenter defaultCenter] addObserver:[self class] selector:@selector(_removeThreadVariables:) name:NSThreadWillExitNotification object:[NSThread currentThread]]; + } + // Disable automatic reconnection, as it's handled in-framework to preserve // options, encodings and connection state. my_bool falseMyBool = FALSE; @@ -835,4 +863,37 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS // Otherwise check the connection return [self checkConnection]; } + +/** + * Ensure that the thread this method is called on has been registered for + * use with MySQL. MySQL requires thread-specific variables for safe + * execution. + */ +- (void)_validateThreadSetup +{ + + // Check to see whether the handler has already been installed + if (pthread_getspecific(mySQLThreadInitFlagKey)) return; + + // If not, install it + mysql_thread_init(); + + // Mark the thread to avoid multiple installs + pthread_setspecific(mySQLThreadInitFlagKey, &mySQLThreadFlag); + + // Set up the notification handler to deregister it + [(NSNotificationCenter *)[NSNotificationCenter defaultCenter] addObserver:[self class] selector:@selector(_removeThreadVariables:) name:NSThreadWillExitNotification object:[NSThread currentThread]]; +} + +/** + * Remove the MySQL variables and handlers from each closing thread which + * has had them installed to avoid memory leaks. + * This is a class method for easy global tracking; it will be called on the appropriate + * thread automatically. + */ ++ (void)_removeThreadVariables:(NSNotification *)aNotification +{ + mysql_thread_end(); +} + @end diff --git a/Resources/English.lproj/Localizable.strings b/Resources/English.lproj/Localizable.strings Binary files differindex 6643741c..bb958cf7 100644 --- a/Resources/English.lproj/Localizable.strings +++ b/Resources/English.lproj/Localizable.strings diff --git a/Source/SPEditSheetTextView.m b/Source/SPEditSheetTextView.m index b028c177..a5d110a6 100644 --- a/Source/SPEditSheetTextView.m +++ b/Source/SPEditSheetTextView.m @@ -343,21 +343,4 @@ [self saveChangedFontInUserDefaults]; } -/** - * Needed to allow Find Panel inside the textView if it runs in a sheet - */ -- (BOOL)becomeFirstResponder -{ - return YES; -} - -/** - * Needed to allow Find Panel inside the textView if it runs in a sheet - */ -- (BOOL)resignFirstResponder -{ - return YES; -} - - @end diff --git a/Source/SPExportFilenameUtilities.m b/Source/SPExportFilenameUtilities.m index ad5ab36b..1f37a561 100644 --- a/Source/SPExportFilenameUtilities.m +++ b/Source/SPExportFilenameUtilities.m @@ -65,32 +65,38 @@ BOOL isCSV = exportType == SPCSVExport; BOOL isDot = exportType == SPDotExport; BOOL isXML = exportType == SPXMLExport; - - NSString *tokens = NSLocalizedString(@"host,database,table,date,year,month,day,time", @"default custom export filename tokens");; - NSString *tokensWithoutTable = NSLocalizedString(@"host,database,date,year,month,day,time", @"custom export filename tokens without table"); - + + NSMutableArray *exportTokens = [NSMutableArray arrayWithObjects: + NSLocalizedString(@"host", @"export filename host token"), + NSLocalizedString(@"database", @"export filename database token"), + NSLocalizedString(@"table", @"table"), + NSLocalizedString(@"date", @"export filename date token"), + NSLocalizedString(@"year", @"export filename date token"), + NSLocalizedString(@"month", @"export filename date token"), + NSLocalizedString(@"day", @"export filename date token"), + NSLocalizedString(@"time", @"export filename time token"), + nil]; + + // Determine whether to remove the table from the tokens list if (exportSource == SPQueryExport || isDot) { - tokens = tokensWithoutTable; + removeTable = YES; } else if (isSQL || isCSV || isXML) { for (NSArray *table in tables) { if ([NSArrayObjectAtIndex(table, 2) boolValue]) { i++; - removeTable = YES; - if (i == 2) break; } } if (i > 1) { removeTable = isSQL ? YES : ![exportFilePerTableCheck state]; - - tokens = isSQL ? tokensWithoutTable : ([exportFilePerTableCheck state] ? tokens : tokensWithoutTable); } } if (removeTable) { + [exportTokens removeObject:NSLocalizedString(@"table", @"table")]; NSArray *tokenParts = [exportCustomFilenameTokenField objectValue]; for (id token in [exportCustomFilenameTokenField objectValue]) @@ -107,8 +113,8 @@ } } } - - [exportCustomFilenameTokensField setStringValue:tokens]; + + [exportCustomFilenameTokensField setStringValue:[exportTokens componentsJoinedByString:@","]]; } /** diff --git a/Source/SPExtendedTableInfo.m b/Source/SPExtendedTableInfo.m index 23f3c426..260c90fd 100644 --- a/Source/SPExtendedTableInfo.m +++ b/Source/SPExtendedTableInfo.m @@ -526,6 +526,7 @@ static NSString *SPUpdateTableTypeNewType = @"SPUpdateTableTypeNewType"; */ - (void)confirmChangeTableTypeDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(NSDictionary *)contextInfo { + [[alert window] orderOut:self]; if (returnCode == NSAlertDefaultReturn) { [self _changeCurrentTableTypeFrom:[contextInfo objectForKey:SPUpdateTableTypeCurrentType] to:[contextInfo objectForKey:SPUpdateTableTypeNewType]]; diff --git a/Source/SPFieldEditorController.h b/Source/SPFieldEditorController.h index f45b6070..da78785b 100644 --- a/Source/SPFieldEditorController.h +++ b/Source/SPFieldEditorController.h @@ -22,6 +22,7 @@ // // More info at <http://code.google.com/p/sequel-pro/> +@class SPWindow; /** * @class SPFieldEditorController SPFieldEditorController.h @@ -45,7 +46,7 @@ IBOutlet id hexTextView; IBOutlet id editTextScrollView; IBOutlet id hexTextScrollView; - IBOutlet id editSheet; + IBOutlet SPWindow *editSheet; IBOutlet id editSheetCancelButton; IBOutlet id editSheetIsNotEditableCancelButton; IBOutlet id editSheetOkButton; diff --git a/Source/SPFieldEditorController.m b/Source/SPFieldEditorController.m index 3a4ee14c..f94af537 100644 --- a/Source/SPFieldEditorController.m +++ b/Source/SPFieldEditorController.m @@ -32,6 +32,7 @@ #import "SPTooltip.h" #import "SPGeometryDataView.h" #import "SPCopyTable.h" +#import "SPWindow.h" #include <objc/objc-runtime.h> #import "SPCustomQuery.h" #import "SPTableContent.h" @@ -85,6 +86,10 @@ // Allow the user to enter cmd+return to close the edit sheet in addition to fn+return [editSheetOkButton setKeyEquivalentModifierMask:NSCommandKeyMask]; + // Permit the field edit sheet to become main if necessary; this allows fields within the sheet to + // support full interactivity, for example use of the NSFindPanel inside NSTextViews. + [editSheet setIsSheetWhichCanBecomeMain:YES]; + allowUndo = NO; selectionChanged = NO; @@ -431,19 +436,7 @@ editSheetWillBeInitialized = NO; [editSheetProgressBar stopAnimation:self]; - - // The field editor sheet runs as sheet thus a NSTextView won't respond to the Find Panel - // since the Find Panel validate its buttons against [[NSApp mainWindow] firstResponder] == NSTextView. - // After ordering out this sheet SPCopyTable remains the first responder thus set it hard. - // This only works in conjunction with [NSTextView becomeFirstResponder] and [NSTextView resignFirstResponder] - // which has to return YES. -#ifndef SP_REFACTOR - if([[self window] firstResponder] == editTextView) - [[NSApp mainWindow] makeFirstResponder:[[self window] firstResponder]]; -#endif - } - } /** diff --git a/Source/SPNarrowDownCompletion.m b/Source/SPNarrowDownCompletion.m index db3f5e3a..76336069 100644 --- a/Source/SPNarrowDownCompletion.m +++ b/Source/SPNarrowDownCompletion.m @@ -862,7 +862,7 @@ break; } } - else if(key == NSCarriageReturnCharacter || (key == NSTabCharacter && !triggerMode)) + else if(key == NSCarriageReturnCharacter || key == NSEnterCharacter || key == NSRightArrowFunctionKey || (key == NSTabCharacter && !triggerMode)) { [self completeAndInsertSnippet]; } diff --git a/Source/SPWindow.h b/Source/SPWindow.h index 74c4f804..9809f3d3 100644 --- a/Source/SPWindow.h +++ b/Source/SPWindow.h @@ -25,5 +25,10 @@ #import <Cocoa/Cocoa.h> @interface SPWindow : NSWindow +{ + BOOL isSheetWhichCanBecomeMain; +} + +@property (assign) BOOL isSheetWhichCanBecomeMain; @end diff --git a/Source/SPWindow.m b/Source/SPWindow.m index 0285ce8a..d023b7b4 100644 --- a/Source/SPWindow.m +++ b/Source/SPWindow.m @@ -27,6 +27,8 @@ @implementation SPWindow +@synthesize isSheetWhichCanBecomeMain; + #pragma mark - #pragma mark Keyboard shortcut additions @@ -116,6 +118,9 @@ [super sendEvent:theEvent]; } +#pragma mark - +#pragma mark Undo manager handling + /** * If this window is controlled by an SPWindowController, and thus supports being asked * for the frontmost SPTableDocument, request the undoController for that table @@ -130,4 +135,27 @@ return [super undoManager]; } +#pragma mark - +#pragma mark Method overrides + +/** + * Allow sheets to become main if necessary, for example if they are acting as an + * editor for a window. + */ +- (BOOL)canBecomeMainWindow +{ + + // If this window is a sheet which is permitted to become main, respond appropriately + if ([self isSheet] && isSheetWhichCanBecomeMain) { + return [self isVisible]; + } + + // Otherwise, if this window has a sheet attached which can become main, return NO. + if ([[self attachedSheet] isKindOfClass:[SPWindow class]] && [(SPWindow *)[self attachedSheet] isSheetWhichCanBecomeMain]) { + return NO; + } + + return [super canBecomeMainWindow]; +} + @end |