diff options
author | stuconnolly <stuart02@gmail.com> | 2011-02-20 13:06:06 +0000 |
---|---|---|
committer | stuconnolly <stuart02@gmail.com> | 2011-02-20 13:06:06 +0000 |
commit | bea72ac3bbebb9e35e34b840968b4ba0f433e87a (patch) | |
tree | a285cfc5ced8eca11849980b9064ec274703a231 /Source | |
parent | 2e31a4ad75b8e47a4feacadd567fbfab65eeede9 (diff) | |
download | sequelpro-bea72ac3bbebb9e35e34b840968b4ba0f433e87a.tar.gz sequelpro-bea72ac3bbebb9e35e34b840968b4ba0f433e87a.tar.bz2 sequelpro-bea72ac3bbebb9e35e34b840968b4ba0f433e87a.zip |
Bring outline view branch up to date with trunk (r3188:r3201).
Diffstat (limited to 'Source')
-rw-r--r-- | Source/NoodleLineNumberView.h | 12 | ||||
-rw-r--r-- | Source/NoodleLineNumberView.m | 117 | ||||
-rw-r--r-- | Source/SPConnectionHandler.m | 7 | ||||
-rw-r--r-- | Source/SPCustomQuery.m | 7 | ||||
-rw-r--r-- | Source/SPDataImport.m | 2 | ||||
-rw-r--r-- | Source/SPDatabaseViewController.m | 25 | ||||
-rw-r--r-- | Source/SPSQLParser.m | 33 | ||||
-rw-r--r-- | Source/SPSSHTunnel.h | 72 | ||||
-rw-r--r-- | Source/SPSSHTunnel.m | 138 | ||||
-rw-r--r-- | Source/SPTableContent.m | 56 | ||||
-rw-r--r-- | Source/SPTableData.h | 4 | ||||
-rw-r--r-- | Source/SPTableData.m | 119 | ||||
-rw-r--r-- | Source/SPTableStructureDelegate.m | 5 | ||||
-rw-r--r-- | Source/SPTableTriggers.h | 1 | ||||
-rw-r--r-- | Source/SPTableTriggers.m | 41 | ||||
-rw-r--r-- | Source/SPWindow.m | 2 |
16 files changed, 359 insertions, 282 deletions
diff --git a/Source/NoodleLineNumberView.h b/Source/NoodleLineNumberView.h index 1d600720..855f75ee 100644 --- a/Source/NoodleLineNumberView.h +++ b/Source/NoodleLineNumberView.h @@ -48,11 +48,23 @@ CGFloat maxWidthOfGlyph6; CGFloat maxWidthOfGlyph7; CGFloat maxWidthOfGlyph8; + CGFloat currentRuleThickness; NSDictionary *textAttributes; // Add support for selection by clicking/dragging NSUInteger dragSelectionStartLine; + SEL lineNumberForCharacterIndexSel; + IMP lineNumberForCharacterIndexIMP; + SEL lineRangeForRangeSel; + SEL numberWithUnsignedIntegerSel; + IMP numberWithUnsignedIntegerIMP; + SEL addObjectSel; + IMP addObjectIMP; + + NSLayoutManager *layoutManager; + NSTextContainer *container; + } @property(retain) NSColor *alternateTextColor; diff --git a/Source/NoodleLineNumberView.m b/Source/NoodleLineNumberView.m index ebca542d..1d44c939 100644 --- a/Source/NoodleLineNumberView.m +++ b/Source/NoodleLineNumberView.m @@ -50,11 +50,13 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); +// Cache loop methods for speed + #pragma mark - @interface NoodleLineNumberView (Private) -- (NSMutableArray *)lineIndices; +- (NSArray *)lineIndices; - (void)invalidateLineIndices; - (void)calculateLines; - (void)updateGutterThicknessConstants; @@ -80,6 +82,17 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); nil] retain]; maxWidthOfGlyph = [[NSString stringWithString:@"8"] sizeWithAttributes:textAttributes].width; [self updateGutterThicknessConstants]; + currentRuleThickness = 0.0f; + + // Cache loop methods for speed + lineNumberForCharacterIndexSel = @selector(lineNumberForCharacterIndex:); + lineNumberForCharacterIndexIMP = [self methodForSelector:lineNumberForCharacterIndexSel]; + lineRangeForRangeSel = @selector(lineRangeForRange:); + addObjectSel = @selector(addObject:); + numberWithUnsignedIntegerSel = @selector(numberWithUnsignedInteger:); + numberWithUnsignedIntegerIMP = [NSNumber methodForSelector:numberWithUnsignedIntegerSel]; + + } return self; @@ -162,9 +175,13 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); if ((aView != nil) && [aView isKindOfClass:[NSTextView class]]) { + layoutManager = [aView layoutManager]; + container = [aView textContainer]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChange:) name:NSTextStorageDidProcessEditingNotification object:[(NSTextView *)aView textStorage]]; [self invalidateLineIndices]; } + } #pragma mark - @@ -190,8 +207,6 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); NSUInteger line, count, rectCount, i; NSRectArray rects; NSRect visibleRect; - NSLayoutManager *layoutManager; - NSTextContainer *container; NSRange nullRange; NSArray *lines; id view; @@ -207,8 +222,6 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); { nullRange = NSMakeRange(NSNotFound, 0); - layoutManager = [view layoutManager]; - container = [view textContainer]; count = [lines count]; // Find the characters that are currently visible @@ -218,10 +231,6 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); // It doesn't show up in the glyphs so would not be accounted for. range.length++; - // Cache loop methods for speed - SEL lineNumberForCharacterIndexSel = @selector(lineNumberForCharacterIndex:); - IMP lineNumberForCharacterIndexIMP = [self methodForSelector:lineNumberForCharacterIndexSel]; - for (line = (NSUInteger)(*lineNumberForCharacterIndexIMP)(self, lineNumberForCharacterIndexSel, range.location); line < count; line++) { @@ -268,29 +277,6 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); return left; } -- (CGFloat)requiredThickness -{ - NSUInteger lineCount = [[self lineIndices] count]; - if(lineCount < 10) - return maxWidthOfGlyph1; - else if(lineCount < 100) - return maxWidthOfGlyph2; - else if(lineCount < 1000) - return maxWidthOfGlyph3; - else if(lineCount < 10000) - return maxWidthOfGlyph4; - else if(lineCount < 100000) - return maxWidthOfGlyph5; - else if(lineCount < 1000000) - return maxWidthOfGlyph6; - else if(lineCount < 10000000) - return maxWidthOfGlyph7; - else if(lineCount < 100000000) - return maxWidthOfGlyph8; - else - return 100; -} - - (void)drawHashMarksAndLabelsInRect:(NSRect)aRect { id view; @@ -298,21 +284,19 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); bounds = [self bounds]; - if (backgroundColor != nil) - { - [backgroundColor set]; - NSRectFill(bounds); - - [[NSColor colorWithCalibratedWhite:0.58 alpha:1.0] set]; - [NSBezierPath strokeLineFromPoint:NSMakePoint(NSMaxX(bounds) - 0.5, NSMinY(bounds)) toPoint:NSMakePoint(NSMaxX(bounds) - 0.5, NSMaxY(bounds))]; - } + // if (backgroundColor != nil) + // { + // [backgroundColor set]; + // NSRectFill(bounds); + // + // [[NSColor colorWithCalibratedWhite:0.58 alpha:1.0] set]; + // [NSBezierPath strokeLineFromPoint:NSMakePoint(NSMaxX(bounds) - 0.5, NSMinY(bounds)) toPoint:NSMakePoint(NSMaxX(bounds) - 0.5, NSMaxY(bounds))]; + // } view = [self clientView]; if ([view isKindOfClass:[NSTextView class]]) { - NSLayoutManager *layoutManager; - NSTextContainer *container; NSRect visibleRect; NSRange range, nullRange; NSString *labelText; @@ -322,8 +306,6 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); NSSize stringSize; NSArray *lines; - layoutManager = [view layoutManager]; - container = [view textContainer]; nullRange = NSMakeRange(NSNotFound, 0); yinset = [view textContainerInset].height; @@ -345,9 +327,6 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); CGFloat yinsetMinY = yinset - NSMinY(visibleRect); CGFloat rectHeight; - // Cache loop methods for speed - SEL lineNumberForCharacterIndexSel = @selector(lineNumberForCharacterIndex:); - IMP lineNumberForCharacterIndexIMP = [self methodForSelector:lineNumberForCharacterIndexSel]; for (line = (NSUInteger)(*lineNumberForCharacterIndexIMP)(self, lineNumberForCharacterIndexSel, range.location); line < count; line++) { @@ -496,7 +475,7 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); #pragma mark - #pragma mark PrivateAPI -- (NSMutableArray *)lineIndices +- (NSArray *)lineIndices { if (lineIndices == nil) @@ -519,7 +498,8 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); if ([view isKindOfClass:[NSTextView class]]) { - NSUInteger index, stringLength, lineEnd, contentEnd; + + NSUInteger index, stringLength, lineEnd, contentEnd, lastLine; NSString *textString; CGFloat newThickness; @@ -539,26 +519,47 @@ typedef NSRange (*RangeOfLineIMP)(id object, SEL selector, NSRange range); index = 0; // Cache loop methods for speed - SEL lineRangeForRangeSel = @selector(lineRangeForRange:); - SEL addObjectSel = @selector(addObject:); RangeOfLineIMP rangeOfLineIMP = (RangeOfLineIMP)[textString methodForSelector:lineRangeForRangeSel]; - IMP addObjectIMP = [lineIndices methodForSelector:addObjectSel]; - + addObjectIMP = [lineIndices methodForSelector:addObjectSel]; + do { - (void*)(*addObjectIMP)(lineIndices, addObjectSel, [NSNumber numberWithUnsignedInteger:index]); + (void*)(*addObjectIMP)(lineIndices, addObjectSel, (*numberWithUnsignedIntegerIMP)([NSNumber class], numberWithUnsignedIntegerSel, index)); + lastLine = index; index = NSMaxRange((*rangeOfLineIMP)(textString, lineRangeForRangeSel, NSMakeRange(index, 0))); } while (index < stringLength); // Check if text ends with a new line. - [textString getLineStart:NULL end:&lineEnd contentsEnd:&contentEnd forRange:NSMakeRange([[lineIndices lastObject] unsignedIntegerValue], 0)]; + [textString getLineStart:NULL end:&lineEnd contentsEnd:&contentEnd forRange:NSMakeRange(lastLine, 0)]; if (contentEnd < lineEnd) - (void*)(*addObjectIMP)(lineIndices, addObjectSel, [NSNumber numberWithUnsignedInteger:index]); + (void*)(*addObjectIMP)(lineIndices, addObjectSel, (*numberWithUnsignedIntegerIMP)([NSNumber class], numberWithUnsignedIntegerSel, index)); + + NSUInteger lineCount = [lineIndices count]; + if(lineCount < 10) + newThickness = maxWidthOfGlyph1; + else if(lineCount < 100) + newThickness = maxWidthOfGlyph2; + else if(lineCount < 1000) + newThickness = maxWidthOfGlyph3; + else if(lineCount < 10000) + newThickness = maxWidthOfGlyph4; + else if(lineCount < 100000) + newThickness = maxWidthOfGlyph5; + else if(lineCount < 1000000) + newThickness = maxWidthOfGlyph6; + else if(lineCount < 10000000) + newThickness = maxWidthOfGlyph7; + else if(lineCount < 100000000) + newThickness = maxWidthOfGlyph8; + else + newThickness = 100; - newThickness = [self requiredThickness]; - if (fabs([self ruleThickness] - newThickness) > 1) + if (fabs(currentRuleThickness - newThickness) > 1) { + + currentRuleThickness = newThickness; + // Not a good idea to resize the view during calculations (which can happen during // display). Do a delayed perform (using NSInvocation since arg is a float). NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:@selector(setRuleThickness:)]]; diff --git a/Source/SPConnectionHandler.m b/Source/SPConnectionHandler.m index adf21ffa..e66580a9 100644 --- a/Source/SPConnectionHandler.m +++ b/Source/SPConnectionHandler.m @@ -282,6 +282,13 @@ certificateAuthorityCertificatePath:[self sslCACertFileLocationEnabled] ? [self NSInteger newState = [theTunnel state]; + // If the user cancelled the password prompt dialog + if ([theTunnel passwordPromptCancelled]) { + [self _restoreConnectionInterface]; + + return; + } + if (newState == PROXY_STATE_IDLE) { [dbDocument setTitlebarStatus:NSLocalizedString(@"SSH Disconnected", @"SSH disconnected titlebar marker")]; diff --git a/Source/SPCustomQuery.m b/Source/SPCustomQuery.m index b2859010..c43df1cd 100644 --- a/Source/SPCustomQuery.m +++ b/Source/SPCustomQuery.m @@ -882,12 +882,13 @@ [resultData removeAllRows]; pthread_mutex_unlock(&resultDataLock); + // Set the column count on the data store before setting up anything else - + // ensures that SPDataStorage is set up for timer-driven data loads + [resultData setColumnCount:[theResult numOfFields]]; + // Set up the table updates timer [[self onMainThread] initQueryLoadTimer]; - // Set the column count on the data store - [resultData setColumnCount:[theResult numOfFields]]; - // Set up an autorelease pool for row processing dataLoadingPool = [[NSAutoreleasePool alloc] init]; diff --git a/Source/SPDataImport.m b/Source/SPDataImport.m index 0a521cc2..bfde3a26 100644 --- a/Source/SPDataImport.m +++ b/Source/SPDataImport.m @@ -1014,7 +1014,7 @@ insertBaseStringHasEntries = NO; for (i = 0; i < [fieldMappingArray count]; i++) { if ([NSArrayObjectAtIndex(fieldMapperOperator, i) integerValue] == 0) { - if (insertBaseStringHasEntries) [insertBaseString appendString:@","]; + if (insertBaseStringHasEntries) [insertRemainingBaseString appendString:@","]; else insertBaseStringHasEntries = YES; [insertRemainingBaseString appendString:[NSArrayObjectAtIndex(fieldMappingTableColumnNames, i) backtickQuotedString]]; } diff --git a/Source/SPDatabaseViewController.m b/Source/SPDatabaseViewController.m index 03153230..a6600d3f 100644 --- a/Source/SPDatabaseViewController.m +++ b/Source/SPDatabaseViewController.m @@ -280,7 +280,7 @@ [tableSourceInstance loadTable:nil]; [tableContentInstance loadTable:nil]; [[extendedTableInfoInstance onMainThread] loadTable:nil]; - [[tableTriggersInstance onMainThread] loadTriggers]; + [[tableTriggersInstance onMainThread] resetInterface]; structureLoaded = NO; contentLoaded = NO; statusLoaded = NO; @@ -393,8 +393,12 @@ // Update the window title [self updateWindowTitle:self]; - // Reset table information caches + // Reset table information caches and mark that all loaded views require their data reloading [tableDataInstance resetAllData]; + structureLoaded = NO; + contentLoaded = NO; + statusLoaded = NO; + triggersLoaded = NO; // Ensure status and details are fetched using UTF8 NSString *previousEncoding = [mySQLConnection encoding]; @@ -408,7 +412,8 @@ [tableDataInstance updateStatusInformationForCurrentTable]; // Check the current encoding against the table encoding to see whether - // an encoding change and reset is required + // 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 @@ -426,24 +431,12 @@ if (changeEncoding) [mySQLConnection restoreStoredEncoding]; - // Cache table information on the working thread - if (selectedTableType == SPTableTypeView) - [tableDataInstance updateInformationForCurrentView]; - else - [tableDataInstance updateInformationForCurrentTable]; - // Notify listeners of the table change now that the state is fully set up. [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:SPTableChangedNotification object:self]; // Restore view states as appropriate [spHistoryControllerInstance restoreViewStates]; - // Mark that all loaded views require their data reloading - structureLoaded = NO; - contentLoaded = NO; - statusLoaded = NO; - triggersLoaded = NO; - // Load the currently selected view if looking at a table or view if (tableEncoding && (selectedTableType == SPTableTypeView || selectedTableType == SPTableTypeTable)) { @@ -475,7 +468,7 @@ if (!structureLoaded) [tableSourceInstance loadTable:nil]; if (!contentLoaded) [tableContentInstance loadTable:nil]; if (!statusLoaded) [[extendedTableInfoInstance onMainThread] loadTable:nil]; - if (!triggersLoaded) [[tableTriggersInstance onMainThread] loadTriggers]; + if (!triggersLoaded) [[tableTriggersInstance onMainThread] resetInterface]; // Update the "Show Create Syntax" window if it's already opened // according to the selected table/view/proc/func diff --git a/Source/SPSQLParser.m b/Source/SPSQLParser.m index 4cb0aaa4..1650b2c2 100644 --- a/Source/SPSQLParser.m +++ b/Source/SPSQLParser.m @@ -668,6 +668,7 @@ TO_BUFFER_STATE to_scan_string (const char *); // Cache frequently used selectors, avoiding dynamic binding overhead IMP charAtIndex = [self methodForSelector:@selector(_charAtIndex:)]; + SEL charAtIndexSEL = @selector(_charAtIndex:); IMP endIndex = [self methodForSelector:@selector(endIndexOfStringQuotedByCharacter:startingAtIndex:)]; IMP substringWithRange = [self methodForSelector:@selector(substringWithRange:)]; @@ -676,7 +677,7 @@ TO_BUFFER_STATE to_scan_string (const char *); // Walk along the string, processing characters for (currentStringIndex = startIndex + 1; currentStringIndex < stringLength; currentStringIndex++) { - currentCharacter = (unichar)(long)(*charAtIndex)(self, @selector(_charAtIndex:), currentStringIndex); + currentCharacter = (unichar)(long)(*charAtIndex)(self, charAtIndexSEL, currentStringIndex); // Check for the ending character, and if it has been found and quoting/brackets is valid, return. // If delimiter support is active and a delimiter is set, check for the delimiter @@ -722,8 +723,8 @@ TO_BUFFER_STATE to_scan_string (const char *); // For comments starting "--[\s]", ensure the start syntax is valid before proceeding. case '-': if (stringLength < currentStringIndex + 2) break; - if ((unichar)(long)(*charAtIndex)(self, @selector(_charAtIndex:), currentStringIndex+1) != '-') break; - if (![[NSCharacterSet whitespaceCharacterSet] characterIsMember:(unichar)(long)(*charAtIndex)(self, @selector(_charAtIndex:), currentStringIndex+2)]) break; + if ((unichar)(long)(*charAtIndex)(self, charAtIndexSEL, currentStringIndex+1) != '-') break; + if (![[NSCharacterSet whitespaceCharacterSet] characterIsMember:(unichar)(long)(*charAtIndex)(self, charAtIndexSEL, currentStringIndex+2)]) break; currentStringIndex = [self endIndexOfCommentOfType:SPDoubleDashComment startingAtIndex:currentStringIndex]; break; @@ -736,7 +737,7 @@ TO_BUFFER_STATE to_scan_string (const char *); case '/': if(ignoreCommentStrings) break; if (stringLength < currentStringIndex + 1) break; - if ((unichar)(long)(*charAtIndex)(self, @selector(_charAtIndex:), currentStringIndex+1) != '*') break; + if ((unichar)(long)(*charAtIndex)(self, charAtIndexSEL, currentStringIndex+1) != '*') break; currentStringIndex = [self endIndexOfCommentOfType:SPCStyleComment startingAtIndex:currentStringIndex]; break; @@ -754,15 +755,15 @@ TO_BUFFER_STATE to_scan_string (const char *); // and that the "d" is the start of a word if (supportDelimiters && stringLength >= currentStringIndex + 11 && (currentStringIndex == 0 - || [[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:(unichar)(long)(*charAtIndex)(self, @selector(_charAtIndex:), currentStringIndex-1)])) + || [[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:(unichar)(long)(*charAtIndex)(self, charAtIndexSEL, currentStringIndex-1)])) { - switch((unichar)(long)(*charAtIndex)(self, @selector(_charAtIndex:), currentStringIndex+1)) { + switch((unichar)(long)(*charAtIndex)(self, charAtIndexSEL, currentStringIndex+1)) { case 'e': case 'E': - switch((unichar)(long)(*charAtIndex)(self, @selector(_charAtIndex:), currentStringIndex+2)) { + switch((unichar)(long)(*charAtIndex)(self, charAtIndexSEL, currentStringIndex+2)) { case 'l': case 'L': - switch((unichar)(long)(*charAtIndex)(self, @selector(_charAtIndex:), currentStringIndex+3)) { + switch((unichar)(long)(*charAtIndex)(self, charAtIndexSEL, currentStringIndex+3)) { case 'i': case 'I': if([self isMatchedByRegex:@"^(delimiter[ \\t]+(\\S+))(?=\\s|\\Z)" @@ -819,18 +820,19 @@ TO_BUFFER_STATE to_scan_string (const char *); // Cache the charAtIndex selector, avoiding dynamic binding overhead IMP charAtIndex = [self methodForSelector:@selector(_charAtIndex:)]; + SEL charAtIndexSEL = @selector(_charAtIndex:); stringLength = [string length]; // Walk the string looking for the string end for ( currentStringIndex = index; currentStringIndex < stringLength; currentStringIndex++) { - currentCharacter = (unichar)(long)(*charAtIndex)(self, @selector(_charAtIndex:), currentStringIndex); + currentCharacter = (unichar)(long)(*charAtIndex)(self, charAtIndexSEL, currentStringIndex); // If the string end is a backtick and one has been encountered, treat it as end of string if (quoteCharacter == '`' && currentCharacter == '`') { // ...as long as the next character isn't also a backtick, in which case it's being quoted. Skip both. - if ((currentStringIndex + 1) < stringLength && (unichar)(long)(*charAtIndex)(self, @selector(_charAtIndex:), currentStringIndex+1) == '`') { + if ((currentStringIndex + 1) < stringLength && (unichar)(long)(*charAtIndex)(self, charAtIndexSEL, currentStringIndex+1) == '`') { currentStringIndex++; continue; } @@ -844,7 +846,7 @@ TO_BUFFER_STATE to_scan_string (const char *); characterIsEscaped = NO; i = 1; quotedStringLength = currentStringIndex - 1; - while ((quotedStringLength - i) > 0 && (unichar)(long)(*charAtIndex)(self, @selector(_charAtIndex:), currentStringIndex - i) == '\\') { + while ((quotedStringLength - i) > 0 && (unichar)(long)(*charAtIndex)(self, charAtIndexSEL, currentStringIndex - i) == '\\') { characterIsEscaped = !characterIsEscaped; i++; } @@ -852,7 +854,7 @@ TO_BUFFER_STATE to_scan_string (const char *); // If an even number have been found, it may be the end of the string - as long as the subsequent character // isn't also the same character, in which case it's another form of escaping. if (!characterIsEscaped) { - if ((currentStringIndex + 1) < stringLength && (unichar)(long)(*charAtIndex)(self, @selector(_charAtIndex:), currentStringIndex+1) == quoteCharacter) { + if ((currentStringIndex + 1) < stringLength && (unichar)(long)(*charAtIndex)(self, charAtIndexSEL, currentStringIndex+1) == quoteCharacter) { currentStringIndex++; continue; } @@ -876,6 +878,7 @@ TO_BUFFER_STATE to_scan_string (const char *); // Cache the charAtIndex selector, avoiding dynamic binding overhead IMP charAtIndex = [self methodForSelector:@selector(_charAtIndex:)]; + SEL charAtIndexSEL = @selector(_charAtIndex:); switch (commentType) { @@ -888,7 +891,7 @@ TO_BUFFER_STATE to_scan_string (const char *); case SPHashComment: index++; for ( ; index < stringLength; index++ ) { - currentCharacter = (unichar)(long)(*charAtIndex)(self, @selector(_charAtIndex:), index); + currentCharacter = (unichar)(long)(*charAtIndex)(self, charAtIndexSEL, index); if (currentCharacter == '\r') containsCRs = YES; if (currentCharacter == '\r' || currentCharacter == '\n') { return index-1; @@ -901,8 +904,8 @@ TO_BUFFER_STATE to_scan_string (const char *); case SPCStyleComment: index = index+2; for ( ; index < stringLength; index++ ) { - if ((unichar)(long)(*charAtIndex)(self, @selector(_charAtIndex:), index) == '*') { - if ((stringLength > index + 1) && (unichar)(long)(*charAtIndex)(self, @selector(_charAtIndex:), index+1) == '/') { + if ((unichar)(long)(*charAtIndex)(self, charAtIndexSEL, index) == '*') { + if ((stringLength > index + 1) && (unichar)(long)(*charAtIndex)(self, charAtIndexSEL, index+1) == '/') { return (index+1); } } diff --git a/Source/SPSSHTunnel.h b/Source/SPSSHTunnel.h index 067cf42a..4afec738 100644 --- a/Source/SPSSHTunnel.h +++ b/Source/SPSSHTunnel.h @@ -27,18 +27,11 @@ @interface SPSSHTunnel : NSObject <MCPConnectionProxy> { - IBOutlet NSWindow *sshQuestionDialog; - IBOutlet NSTextField *sshQuestionText; - IBOutlet NSButton *sshPasswordKeychainCheckbox; - IBOutlet NSWindow *sshPasswordDialog; - IBOutlet NSTextField *sshPasswordText; - IBOutlet NSSecureTextField *sshPasswordField; + id delegate; NSWindow *parentWindow; NSTask *task; NSPipe *standardError; - id delegate; - SEL stateChangeSelector; NSConnection *tunnelConnection; NSString *lastError; NSString *tunnelConnectionName; @@ -53,9 +46,6 @@ NSString *identityFilePath; NSMutableArray *debugMessages; NSLock *debugMessagesLock; - BOOL useHostFallback; - BOOL requestedResponse; - BOOL passwordInKeychain; NSInteger sshPort; NSInteger remotePort; NSInteger localPort; @@ -64,29 +54,45 @@ NSLock *answerAvailableLock; NSString *currentKeyName; + + SEL stateChangeSelector; + + BOOL useHostFallback; + BOOL requestedResponse; + BOOL passwordInKeychain; + BOOL passwordPromptCancelled; + + IBOutlet NSWindow *sshQuestionDialog; + IBOutlet NSTextField *sshQuestionText; + IBOutlet NSButton *sshPasswordKeychainCheckbox; + IBOutlet NSWindow *sshPasswordDialog; + IBOutlet NSTextField *sshPasswordText; + IBOutlet NSSecureTextField *sshPasswordField; } -- (id) initToHost:(NSString *) theHost port:(NSInteger) thePort login:(NSString *) theLogin tunnellingToPort:(NSInteger) targetPort onHost:(NSString *) targetHost; -- (BOOL) setConnectionStateChangeSelector:(SEL)theStateChangeSelector delegate:(id)theDelegate; -- (void) setParentWindow:(NSWindow *)theWindow; -- (BOOL) setPasswordKeychainName:(NSString *)theName account:(NSString *)theAccount; -- (BOOL) setPassword:(NSString *)thePassword; -- (BOOL) setKeyFilePath:(NSString *)thePath; -- (NSInteger) state; -- (NSString *) lastError; -- (NSString *) debugMessages; -- (NSInteger) localPort; -- (NSInteger) localPortFallback; -- (void) connect; -- (void) launchTask:(id) dummy; -- (void) disconnect; -- (void) standardErrorHandler:(NSNotification*)aNotification; -- (NSString *) getPasswordWithVerificationHash:(NSString *)theHash; -- (BOOL) getResponseForQuestion:(NSString *)theQuestion; -- (void) workerGetResponseForQuestion:(NSString *)theQuestion; -- (NSString *) getPasswordForQuery:(NSString *)theQuery verificationHash:(NSString *)theHash; -- (void) workerGetPasswordForQuery:(NSString *)theQuery; -- (IBAction) closeSSHQuestionSheet:(id)sender; -- (IBAction) closeSSHPasswordSheet:(id)sender; +@property (readonly) BOOL passwordPromptCancelled; + +- (id)initToHost:(NSString *)theHost port:(NSInteger)thePort login:(NSString *)theLogin tunnellingToPort:(NSInteger)targetPort onHost:(NSString *)targetHost; +- (BOOL)setConnectionStateChangeSelector:(SEL)theStateChangeSelector delegate:(id)theDelegate; +- (void)setParentWindow:(NSWindow *)theWindow; +- (BOOL)setPasswordKeychainName:(NSString *)theName account:(NSString *)theAccount; +- (BOOL)setPassword:(NSString *)thePassword; +- (BOOL)setKeyFilePath:(NSString *)thePath; +- (NSInteger)state; +- (NSString *)lastError; +- (NSString *)debugMessages; +- (NSInteger)localPort; +- (NSInteger)localPortFallback; +- (void)connect; +- (void)launchTask:(id)dummy; +- (void)disconnect; +- (void)standardErrorHandler:(NSNotification*)aNotification; +- (NSString *)getPasswordWithVerificationHash:(NSString *)theHash; +- (BOOL)getResponseForQuestion:(NSString *)theQuestion; +- (void)workerGetResponseForQuestion:(NSString *)theQuestion; +- (NSString *)getPasswordForQuery:(NSString *)theQuery verificationHash:(NSString *)theHash; +- (void)workerGetPasswordForQuery:(NSString *)theQuery; +- (IBAction)closeSSHQuestionSheet:(id)sender; +- (IBAction)closeSSHPasswordSheet:(id)sender; @end diff --git a/Source/SPSSHTunnel.m b/Source/SPSSHTunnel.m index 0d4b5993..e112e624 100644 --- a/Source/SPSSHTunnel.m +++ b/Source/SPSSHTunnel.m @@ -32,57 +32,62 @@ @implementation SPSSHTunnel +@synthesize passwordPromptCancelled; + /* * Initialise with the supplied connection details. Host, login and port should all be provided. * The password can either be set later via setPassword:, which stores the password locally and is * therefore not recommended, or via setPasswordKeychainName:, which will use the keychain on-demand * and is therefore preferred. */ -- (id) initToHost:(NSString *) theHost port:(NSInteger) thePort login:(NSString *) theLogin tunnellingToPort:(NSInteger) targetPort onHost:(NSString *) targetHost +- (id)initToHost:(NSString *)theHost port:(NSInteger)thePort login:(NSString *)theLogin tunnellingToPort:(NSInteger)targetPort onHost:(NSString *)targetHost { if (!theHost || !targetPort || !targetHost) return nil; - self = [super init]; - - // Store the connection settings as appropriate - sshHost = [[NSString alloc] initWithString:theHost]; - sshLogin = [[NSString alloc] initWithString:(theLogin?theLogin:@"")]; - sshPort = thePort; - useHostFallback = [theHost isEqualToString:targetHost]; - remoteHost = [[NSString alloc] initWithString:targetHost]; - remotePort = targetPort; - delegate = nil; - stateChangeSelector = nil; - lastError = nil; - debugMessages = [[NSMutableArray alloc] init]; - debugMessagesLock = [[NSLock alloc] init]; - answerAvailableLock = [[NSLock alloc] init]; - - // Set up a connection for use by the tunnel process - tunnelConnectionName = [[NSString alloc] initWithFormat:@"SequelPro-%lu", (unsigned long)[[NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]] hash]]; - tunnelConnectionVerifyHash = [[NSString alloc] initWithFormat:@"%lu", (unsigned long)[[NSString stringWithFormat:@"%f-seeded", [[NSDate date] timeIntervalSince1970]] hash]]; - tunnelConnection = [NSConnection new]; - [tunnelConnection runInNewThread]; - [tunnelConnection removeRunLoop:[NSRunLoop currentRunLoop]]; - [tunnelConnection setRootObject:self]; - if ([tunnelConnection registerName:tunnelConnectionName] == NO) { - return nil; + if ((self = [super init])) { + + // Store the connection settings as appropriate + sshHost = [[NSString alloc] initWithString:theHost]; + sshLogin = [[NSString alloc] initWithString:(theLogin?theLogin:@"")]; + sshPort = thePort; + useHostFallback = [theHost isEqualToString:targetHost]; + remoteHost = [[NSString alloc] initWithString:targetHost]; + remotePort = targetPort; + delegate = nil; + stateChangeSelector = nil; + lastError = nil; + debugMessages = [[NSMutableArray alloc] init]; + debugMessagesLock = [[NSLock alloc] init]; + answerAvailableLock = [[NSLock alloc] init]; + + // Set up a connection for use by the tunnel process + tunnelConnectionName = [[NSString alloc] initWithFormat:@"SequelPro-%lu", (unsigned long)[[NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]] hash]]; + tunnelConnectionVerifyHash = [[NSString alloc] initWithFormat:@"%lu", (unsigned long)[[NSString stringWithFormat:@"%f-seeded", [[NSDate date] timeIntervalSince1970]] hash]]; + tunnelConnection = [NSConnection new]; + + [tunnelConnection runInNewThread]; + [tunnelConnection removeRunLoop:[NSRunLoop currentRunLoop]]; + [tunnelConnection setRootObject:self]; + + if (![tunnelConnection registerName:tunnelConnectionName]) return nil; + + parentWindow = nil; + identityFilePath = nil; + sshQuestionDialog = nil; + sshPasswordDialog = nil; + password = nil; + keychainName = nil; + keychainAccount = nil; + requestedPassphrase = nil; + task = nil; + localPort = 0; + connectionState = PROXY_STATE_IDLE; + + requestedResponse = NO; + passwordInKeychain = NO; + passwordPromptCancelled = NO; } - parentWindow = nil; - identityFilePath = nil; - sshQuestionDialog = nil; - sshPasswordDialog = nil; - password = nil; - keychainName = nil; - keychainAccount = nil; - passwordInKeychain = NO; - requestedPassphrase = nil; - requestedResponse = NO; - task = nil; - localPort = 0; - connectionState = PROXY_STATE_IDLE; - return self; } @@ -90,7 +95,7 @@ * Sets the connection callback selector; a function to be called whenever the tunnel state changes. * The callback function will be called and passed this SSH Tunnel object.. */ -- (BOOL) setConnectionStateChangeSelector:(SEL)theStateChangeSelector delegate:(id)theDelegate +- (BOOL)setConnectionStateChangeSelector:(SEL)theStateChangeSelector delegate:(id)theDelegate { delegate = theDelegate; stateChangeSelector = theStateChangeSelector; @@ -119,7 +124,7 @@ * Sets the password to be stored (and returned to the tunnel authenticator) locally. * Providing a keychain name is much more secure. */ -- (BOOL) setPassword:(NSString *)thePassword +- (BOOL)setPassword:(NSString *)thePassword { if (passwordInKeychain) return NO; password = [[NSString alloc] initWithString:thePassword]; @@ -130,7 +135,7 @@ /** * Sets the path of an identity file, or public key file, to use when connecting. */ -- (BOOL) setKeyFilePath:(NSString *)thePath +- (BOOL)setKeyFilePath:(NSString *)thePath { NSString *expandedPath = [thePath stringByExpandingTildeInPath]; if (![[NSFileManager defaultManager] fileExistsAtPath:expandedPath]) return NO; @@ -144,7 +149,7 @@ * Sets the keychain name to use to retrieve the password. This is the recommended and * secure way of supplying a password to the SSH tunnel. */ -- (BOOL) setPasswordKeychainName:(NSString *)theName account:(NSString *)theAccount +- (BOOL)setPasswordKeychainName:(NSString *)theName account:(NSString *)theAccount { if (password) [password release], password = nil; @@ -158,7 +163,7 @@ /* * Get the state of the connection. */ -- (NSInteger) state +- (NSInteger)state { // See if an auth dialog is up @@ -174,9 +179,10 @@ /* * Returns the last error string, if any. */ -- (NSString *) lastError +- (NSString *)lastError { if (!lastError) return nil; + return [NSString stringWithString:lastError]; } @@ -184,7 +190,7 @@ * Returns all the debug text for this tunnel as a string, separated * by line endings. */ -- (NSString *) debugMessages { +- (NSString *)debugMessages { [debugMessagesLock lock]; NSString *debugMessagesString = [debugMessages componentsJoinedByString:@"\n"]; [debugMessagesLock unlock]; @@ -194,7 +200,7 @@ /* * Initiate the SSH tunnel connection, launching the task in a background thread. */ -- (void) connect +- (void)connect { localPort = 0; @@ -210,7 +216,7 @@ * tunnel to the remote server. * Sets up and tears down as appropriate for usage in a background thread. */ -- (void) launchTask:(id) dummy +- (void)launchTask:(id) dummy { if (connectionState != PROXY_STATE_IDLE || task) return; NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; @@ -482,7 +488,7 @@ /* * Returns the local port assigned for use by the tunnel */ -- (NSInteger) localPort +- (NSInteger)localPort { return localPort; } @@ -490,9 +496,10 @@ /* * Returns the local port assigned for fallback use by the tunnel, if any */ -- (NSInteger) localPortFallback +- (NSInteger)localPortFallback { if (!useHostFallback) return 0; + return localPortFallback; } @@ -513,7 +520,7 @@ * a boolean. This is used by the SSH_ASKPASS environment setting to deal with situations like * host key mismatches. */ -- (BOOL) getResponseForQuestion:(NSString *)theQuestion +- (BOOL)getResponseForQuestion:(NSString *)theQuestion { // Lock the answer available lock [[answerAvailableLock onMainThread] lock]; @@ -530,12 +537,12 @@ // Unlock the lock again [answerAvailableLock unlock]; - //return the answer + // Return the answer return response; } -- (void) workerGetResponseForQuestion:(NSString *)theQuestion -{ +- (void)workerGetResponseForQuestion:(NSString *)theQuestion +{ NSSize questionTextSize; NSRect windowFrameRect; @@ -550,10 +557,11 @@ [NSApp beginSheet:sshQuestionDialog modalForWindow:parentWindow modalDelegate:self didEndSelector:nil contextInfo:nil]; [parentWindow makeKeyAndOrderFront:self]; } + /* * Ends an existing modal session */ -- (IBAction) closeSSHQuestionSheet:(id)sender +- (IBAction)closeSSHQuestionSheet:(id)sender { requestedResponse = [sender tag]==1 ? YES : NO; [NSApp endSheet:sshQuestionDialog]; @@ -565,9 +573,11 @@ * Method to allow an SSH tunnel to request a password. This is used by the program set by the * SSH_ASKPASS environment setting to request passphrases for SSH keys. */ -- (NSString *) getPasswordForQuery:(NSString *)theQuery verificationHash:(NSString *)theHash +- (NSString *)getPasswordForQuery:(NSString *)theQuery verificationHash:(NSString *)theHash { if (![theHash isEqualToString:tunnelConnectionVerifyHash]) return nil; + + if (passwordPromptCancelled) return nil; // Lock the answer available lock [[answerAvailableLock onMainThread] lock]; @@ -591,18 +601,21 @@ // Return the answer return thePassword; } -- (void) workerGetPasswordForQuery:(NSString *)theQuery + +- (void)workerGetPasswordForQuery:(NSString *)theQuery { NSSize queryTextSize; NSRect windowFrameRect; // Work out whether a passphrase is being requested, extracting the key name NSString *keyName = [theQuery stringByMatching:@"^\\s*Enter passphrase for key \\'(.*)\\':\\s*$" capture:1L]; + if (keyName) { [sshPasswordText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Enter your password for the SSH key\n\"%@\"", @"SSH key password prompt"), keyName]]; [sshPasswordKeychainCheckbox setHidden:NO]; currentKeyName = [keyName retain]; - } else { + } + else { [sshPasswordText setStringValue:theQuery]; [sshPasswordKeychainCheckbox setHidden:YES]; currentKeyName = nil; @@ -612,6 +625,7 @@ queryTextSize = [[sshPasswordText cell] cellSizeForBounds:NSMakeRect(0, 0, [sshPasswordText bounds].size.width, 500)]; windowFrameRect = [sshPasswordDialog frame]; windowFrameRect.size.height = ((queryTextSize.height < 40)?40:queryTextSize.height) + 140 + ([sshPasswordDialog isSheet]?0:22); + [sshPasswordDialog setFrame:windowFrameRect display:NO]; [NSApp beginSheet:sshPasswordDialog modalForWindow:parentWindow modalDelegate:self didEndSelector:nil contextInfo:nil]; [parentWindow makeKeyAndOrderFront:self]; @@ -620,9 +634,10 @@ /* * Ends an existing modal session */ -- (IBAction) closeSSHPasswordSheet:(id)sender +- (IBAction)closeSSHPasswordSheet:(id)sender { requestedResponse = [sender tag]==1 ? YES : NO; + [NSApp endSheet:sshPasswordDialog]; [sshPasswordDialog orderOut:nil]; @@ -645,10 +660,13 @@ currentKeyName = nil; } } + + if (!requestedPassphrase) passwordPromptCancelled = YES; [[answerAvailableLock onMainThread] unlock]; } +#pragma mark - - (void)dealloc { diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m index 7acc0187..a0b184dd 100644 --- a/Source/SPTableContent.m +++ b/Source/SPTableContent.m @@ -445,7 +445,13 @@ // Set up the column theCol = [[NSTableColumn alloc] initWithIdentifier:[columnDefinition objectForKey:@"datacolumnindex"]]; [[theCol headerCell] setStringValue:[columnDefinition objectForKey:@"name"]]; - [theCol setHeaderToolTip:[NSString stringWithFormat:@"%@ – %@%@", [columnDefinition objectForKey:@"name"], [columnDefinition objectForKey:@"type"], ([columnDefinition objectForKey:@"length"]) ? [NSString stringWithFormat:@"(%@)", [columnDefinition objectForKey:@"length"]] : @""]]; + [theCol setHeaderToolTip:[NSString stringWithFormat:@"%@ – %@%@%@%@", + [columnDefinition objectForKey:@"name"], + [columnDefinition objectForKey:@"type"], + ([columnDefinition objectForKey:@"length"]) ? [NSString stringWithFormat:@"(%@)", [columnDefinition objectForKey:@"length"]] : @"", + ([columnDefinition objectForKey:@"values"]) ? [NSString stringWithFormat:@"(\n- %@\n)", [[columnDefinition objectForKey:@"values"] componentsJoinedByString:@"\n- "]] : @"", + ([columnDefinition objectForKey:@"comment"] && [[columnDefinition objectForKey:@"comment"] length]) ? [NSString stringWithFormat:@"\n%@", [[columnDefinition objectForKey:@"comment"] stringByReplacingOccurrencesOfString:@"\\n" withString:@"\n"]] : @"" + ]]; [theCol setEditable:YES]; // Set up column for filterTable @@ -799,12 +805,13 @@ BOOL *columnBlobStatuses = malloc(dataColumnsCount * sizeof(BOOL)); tableLoadTargetRowCount = targetRowCount; + // Set the column count on the data store before setting up anything else - + // ensures that SPDataStorage is set up for timer-driven data loads + [tableValues setColumnCount:dataColumnsCount]; + // Set up the table updates timer [[self onMainThread] initTableLoadTimer]; - // Set the column count on the data store - [tableValues setColumnCount:dataColumnsCount]; - NSAutoreleasePool *dataLoadingPool; NSProgressIndicator *dataLoadingIndicator = [tableDocumentInstance valueForKey:@"queryProgressBar"]; BOOL prefsLoadBlobsAsNeeded = [prefs boolForKey:SPLoadBlobsAsNeeded]; @@ -2251,7 +2258,10 @@ return; } + // Retrieve the current field comparison setting for later restoration if possible + NSString *titleToRestore = [[compareField selectedItem] title]; + // Reset the menu before building it back up [compareField removeAllItems]; NSString *fieldTypeGrouping; @@ -2372,6 +2382,10 @@ [menu addItem:item]; [item release]; + // Attempt to reselect the previously selected title, falling back to the first item + [compareField selectItemWithTitle:titleToRestore]; + if (![compareField selectedItem]) [compareField selectItemAtIndex:0]; + // Update the argumentField enabled state [self performSelectorOnMainThread:@selector(toggleFilterField:) withObject:self waitUntilDone:YES]; @@ -3455,7 +3469,7 @@ * - if blob data can be interpret as image data display the image as transparent thumbnail * (up to now using base64 encoded HTML data) */ -- (NSString *)tableView:(NSTableView *)aTableView toolTipForCell:(SPTextAndLinkCell *)aCell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)row mouseLocation:(NSPoint)mouseLocation +- (NSString *)tableView:(NSTableView *)aTableView toolTipForCell:(id)aCell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)row mouseLocation:(NSPoint)mouseLocation { if(aTableView == filterTableView) { @@ -4063,9 +4077,17 @@ BOOL isBlob = [tableDataInstance columnIsBlobOrText:[[aTableColumn headerCell] stringValue]]; BOOL isFieldEditable = YES; + // Retrieve the column defintion + NSDictionary *columnDefinition = nil; + for(id c in cqColumnDefinition) { + if([[c objectForKey:@"datacolumnindex"] isEqualToNumber:[aTableColumn identifier]]) { + columnDefinition = [NSDictionary dictionaryWithDictionary:c]; + break; + } + } // Open the sheet if the multipleLineEditingButton is enabled or the column was a blob or a text. - if ([multipleLineEditingButton state] == NSOnState || isBlob) { + if (([multipleLineEditingButton state] == NSOnState || isBlob) && ![[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"enum"]) { // A table is per definitionem editable isFieldEditable = YES; @@ -4081,19 +4103,13 @@ NSString *fieldEncoding = nil; BOOL allowNULL = YES; - // Retrieve the column defintion - for(id c in cqColumnDefinition) { - if([[c objectForKey:@"datacolumnindex"] isEqualToNumber:[aTableColumn identifier]]) { - fieldType = [c objectForKey:@"type"]; - if([c objectForKey:@"char_length"]) - fieldLength = [[c objectForKey:@"char_length"] integerValue]; - if([c objectForKey:@"null"]) - allowNULL = (![[c objectForKey:@"null"] integerValue]); - if([c objectForKey:@"charset_name"] && ![[c objectForKey:@"charset_name"] isEqualToString:@"binary"]) - fieldEncoding = [c objectForKey:@"charset_name"]; - break; - } - } + fieldType = [columnDefinition objectForKey:@"type"]; + if([columnDefinition objectForKey:@"char_length"]) + fieldLength = [[columnDefinition objectForKey:@"char_length"] integerValue]; + if([columnDefinition objectForKey:@"null"]) + allowNULL = (![[columnDefinition objectForKey:@"null"] integerValue]); + if([columnDefinition objectForKey:@"charset_name"] && ![[columnDefinition objectForKey:@"charset_name"] isEqualToString:@"binary"]) + fieldEncoding = [columnDefinition objectForKey:@"charset_name"]; if(fieldEditor) [fieldEditor release], fieldEditor = nil; fieldEditor = [[SPFieldEditorController alloc] init]; @@ -4393,7 +4409,7 @@ // Check if current edited field is a blob if ((fieldType = [[tableDataInstance columnWithName:[[NSArrayObjectAtIndex([tableContentView tableColumns], column) headerCell] stringValue]] objectForKey:@"typegrouping"]) - && ([fieldType isEqualToString:@"textdata"] || [fieldType isEqualToString:@"blobdata"] || [multipleLineEditingButton state] == NSOnState)) + && ![fieldType isEqualToString:@"enum"] && ([fieldType isEqualToString:@"textdata"] || [fieldType isEqualToString:@"blobdata"] || [multipleLineEditingButton state] == NSOnState)) { [tableContentView setFieldEditorSelectedRange:[fieldEditor selectedRange]]; diff --git a/Source/SPTableData.h b/Source/SPTableData.h index cb24d51f..4503bd03 100644 --- a/Source/SPTableData.h +++ b/Source/SPTableData.h @@ -41,9 +41,9 @@ MCPConnection *mySQLConnection; - BOOL isWorking; - BOOL tableHasAutoIncrementField; + pthread_mutex_t dataProcessingLock; + BOOL tableHasAutoIncrementField; } @property (readwrite, assign) BOOL isWorking; diff --git a/Source/SPTableData.m b/Source/SPTableData.m index 28717864..7ae8f563 100644 --- a/Source/SPTableData.m +++ b/Source/SPTableData.m @@ -31,11 +31,13 @@ #import "RegexKitLite.h" #import "SPServerSupport.h" +@interface SPTableData (PrivateAPI) +- (void)_loopWhileWorking; +@end + @implementation SPTableData -@synthesize isWorking; @synthesize tableHasAutoIncrementField; -; /** * Init class. @@ -52,9 +54,9 @@ tableEncoding = nil; tableCreateSyntax = nil; mySQLConnection = nil; - isWorking = NO; tableHasAutoIncrementField = NO; + pthread_mutex_init(&dataProcessingLock, NULL); } return self; @@ -79,8 +81,8 @@ - (NSString *) tableEncoding { - // Return if CREATE SYNTAX is being parsed - if(isWorking) return nil; + // If processing is already in action, wait for it to complete + [self _loopWhileWorking]; if (tableEncoding == nil) { if ([tableListInstance tableType] == SPTableTypeView) { @@ -98,8 +100,8 @@ - (NSString *) tableCreateSyntax { - // Return if CREATE SYNTAX is being parsed - if(isWorking) return nil; + // If processing is already in action, wait for it to complete + [self _loopWhileWorking]; if (tableCreateSyntax == nil) { if ([tableListInstance tableType] == SPTableTypeView) { @@ -123,8 +125,8 @@ - (NSArray *) columns { - // Return if CREATE SYNTAX is being parsed - if(isWorking) return [NSArray array]; + // If processing is already in action, wait for it to complete + [self _loopWhileWorking]; if ([columns count] == 0) { if ([tableListInstance tableType] == SPTableTypeView) { @@ -149,9 +151,8 @@ */ - (NSArray *) triggers { - // Return if CREATE SYNTAX is being parsed - if (isWorking) return [NSArray array]; - + // If processing is already in action, wait for it to complete + [self _loopWhileWorking]; // If triggers is nil, the triggers need to be loaded - if a table is selected on MySQL >= 5.0.2 if (!triggers) { @@ -173,10 +174,8 @@ */ - (NSDictionary *) columnWithName:(NSString *)colName { - - // Return if CREATE SYNTAX is being parsed - if(isWorking) return nil; - + // If processing is already in action, wait for it to complete + [self _loopWhileWorking]; if ([columns count] == 0) { if ([tableListInstance tableType] == SPTableTypeView) { @@ -196,10 +195,8 @@ */ - (NSArray *) columnNames { - - // Return if CREATE SYNTAX is being parsed - if(isWorking) return [NSArray array]; - + // If processing is already in action, wait for it to complete + [self _loopWhileWorking]; if ([columnNames count] == 0) { if ([tableListInstance tableType] == SPTableTypeView) { @@ -219,9 +216,8 @@ */ - (NSDictionary *) columnAtIndex:(NSInteger)index { - - // Return if CREATE SYNTAX is being parsed - if(isWorking) return nil; + // If processing is already in action, wait for it to complete + [self _loopWhileWorking]; if ([columns count] == 0) { if ([tableListInstance tableType] == SPTableTypeView) { @@ -242,9 +238,8 @@ - (BOOL) columnIsBlobOrText:(NSString *)colName { - - // Return if CREATE SYNTAX is being parsed - if(isWorking) return YES; // to be at the safe side + // If processing is already in action, wait for it to complete + [self _loopWhileWorking]; if ([columns count] == 0) { if ([tableListInstance tableType] == SPTableTypeView) { @@ -266,9 +261,8 @@ - (BOOL) columnIsGeometry:(NSString *)colName { - - // Return if CREATE SYNTAX is being parsed - if(isWorking) return YES; // to be at the safe side + // If processing is already in action, wait for it to complete + [self _loopWhileWorking]; if ([columns count] == 0) { if ([tableListInstance tableType] == SPTableTypeView) { @@ -289,9 +283,8 @@ */ - (NSString *) statusValueForKey:(NSString *)aKey { - - // Return if CREATE SYNTAX is being parsed - if(isWorking) return nil; + // If processing is already in action, wait for it to complete + [self _loopWhileWorking]; if ([status count] == 0) { [self updateStatusInformationForCurrentTable]; @@ -318,9 +311,8 @@ */ - (NSDictionary *) statusValues { - - // Return if CREATE SYNTAX is being parsed - if(isWorking) return nil; + // If processing is already in action, wait for it to complete + [self _loopWhileWorking]; if ([status count] == 0) { [self updateStatusInformationForCurrentTable]; @@ -380,7 +372,7 @@ */ - (BOOL) updateInformationForCurrentTable { - isWorking = YES; + pthread_mutex_lock(&dataProcessingLock); NSDictionary *tableData = nil; NSDictionary *columnData; @@ -395,8 +387,12 @@ tableData = [self informationForTable:[tableListInstance tableName]]; } + // If nil is returned, return failure. if (tableData == nil ) { - isWorking = NO; + + // The table information fetch may have already unlocked the data lock. + pthread_mutex_trylock(&dataProcessingLock); + pthread_mutex_unlock(&dataProcessingLock); return FALSE; } @@ -412,7 +408,7 @@ } tableEncoding = [[NSString alloc] initWithString:[tableData objectForKey:@"encoding"]]; - isWorking = NO; + pthread_mutex_unlock(&dataProcessingLock); return TRUE; } @@ -423,8 +419,7 @@ */ - (BOOL) updateInformationForCurrentView { - - isWorking = YES; + pthread_mutex_lock(&dataProcessingLock); NSDictionary *viewData = [self informationForView:[tableListInstance tableName]]; NSDictionary *columnData; @@ -436,7 +431,7 @@ [columns removeAllObjects]; [columnNames removeAllObjects]; [constraints removeAllObjects]; - isWorking = NO; + pthread_mutex_unlock(&dataProcessingLock); return FALSE; } @@ -452,7 +447,7 @@ } tableEncoding = [[NSString alloc] initWithString:[viewData objectForKey:@"encoding"]]; - isWorking = NO; + pthread_mutex_unlock(&dataProcessingLock); return TRUE; } @@ -476,10 +471,6 @@ unichar quoteCharacter; BOOL changeEncoding = ![[mySQLConnection encoding] isEqualToString:@"utf8"]; - [columns removeAllObjects]; - [columnNames removeAllObjects]; - [constraints removeAllObjects]; - // Catch unselected tables and return nil if ([tableName isEqualToString:@""] || !tableName) return nil; @@ -500,8 +491,13 @@ nil, nil, [NSApp mainWindow], self, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving the information for table '%@'. Please try again.\n\nMySQL said: %@", @"error retrieving table information informative message"), tableName, [mySQLConnection getLastErrorMessage]]); + // If the current table doesn't exist anymore reload table list if([mySQLConnection getLastErrorID] == 1146) { + + // Release the table loading lock to allow reselection/reloading to requery the database. + pthread_mutex_unlock(&dataProcessingLock); + [[tableListInstance valueForKeyPath:@"tablesListView"] deselectAll:nil]; [tableListInstance updateTables:self]; } @@ -925,14 +921,13 @@ */ - (BOOL)updateStatusInformationForCurrentTable { - - isWorking = YES; + pthread_mutex_lock(&dataProcessingLock); BOOL changeEncoding = ![[mySQLConnection encoding] isEqualToString:@"utf8"]; // Catch unselected tables and return false if ([[tableListInstance tableName] isEqualToString:@""] || ![tableListInstance tableName]) { - isWorking = NO; + pthread_mutex_unlock(&dataProcessingLock); return FALSE; } @@ -979,7 +974,7 @@ [mySQLConnection getLastErrorMessage]]); if (changeEncoding) [mySQLConnection restoreStoredEncoding]; } - isWorking = NO; + pthread_mutex_unlock(&dataProcessingLock); return FALSE; } @@ -997,7 +992,7 @@ if ([[status objectForKey:@"Engine"] isNSNull]) { [status setDictionary:[NSDictionary dictionaryWithObjectsAndKeys:@"Error", @"Engine", [NSString stringWithFormat:NSLocalizedString(@"An error occurred retrieving table information. MySQL said: %@", @"MySQL table info retrieval error message"), [status objectForKey:@"Comment"]], @"Comment", [tableListInstance tableName], @"Name", nil]]; if (changeEncoding) [mySQLConnection restoreStoredEncoding]; - isWorking = NO; + pthread_mutex_unlock(&dataProcessingLock); return FALSE; } @@ -1032,7 +1027,7 @@ if (changeEncoding) [mySQLConnection restoreStoredEncoding]; - isWorking = NO; + pthread_mutex_unlock(&dataProcessingLock); return TRUE; } @@ -1042,8 +1037,7 @@ */ - (BOOL) updateTriggersForCurrentTable { - - isWorking = YES; + pthread_mutex_lock(&dataProcessingLock); // Ensure queries are made in UTF8 BOOL changeEncoding = ![[mySQLConnection encoding] isEqualToString:@"utf8"]; @@ -1067,7 +1061,7 @@ if (changeEncoding) [mySQLConnection restoreStoredEncoding]; } - isWorking = NO; + pthread_mutex_unlock(&dataProcessingLock); return NO; } @@ -1079,7 +1073,7 @@ if (changeEncoding) [mySQLConnection restoreStoredEncoding]; - isWorking = NO; + pthread_mutex_unlock(&dataProcessingLock); return YES; } @@ -1364,7 +1358,18 @@ if (tableCreateSyntax) [tableCreateSyntax release]; if (mySQLConnection) [mySQLConnection release]; + pthread_mutex_destroy(&dataProcessingLock); + [super dealloc]; } -@end +#pragma mark - +#pragma mark Private API + +- (void)_loopWhileWorking +{ + while (pthread_mutex_trylock(&dataProcessingLock)) usleep(10000); + pthread_mutex_unlock(&dataProcessingLock); +} + +@end
\ No newline at end of file diff --git a/Source/SPTableStructureDelegate.m b/Source/SPTableStructureDelegate.m index 17ad817d..729091d6 100644 --- a/Source/SPTableStructureDelegate.m +++ b/Source/SPTableStructureDelegate.m @@ -49,7 +49,10 @@ NSInteger end = [enc length] - start - 1; collations = [databaseDataInstance getDatabaseCollationsForEncoding:[enc substringWithRange:NSMakeRange(start, end)]]; } else { - if([tableDataInstance tableEncoding] != nil) { + + // If the structure has loaded (not still loading!) and the table encoding + // is set, use the appropriate collations. + if([tableDocumentInstance structureLoaded] && [tableDataInstance tableEncoding] != nil) { collations = [databaseDataInstance getDatabaseCollationsForEncoding:[tableDataInstance tableEncoding]]; } else { collations = [NSArray array]; diff --git a/Source/SPTableTriggers.h b/Source/SPTableTriggers.h index 0dfd6f30..70eeeb2e 100644 --- a/Source/SPTableTriggers.h +++ b/Source/SPTableTriggers.h @@ -65,6 +65,7 @@ @property (readwrite, assign) MCPConnection *connection; - (void)loadTriggers; +- (void)resetInterface; // IB action methods - (IBAction)addTrigger:(id)sender; diff --git a/Source/SPTableTriggers.m b/Source/SPTableTriggers.m index 44a17549..f5c15db0 100644 --- a/Source/SPTableTriggers.m +++ b/Source/SPTableTriggers.m @@ -111,17 +111,7 @@ static const NSString *SPTriggerSQLMode = @"TriggerSQLMode"; { BOOL enableInteraction = ((![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarTableTriggers]) || (![tableDocumentInstance isWorking])); - // Disable all interface elements by default - [addTriggerButton setEnabled:NO]; - [refreshTriggersButton setEnabled:NO]; - [triggersTableView setEnabled:NO]; - [labelTextField setStringValue:@""]; - - // Show a warning if the version of MySQL is too low to support triggers - if (![[tableDocumentInstance serverSupport] supportsTriggers]) { - [labelTextField setStringValue:NSLocalizedString(@"This version of MySQL does not support triggers. Support for triggers was added in MySQL 5.0.2", @"triggers not supported label")]; - return; - } + [self resetInterface]; // If no item is selected, or the item selected is not a table, return. if (![tablesListInstance tableName] || [tablesListInstance tableType] != SPTableTypeTable) @@ -139,6 +129,26 @@ static const NSString *SPTriggerSQLMode = @"TriggerSQLMode"; [self _refreshTriggerDataForcingCacheRefresh:NO]; } +/** + * Reset the trigger interface, as for no selected table. + */ +- (void)resetInterface +{ + [triggerData removeAllObjects]; + [triggersTableView noteNumberOfRowsChanged]; + + // Disable all interface elements by default + [addTriggerButton setEnabled:NO]; + [refreshTriggersButton setEnabled:NO]; + [triggersTableView setEnabled:NO]; + [labelTextField setStringValue:@""]; + + // Show a warning if the version of MySQL is too low to support triggers + if (![[tableDocumentInstance serverSupport] supportsTriggers]) { + [labelTextField setStringValue:NSLocalizedString(@"This version of MySQL does not support triggers. Support for triggers was added in MySQL 5.0.2", @"triggers not supported label")]; + } +} + #pragma mark - #pragma mark IB action methods @@ -400,15 +410,16 @@ static const NSString *SPTriggerSQLMode = @"TriggerSQLMode"; while (row != NSNotFound) { - NSString *triggerName = [[triggerData objectAtIndex:row] objectForKey:@"trigger"]; + NSString *triggerName = [[triggerData objectAtIndex:row] objectForKey:SPTriggerName]; NSString *query = [NSString stringWithFormat:@"DROP TRIGGER %@.%@", [database backtickQuotedString], [triggerName backtickQuotedString]]; [connection queryString:query]; if ([connection queryErrored]) { + [[alert window] orderOut:self]; SPBeginAlertSheet(NSLocalizedString(@"Unable to delete trigger", @"error deleting trigger message"), NSLocalizedString(@"OK", @"OK button"), - nil, nil, [NSApp mainWindow], nil, nil, nil, + nil, nil, [tableDocumentInstance parentWindow], nil, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"The selected trigger couldn't be deleted.\n\nMySQL said: %@", @"error deleting trigger informative message"), [connection getLastErrorMessage]]); // Abort loop @@ -554,7 +565,7 @@ static const NSString *SPTriggerSQLMode = @"TriggerSQLMode"; // Timin title is different then what we have saved in the database (case difference) for (NSUInteger i = 0; i < [[triggerActionTimePopUpButton itemArray] count]; i++) { - if ([[[triggerActionTimePopUpButton itemTitleAtIndex:i] uppercaseString] isEqualToString:[[trigger objectForKey:@"timing"] uppercaseString]]) { + if ([[[triggerActionTimePopUpButton itemTitleAtIndex:i] uppercaseString] isEqualToString:[[trigger objectForKey:SPTriggerActionTime] uppercaseString]]) { [triggerActionTimePopUpButton selectItemAtIndex:i]; break; } @@ -563,7 +574,7 @@ static const NSString *SPTriggerSQLMode = @"TriggerSQLMode"; // Event title is different then what we have saved in the database (case difference) for (NSUInteger i = 0; i < [[triggerEventPopUpButton itemArray] count]; i++) { - if ([[[triggerEventPopUpButton itemTitleAtIndex:i] uppercaseString] isEqualToString:[[trigger objectForKey:@"event"] uppercaseString]]) { + if ([[[triggerEventPopUpButton itemTitleAtIndex:i] uppercaseString] isEqualToString:[[trigger objectForKey:SPTriggerEvent] uppercaseString]]) { [triggerEventPopUpButton selectItemAtIndex:i]; break; } diff --git a/Source/SPWindow.m b/Source/SPWindow.m index 58f38d0b..8f2354db 100644 --- a/Source/SPWindow.m +++ b/Source/SPWindow.m @@ -38,7 +38,7 @@ - (void) sendEvent:(NSEvent *)theEvent { - if ([theEvent type] == NSKeyDown && [[theEvent characters] length]) { + if ([theEvent type] == NSKeyDown && [[theEvent charactersIgnoringModifiers] length]) { unichar theCharacter = [[theEvent charactersIgnoringModifiers] characterAtIndex:0]; |