From 62acd195d1fe1ca3a586e2b88ca5710b09dbb461 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 30 Sep 2015 20:23:22 +0200 Subject: Remove a bit duplicate code --- Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m index 40c95321..bcb4e031 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m @@ -526,10 +526,7 @@ asm(".desc ___crashreporter_info__, 0x10"); // 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]]; - } + [self _validateThreadSetup]; // Disable automatic reconnection, as it's handled in-framework to preserve // options, encodings and connection state. @@ -1018,6 +1015,8 @@ asm(".desc ___crashreporter_info__, 0x10"); * Ensure that the thread this method is called on has been registered for * use with MySQL. MySQL requires thread-specific variables for safe * execution. + * + * Calling this multiple times per thread is OK. */ - (void)_validateThreadSetup { @@ -1026,7 +1025,7 @@ asm(".desc ___crashreporter_info__, 0x10"); if (pthread_getspecific(mySQLThreadInitFlagKey)) return; // If not, install it - mysql_thread_init(); + mysql_thread_init(); // multiple calls per thread OK. // Mark the thread to avoid multiple installs pthread_setspecific(mySQLThreadInitFlagKey, &mySQLThreadFlag); -- cgit v1.2.3 From 730f1d66bee8532d35f7f52ddafb3dfbed427876 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 1 Oct 2015 02:24:18 +0200 Subject: Add some debug code for an exception I haven't seen before --- Source/SPExportController.m | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Source/SPExportController.m b/Source/SPExportController.m index 97840171..b249505a 100644 --- a/Source/SPExportController.m +++ b/Source/SPExportController.m @@ -417,8 +417,14 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; [panel setDirectoryURL:[NSURL URLWithString:[exportPathField stringValue]]]; [panel beginSheetModalForWindow:[self window] completionHandler:^(NSInteger returnCode) { if (returnCode == NSOKButton) { - [exportPathField setStringValue:[[panel directoryURL] path]]; - [prefs setObject:[[panel directoryURL] path] forKey:SPExportLastDirectory]; + NSString *path = [[panel directoryURL] path]; + if(!path) { + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:[NSString stringWithFormat:@"File panel ended with OK, but returned nil for path!? directoryURL=%@,isFileURL=%d",[panel directoryURL],[[panel directoryURL] isFileURL]] + userInfo:nil]; + } + [exportPathField setStringValue:path]; + [prefs setObject:path forKey:SPExportLastDirectory]; } }]; } -- cgit v1.2.3 From c5878bdad37e353b5411d4b1a3e5233213d72a1b Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 1 Oct 2015 04:12:05 +0200 Subject: Fix two cases of "UI manipulation from background thread" (relates to #2248) --- Source/SPDataImport.m | 51 +++++++++++++++++++----------------- Source/SPFunctions.h | 33 +++++++++++++++++++++++ Source/SPFunctions.m | 41 +++++++++++++++++++++++++++++ sequel-pro.xcodeproj/project.pbxproj | 14 ++++++++++ 4 files changed, 115 insertions(+), 24 deletions(-) create mode 100644 Source/SPFunctions.h create mode 100644 Source/SPFunctions.m diff --git a/Source/SPDataImport.m b/Source/SPDataImport.m index aa50a827..5cf67976 100644 --- a/Source/SPDataImport.m +++ b/Source/SPDataImport.m @@ -47,6 +47,7 @@ #import "SPFileHandle.h" #import "SPEncodingPopupAccessory.h" #import "SPThreadAdditions.h" +#import "SPFunctions.h" #import @@ -401,18 +402,20 @@ BOOL useIndeterminate = NO; if ([sqlFileHandle compressionFormat] == SPBzip2Compression) useIndeterminate = YES; - // Reset progress interface - [errorsView setString:@""]; - [[singleProgressTitle onMainThread] setStringValue:NSLocalizedString(@"Importing SQL", @"text showing that the application is importing SQL")]; - [[singleProgressText onMainThread] setStringValue:NSLocalizedString(@"Reading...", @"text showing that app is reading dump")]; - [[singleProgressBar onMainThread] setIndeterminate:useIndeterminate]; - [[singleProgressBar onMainThread] setMaxValue:fileTotalLength]; - [[singleProgressBar onMainThread] setUsesThreadedAnimation:YES]; - [[singleProgressBar onMainThread] startAnimation:self]; - - // Open the progress sheet - [[NSApp onMainThread] beginSheet:singleProgressSheet modalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:nil contextInfo:nil]; - [[singleProgressSheet onMainThread] makeKeyWindow]; + SPMainQSync(^{ + // Reset progress interface + [errorsView setString:@""]; + [singleProgressTitle setStringValue:NSLocalizedString(@"Importing SQL", @"text showing that the application is importing SQL")]; + [singleProgressText setStringValue:NSLocalizedString(@"Reading...", @"text showing that app is reading dump")]; + [singleProgressBar setIndeterminate:useIndeterminate]; + [singleProgressBar setMaxValue:fileTotalLength]; + [singleProgressBar setUsesThreadedAnimation:YES]; + [singleProgressBar startAnimation:self]; + + // Open the progress sheet + [NSApp beginSheet:singleProgressSheet modalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:nil contextInfo:nil]; + [singleProgressSheet makeKeyWindow]; + }); [tableDocumentInstance setQueryMode:SPImportExportQueryMode]; @@ -568,18 +571,18 @@ // If not set to ignore errors, ask what to do. Use NSAlert rather than // SPBeginWaitingAlertSheet as there is already a modal sheet in progress. if (!ignoreSQLErrors) { - NSInteger sqlImportErrorSheetReturnCode; - - NSAlert *sqlErrorAlert = [NSAlert - alertWithMessageText:NSLocalizedString(@"An error occurred while importing SQL", @"sql import error message") - defaultButton:NSLocalizedString(@"Continue", @"continue button") - alternateButton:NSLocalizedString(@"Ignore All Errors", @"ignore errors button") - otherButton:NSLocalizedString(@"Stop", @"stop button") - informativeTextWithFormat:NSLocalizedString(@"[ERROR in query %ld] %@\n", @"error text when multiple custom query failed"), (long)(queriesPerformed+1), [mySQLConnection lastErrorMessage] - ]; - [sqlErrorAlert setAlertStyle:NSWarningAlertStyle]; - sqlImportErrorSheetReturnCode = [sqlErrorAlert runModal]; - + __block NSInteger sqlImportErrorSheetReturnCode; + + SPMainQSync(^{ + NSAlert *sqlErrorAlert = [NSAlert alertWithMessageText:NSLocalizedString(@"An error occurred while importing SQL", @"sql import error message") + defaultButton:NSLocalizedString(@"Continue", @"continue button") + alternateButton:NSLocalizedString(@"Ignore All Errors", @"ignore errors button") + otherButton:NSLocalizedString(@"Stop", @"stop button") + informativeTextWithFormat:NSLocalizedString(@"[ERROR in query %ld] %@\n", @"error text when multiple custom query failed"), (long)(queriesPerformed+1), [mySQLConnection lastErrorMessage]]; + [sqlErrorAlert setAlertStyle:NSWarningAlertStyle]; + sqlImportErrorSheetReturnCode = [sqlErrorAlert runModal]; + }); + switch (sqlImportErrorSheetReturnCode) { // On "continue", no additional action is required diff --git a/Source/SPFunctions.h b/Source/SPFunctions.h new file mode 100644 index 00000000..f2ca9c7e --- /dev/null +++ b/Source/SPFunctions.h @@ -0,0 +1,33 @@ +// +// SPFunctions.h +// sequel-pro +// +// Created by Max Lohrmann on 01.10.15. +// Copyright (c) 2015 Max Lohrmann. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at + + +void SPMainQSync(void (^block)(void)); + diff --git a/Source/SPFunctions.m b/Source/SPFunctions.m new file mode 100644 index 00000000..f485d36a --- /dev/null +++ b/Source/SPFunctions.m @@ -0,0 +1,41 @@ +// +// SPFunctions.m +// sequel-pro +// +// Created by Max Lohrmann on 01.10.15. +// Copyright (c) 2015 Max Lohrmann. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at + +#import "SPFunctions.h" + +void SPMainQSync(void (^block)(void)) +{ + if(dispatch_get_current_queue() == dispatch_get_main_queue()) { + block(); + } + else { + dispatch_sync(dispatch_get_main_queue(), block); + } +} diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index 0958501d..1dddeaca 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -189,6 +189,7 @@ 503B02D21AE95E010060CAB1 /* SPConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 173284E91088FEDE0062E892 /* SPConstants.m */; }; 503CDBB21ACDC204004F8A2F /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 503CDBB11ACDC204004F8A2F /* Quartz.framework */; }; 506CE9311A311C6C0039F736 /* SPTableContentFilterController.m in Sources */ = {isa = PBXBuildFile; fileRef = 506CE9301A311C6C0039F736 /* SPTableContentFilterController.m */; }; + 507FF1121BBCC57600104523 /* SPFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF1111BBCC57600104523 /* SPFunctions.m */; }; 50A9F8B119EAD4B90053E571 /* SPGotoDatabaseController.m in Sources */ = {isa = PBXBuildFile; fileRef = 50A9F8B019EAD4B90053E571 /* SPGotoDatabaseController.m */; }; 50D3C3491A75B8A800B5429C /* GotoDatabaseDialog.xib in Resources */ = {isa = PBXBuildFile; fileRef = 50D3C34B1A75B8A800B5429C /* GotoDatabaseDialog.xib */; }; 50D3C3521A77135F00B5429C /* SPParserUtils.c in Sources */ = {isa = PBXBuildFile; fileRef = 50D3C3501A77135F00B5429C /* SPParserUtils.c */; }; @@ -901,6 +902,8 @@ 503CDBB11ACDC204004F8A2F /* Quartz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quartz.framework; path = System/Library/Frameworks/Quartz.framework; sourceTree = SDKROOT; }; 506CE92F1A311C6C0039F736 /* SPTableContentFilterController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableContentFilterController.h; sourceTree = ""; }; 506CE9301A311C6C0039F736 /* SPTableContentFilterController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableContentFilterController.m; sourceTree = ""; }; + 507FF1101BBCC4C400104523 /* SPFunctions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPFunctions.h; sourceTree = ""; }; + 507FF1111BBCC57600104523 /* SPFunctions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPFunctions.m; sourceTree = ""; }; 50A9F8AF19EAD4B90053E571 /* SPGotoDatabaseController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPGotoDatabaseController.h; sourceTree = ""; }; 50A9F8B019EAD4B90053E571 /* SPGotoDatabaseController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPGotoDatabaseController.m; sourceTree = ""; }; 50D3C34A1A75B8A800B5429C /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/GotoDatabaseDialog.xib; sourceTree = ""; }; @@ -2069,6 +2072,7 @@ 17E6416E0EF01F3B001BC333 /* Other */ = { isa = PBXGroup; children = ( + 507FF10E1BBCC4A900104523 /* Utility */, 1198F5B01174EDA700670590 /* Database Actions */, 583CE39511722B70008F148E /* File Compression */, 173284E51088FEC20062E892 /* Data */, @@ -2369,6 +2373,15 @@ path = UnitTests; sourceTree = ""; }; + 507FF10E1BBCC4A900104523 /* Utility */ = { + isa = PBXGroup; + children = ( + 507FF1101BBCC4C400104523 /* SPFunctions.h */, + 507FF1111BBCC57600104523 /* SPFunctions.m */, + ); + name = Utility; + sourceTree = ""; + }; 50D3C3591A771C2300B5429C /* Other */ = { isa = PBXGroup; children = ( @@ -3141,6 +3154,7 @@ 17E641460EF01EB5001BC333 /* main.m in Sources */, 17E641560EF01EF6001BC333 /* SPCustomQuery.m in Sources */, 17E641570EF01EF6001BC333 /* SPAppController.m in Sources */, + 507FF1121BBCC57600104523 /* SPFunctions.m in Sources */, 17E641580EF01EF6001BC333 /* SPGrowlController.m in Sources */, 17E641590EF01EF6001BC333 /* SPTableContent.m in Sources */, 17E6415A0EF01EF6001BC333 /* SPDatabaseDocument.m in Sources */, -- cgit v1.2.3 From 11f5bf55b3d6ecb9afe3a19d60aa9669bd449e42 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 2 Oct 2015 16:31:39 +0200 Subject: Fix conversion of BIT fields (fixes #2254) --- .../SPMySQL Unit Tests/DataConversion_Tests.m | 71 ++++++++++++++++++++++ .../SPMySQLResult Categories/Data Conversion.m | 27 ++++---- 2 files changed, 84 insertions(+), 14 deletions(-) create mode 100644 Frameworks/SPMySQLFramework/SPMySQL Unit Tests/DataConversion_Tests.m diff --git a/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/DataConversion_Tests.m b/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/DataConversion_Tests.m new file mode 100644 index 00000000..385eb0b2 --- /dev/null +++ b/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/DataConversion_Tests.m @@ -0,0 +1,71 @@ +// +// DataConversion_Tests.m +// SPMySQLFramework +// +// Created by Max Lohrmann on 01.10.15. +// Copyright (c) 2015 Max Lohrmann. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at + +@interface DataConversion_Tests : XCTestCase + +- (void)test_bitStringWithBytes; + +@end + +@implementation DataConversion_Tests + +- (void)test_bitStringWithBytes +{ + // BIT(1) + { + unsigned char y = 1; + unsigned char n = 0; + XCTAssertEqualObjects(_bitStringWithBytes(&y,sizeof(y),1), @"1"); + XCTAssertEqualObjects(_bitStringWithBytes(&n,sizeof(n),0), @"0"); + } + // BIT(3) + { + const char input[] = {5}; + NSUInteger bitSize = 3; + NSString *res = _bitStringWithBytes(input,sizeof(input),bitSize); + XCTAssertEqualObjects(res, @"101"); + } + // BIT(16) + { + const char input[] = {0xcc,0xf0}; + NSUInteger bitSize = 16; + NSString *res = _bitStringWithBytes(input,sizeof(input),bitSize); + XCTAssertEqualObjects(res, @"1100110011110000"); + } + // BIT(20) + { + const char input[] = {0x0f,0xcc,0xf0}; + NSUInteger bitSize = 20; + NSString *res = _bitStringWithBytes(input,sizeof(input),bitSize); + XCTAssertEqualObjects(res, @"11111100110011110000"); + } +} + +@end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.m b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.m index 639ff0b9..8c5b9181 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.m @@ -209,27 +209,26 @@ static inline NSString * _bitStringWithBytes(const char *bytes, NSUInteger lengt return nil; } - // Ensure padLength is never lower than the length - if (padLength < bitLength) { - padLength = bitLength; - } - + // use whatever is smaller. padLength comes from BIT(x), bitLength from the actual bytes transmitted. + // if bitLength < padLength it means the value is smaller than what the field can accomodate. + // if bitLength > padLength it means BIT(x) is not a full n bytes long and was extended by mysqls storage. + // In that case the additional bits should still be 0 as mysql does not allow to set bits over the size of x. + bitLength = MIN(bitLength,padLength); // Generate a nul-terminated C string representation of the binary data char *cStringBuffer = malloc(padLength + 1); - cStringBuffer[padLength] = '\0'; + memset(cStringBuffer, '0', padLength); while (i < bitLength) { + // start with the least significant bit (the rightmost bit in the last byte) and move left + unsigned char bitInByteMask = i % 8; // 0-7, the cycle is 0,1,...,7,0,... + unsigned long bytesOffset = (length - 1) - (i >> 3); // i>>3 == floor(i/8) ++i; - - cStringBuffer[padLength - i] = ((bytes[length - 1 - (i >> 3)] >> (i & 0x7)) & 1 ) ? '1' : '0'; + cStringBuffer[padLength - i] = ((bytes[bytesOffset] & (1 << bitInByteMask)) != 0) ? '1' : '0'; } - - while (i++ < padLength) - { - cStringBuffer[padLength - i] = '0'; - } - + + cStringBuffer[padLength] = '\0'; + // Convert to a string NSString *returnString = [NSString stringWithUTF8String:cStringBuffer]; -- cgit v1.2.3 From 9233309f6d40c1aacd12088821beef5029b7a60c Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 2 Oct 2015 16:35:54 +0200 Subject: Give all table views names for crash identification --- Interfaces/English.lproj/DBView.xib | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Interfaces/English.lproj/DBView.xib b/Interfaces/English.lproj/DBView.xib index 289a9eef..4ed8a6cf 100644 --- a/Interfaces/English.lproj/DBView.xib +++ b/Interfaces/English.lproj/DBView.xib @@ -24357,6 +24357,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA com.apple.InterfaceBuilder.CocoaPlugin + TablesListTableView com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -24366,6 +24367,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA com.apple.InterfaceBuilder.CocoaPlugin + TableStructureColumnsTableView com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -24438,6 +24440,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA com.apple.InterfaceBuilder.CocoaPlugin + TableContentTableView com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -24941,6 +24944,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA com.apple.InterfaceBuilder.CocoaPlugin + TableRelationsTableView com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -25706,6 +25710,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA com.apple.InterfaceBuilder.CocoaPlugin + TableTriggersTableView com.apple.InterfaceBuilder.CocoaPlugin @@ -26111,6 +26116,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA com.apple.InterfaceBuilder.CocoaPlugin + CustomQueryResultsTableView com.apple.InterfaceBuilder.CocoaPlugin @@ -26806,6 +26812,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA com.apple.InterfaceBuilder.CocoaPlugin + AdvancedFilterTableView com.apple.InterfaceBuilder.CocoaPlugin -- cgit v1.2.3 From f38ffcb95896ddf3774d30d0c64d8a57139b4ec6 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 2 Oct 2015 17:04:07 +0200 Subject: Add some debug code for another crash that has been happening lately --- .../Querying & Preparation.m | 2 +- Source/SPObjectAdditions.m | 38 +++++++++++++++++++++- Source/SPTableView.m | 2 +- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m index 48f4fc1e..1b7ec58e 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m @@ -313,7 +313,7 @@ theSqlstate = [self _stringForCString:mysql_sqlstate(mySQLConnection)]; // Prevent retries if the query was cancelled or not a connection error - if (lastQueryWasCancelled || ![SPMySQLConnection isErrorIDConnectionError:mysql_errno(mySQLConnection)]) { + if (lastQueryWasCancelled || ![SPMySQLConnection isErrorIDConnectionError:theErrorID]) { break; } } diff --git a/Source/SPObjectAdditions.m b/Source/SPObjectAdditions.m index 9f51d652..f29aa3b4 100644 --- a/Source/SPObjectAdditions.m +++ b/Source/SPObjectAdditions.m @@ -140,7 +140,7 @@ retryDescribe: if([notificationObserver isKindOfClass:[NSView class]]) { [val appendFormat:@" view info: id=%@, tag=%ld\n",[(NSView *)notificationObserver identifier], [(NSView *)notificationObserver tag]]; } - [val appendFormat:@"\nbacktrace:\n%@\n\n",[NSThread callStackSymbols]]; + [val appendFormat:@"\nregistration backtrace:\n%@\n\n",[NSThread callStackSymbols]]; [gScrollViewListeners setObject:val forKey:key]; } @@ -185,4 +185,40 @@ retryDescribe: } +@end + +#pragma mark - + +@interface NSAlert (ApplePrivate) + +- (IBAction)buttonPressed:(id)sender; + +@end + +@implementation NSAlert (SPAlertDebug) + ++ (void)load +{ + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + Class alertClass = [self class]; + + SEL orig = @selector(buttonPressed:); + SEL exch = @selector(sp_buttonPressed:); + + Method origM = class_getInstanceMethod(alertClass, orig); + Method exchM = class_getInstanceMethod(alertClass, exch); + + method_exchangeImplementations(origM, exchM); + }); +} + +- (IBAction)sp_buttonPressed:(id)obj +{ + NSLog(@"%s of %@ title=\n%@\ntext=\n%@",__func__,self,[self messageText],[self informativeText]); + + [self sp_buttonPressed:obj]; +} + @end diff --git a/Source/SPTableView.m b/Source/SPTableView.m index 7a1c01c1..3b1cf507 100644 --- a/Source/SPTableView.m +++ b/Source/SPTableView.m @@ -72,7 +72,7 @@ if ([NSTableView instancesRespondToSelector:@selector(awakeFromNib)]) { [super awakeFromNib]; -} + } } #pragma mark - -- cgit v1.2.3 From b72ee56b6cdb5c54ba67107da61b8f0228d36edc Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 2 Oct 2015 21:17:49 +0200 Subject: Add a unit test someone forgot and clean it up (Also add a test that will currently fail) --- UnitTests/SPTableCopyTest.m | 70 ++++++++++++++++++++---------------- sequel-pro.xcodeproj/project.pbxproj | 2 ++ 2 files changed, 42 insertions(+), 30 deletions(-) diff --git a/UnitTests/SPTableCopyTest.m b/UnitTests/SPTableCopyTest.m index 76a2fe45..05ab4a76 100644 --- a/UnitTests/SPTableCopyTest.m +++ b/UnitTests/SPTableCopyTest.m @@ -39,52 +39,62 @@ @interface SPTableCopyTest : SenTestCase - (void)testCopyTableFromToWithData; +- (void)testCopyTableFromTo_NoPermissions; @end @implementation SPTableCopyTest -- (id)mockConnection -{ - return [[OCMockObject niceMockForClass:[SPMySQLConnection class]] autorelease]; -} - -- (id)mockResult -{ - return [[OCMockObject niceMockForClass:[SPMySQLResult class]] autorelease]; -} - - (void)testCopyTableFromToWithData { - id mockResult = [self mockResult]; + id mockResult = OCMClassMock([SPMySQLResult class]); - unsigned long long varOne = 1; - NSValue *valueOne = [NSValue value:&varOne withObjCType:@encode(__typeof__(varOne))]; - BOOL varNo = NO; - - NSValue *valueNo = [NSValue value:&varNo withObjCType:@encode(BOOL)]; NSArray *resultArray = [[NSArray alloc] initWithObjects:@"", @"CREATE TABLE `table_name` ()", nil]; - id mockConnection = [self mockConnection]; + id mockConnection = OCMClassMock([SPMySQLConnection class]); - [(SPMySQLResult *)[[mockResult expect] andReturn:valueOne] numberOfRows]; - [[[mockResult expect] andReturn:resultArray] getRowAsArray]; + OCMExpect([mockResult numberOfRows]).andReturn(1); + OCMExpect([mockResult getRowAsArray]).andReturn(resultArray); - [[[mockConnection expect] andReturn:mockResult] queryString:@"SHOW CREATE TABLE `source_db`.`table_name`"]; - [[mockConnection expect] queryString:@"CREATE TABLE `target_db`.`table_name` ()"]; - [[mockConnection expect] queryString:@"INSERT INTO `target_db`.`table_name` SELECT * FROM `source_db`.`table_name`"]; - [[[mockConnection stub] andReturnValue:valueNo] queryErrored]; + OCMExpect([mockConnection queryString:@"SHOW CREATE TABLE `source_db`.`table_name`"]).andReturn(mockResult); + OCMExpect([mockConnection queryString:@"CREATE TABLE `target_db`.`table_name` ()"]); + OCMExpect([mockConnection queryString:@"INSERT INTO `target_db`.`table_name` SELECT * FROM `source_db`.`table_name`"]); + OCMStub([mockConnection queryErrored]).andReturn(NO); - SPTableCopy *tableCopy = [[SPTableCopy alloc] init]; - - [tableCopy setConnection:mockConnection]; - [tableCopy copyTable:@"table_name" from:@"source_db" to:@"target_db" withContent:YES]; + { + SPTableCopy *tableCopy = [[SPTableCopy alloc] init]; + + [tableCopy setConnection:mockConnection]; + [tableCopy copyTable:@"table_name" from:@"source_db" to:@"target_db" withContent:YES]; + + [tableCopy release]; + } - [mockResult verify]; - [mockConnection verify]; + OCMVerifyAll(mockResult); + OCMVerifyAll(mockConnection); - [tableCopy release]; [resultArray release]; } +- (void)testCopyTableFromTo_NoPermissions +{ + id mockConnection = OCMStrictClassMock([SPMySQLConnection class]); + + OCMExpect([mockConnection queryString:@"SHOW CREATE TABLE `source_db`.`table_name`"]).andReturn(nil); + OCMStub([mockConnection queryErrored]).andReturn(YES); + OCMStub([mockConnection lastErrorMessage]).andReturn(@"SHOW command denied to user 'alice'@'localhost' for table 'table_name'"); + OCMStub([mockConnection lastErrorID]).andReturn(1142); + OCMStub([mockConnection lastSqlstate]).andReturn(@"42000"); + + { + SPTableCopy *tableCopy = [[SPTableCopy alloc] init]; + + STAssertFalse([tableCopy copyTable:@"table_name" from:@"source_db" to:@"target_db"],@"copy operation must fail."); + + [tableCopy release]; + } + + [mockConnection verify]; +} + @end diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index 1dddeaca..bdd39761 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -190,6 +190,7 @@ 503CDBB21ACDC204004F8A2F /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 503CDBB11ACDC204004F8A2F /* Quartz.framework */; }; 506CE9311A311C6C0039F736 /* SPTableContentFilterController.m in Sources */ = {isa = PBXBuildFile; fileRef = 506CE9301A311C6C0039F736 /* SPTableContentFilterController.m */; }; 507FF1121BBCC57600104523 /* SPFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF1111BBCC57600104523 /* SPFunctions.m */; }; + 507FF1621BBF0D5000104523 /* SPTableCopyTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 112730551180788A000737FD /* SPTableCopyTest.m */; }; 50A9F8B119EAD4B90053E571 /* SPGotoDatabaseController.m in Sources */ = {isa = PBXBuildFile; fileRef = 50A9F8B019EAD4B90053E571 /* SPGotoDatabaseController.m */; }; 50D3C3491A75B8A800B5429C /* GotoDatabaseDialog.xib in Resources */ = {isa = PBXBuildFile; fileRef = 50D3C34B1A75B8A800B5429C /* GotoDatabaseDialog.xib */; }; 50D3C3521A77135F00B5429C /* SPParserUtils.c in Sources */ = {isa = PBXBuildFile; fileRef = 50D3C3501A77135F00B5429C /* SPParserUtils.c */; }; @@ -3075,6 +3076,7 @@ 503B02D11AE95DD40060CAB1 /* SPTableFilterParser.m in Sources */, 503B02CF1AE95C2C0060CAB1 /* SPTableFilterParserTest.m in Sources */, 50EA92681AB23EFC008D3C4F /* SPTableCopy.m in Sources */, + 507FF1621BBF0D5000104523 /* SPTableCopyTest.m in Sources */, 50EA926A1AB246B8008D3C4F /* SPDatabaseActionTest.m in Sources */, 50EA92651AB23EC8008D3C4F /* SPDatabaseAction.m in Sources */, 50EA92641AB23EAD008D3C4F /* SPDatabaseCopy.m in Sources */, -- cgit v1.2.3 From cbb44947b8519a17ff34c1e23dc74e00fedfe2b3 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 3 Oct 2015 00:51:04 +0200 Subject: Fix exporting Bundles not working (#2261) --- Source/SPBundleEditorController.m | 81 ++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/Source/SPBundleEditorController.m b/Source/SPBundleEditorController.m index f38ffb28..a0c7d7ce 100644 --- a/Source/SPBundleEditorController.m +++ b/Source/SPBundleEditorController.m @@ -36,8 +36,6 @@ #import "SPSplitView.h" #import "SPAppController.h" -static NSString *SPSaveBundleAction = @"SPSaveBundle"; - #define kBundleNameKey @"bundleName" #define kChildrenKey @"_children_" #define kInputFieldScopeArrayIndex 0 @@ -722,9 +720,47 @@ static NSString *SPSaveBundleAction = @"SPSaveBundle"; [panel setNameFieldStringValue:[[self _currentSelectedObject] objectForKey:kBundleNameKey]]; - [panel beginSheetModalForWindow:[self window] completionHandler:^(NSInteger returnCode) - { - [self sheetDidEnd:panel returnCode:returnCode contextInfo:SPSaveBundleAction]; + [panel beginSheetModalForWindow:[self window] completionHandler:^(NSInteger returnCode) { + if (returnCode != NSOKButton) return; + + // Panel is still on screen. Hide it first. (This is Apple's recommended way) + [panel orderOut:nil]; + + id aBundle = [self _currentSelectedObject]; + + NSString *bundleFileName = [aBundle objectForKey:kBundleNameKey]; + NSString *possibleExisitingBundleFilePath = [NSString stringWithFormat:@"%@/%@.%@", bundlePath, bundleFileName, SPUserBundleFileExtension]; + NSAssert(possibleExisitingBundleFilePath != nil, @"source bundle path must be non-nil!"); + + NSString *savePath = [[panel URL] path]; + NSAssert(savePath != nil, @"destination bundle path must be non-nil! (URL=%@)",[panel URL]); + + BOOL isDir; + BOOL copyingWasSuccessful = YES; + NSError *err = nil; + + // Copy possible existing bundle with content + if([[NSFileManager defaultManager] fileExistsAtPath:possibleExisitingBundleFilePath isDirectory:&isDir] && isDir) { + //FIXME This will fail if savePath exists, but the user already consented overwriting in the save panel. We should use trashItemAtURL:... once we are 10.8+ + if(![[NSFileManager defaultManager] copyItemAtPath:possibleExisitingBundleFilePath toPath:savePath error:&err]) { + //if we have an NSError that will provide the nicest error message. + if(err) { + [[NSAlert alertWithError:err] runModal]; + return; + } + NSLog(@"copy(%@ -> %@) failed!",possibleExisitingBundleFilePath,savePath); + copyingWasSuccessful = NO; + } + } + + if(!copyingWasSuccessful || ![self saveBundle:aBundle atPath:savePath]) { + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:NSLocalizedString(@"Error while saving the Bundle.", @"Bundle Editor : Save-Bundle-Error : error dialog title")]; + [alert addButtonWithTitle:NSLocalizedString(@"OK", @"Bundle Editor : Save-Bundle-Error : OK button")]; + [alert setAlertStyle:NSCriticalAlertStyle]; + [alert runModal]; //blocks + [alert release]; + } }]; } @@ -1005,37 +1041,6 @@ static NSString *SPSaveBundleAction = @"SPSaveBundle"; } } - else if([contextInfo isEqualToString:@"saveBundle"]) { - if (returnCode == NSOKButton) { - - id aBundle = [self _currentSelectedObject]; - - NSString *bundleFileName = [aBundle objectForKey:kBundleNameKey]; - NSString *possibleExisitingBundleFilePath = [NSString stringWithFormat:@"%@/%@.%@", bundlePath, bundleFileName, SPUserBundleFileExtension]; - - NSString *savePath = [[sheet URL] path]; - - BOOL isDir; - BOOL copyingWasSuccessful = YES; - - // Copy possible existing bundle with content - if([[NSFileManager defaultManager] fileExistsAtPath:possibleExisitingBundleFilePath isDirectory:&isDir] && isDir) { - if(![[NSFileManager defaultManager] copyItemAtPath:possibleExisitingBundleFilePath toPath:savePath error:nil]) - copyingWasSuccessful = NO; - } - - if(!copyingWasSuccessful || ![self saveBundle:aBundle atPath:savePath]) { - NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Error while saving the Bundle.", @"Bundle Editor : Save-Bundle-Error : error dialog title") - defaultButton:NSLocalizedString(@"OK", @"Bundle Editor : Save-Bundle-Error : OK button") - alternateButton:nil - otherButton:nil - informativeTextWithFormat:@""]; - - [alert setAlertStyle:NSCriticalAlertStyle]; - [alert runModal]; - } - } - } else if([contextInfo isEqualToString:@"undeleteSelectedDefaultBundles"]) { if(returnCode == 1) { @@ -1058,6 +1063,10 @@ static NSString *SPSaveBundleAction = @"SPSaveBundle"; } } + else { + NSBeep(); + NSLog(@"%s: unhandled case! (contextInfo=%p)",__func__,contextInfo); + } } -- cgit v1.2.3 From a0641ac04022f8f6d35daa2f93d470b99494af12 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 4 Oct 2015 04:14:39 +0200 Subject: Fix export of BIT fields in SQL (#2262) --- Source/SPExportInitializer.m | 2 +- Source/SPSQLExporter.m | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Source/SPExportInitializer.m b/Source/SPExportInitializer.m index 7794d1e4..03284525 100644 --- a/Source/SPExportInitializer.m +++ b/Source/SPExportInitializer.m @@ -65,7 +65,7 @@ [exportProgressIndicator setIndeterminate:NO]; [exportProgressIndicator setDoubleValue:0]; - // If it's not already displayed, open the progress sheet open the progress sheet. + // If it's not already displayed, open the progress sheet if (![exportProgressWindow isVisible]) { [NSApp beginSheet:exportProgressWindow modalForWindow:[tableDocumentInstance parentWindow] diff --git a/Source/SPSQLExporter.m b/Source/SPSQLExporter.m index f72bb72f..94506728 100644 --- a/Source/SPSQLExporter.m +++ b/Source/SPSQLExporter.m @@ -316,9 +316,8 @@ useRawHexDataForColumnAtIndex[j] = YES; } - // Floats, integers and bits can be output directly assuming they're non-binary - if (![[theColumnDetail objectForKey:@"binary"] boolValue] - && ([theTypeGrouping isEqualToString:@"bit"] || [theTypeGrouping isEqualToString:@"integer"] || [theTypeGrouping isEqualToString:@"float"])) + // Floats, integers can be output directly assuming they're non-binary + if (![[theColumnDetail objectForKey:@"binary"] boolValue] && ([@[@"integer",@"float"] containsObject:theTypeGrouping])) { useRawDataForColumnAtIndex[j] = YES; } -- cgit v1.2.3 From 5239e6df5961a475e9497d30a33f48bfd99e8e5b Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 4 Oct 2015 04:16:10 +0200 Subject: Add logging for a possible issue in Connection keepalive --- .../Source/SPMySQLConnection Categories/Ping & KeepAlive.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m index e8338bb4..e43b4a2f 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m @@ -80,6 +80,10 @@ */ - (void)_threadedKeepAlive { + if(keepAliveThread) { + NSLog(@"warning: overwriting existing keepAliveThread: %@, results may be unpredictable!",keepAliveThread); + } + keepAliveThread = [NSThread currentThread]; [keepAliveThread setName:@"SPMySQL connection keepalive monitor thread"]; -- cgit v1.2.3 From cd3ebbe91342860da332eef7c744b143b3094bbf Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 4 Oct 2015 04:18:32 +0200 Subject: Replace some for for() loops with FastEnumeration where i is not needed Also has the advantage of causing an exception on concurrent modification --- Source/SPTableContent.m | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m index 2b185bca..6b0065dd 100644 --- a/Source/SPTableContent.m +++ b/Source/SPTableContent.m @@ -372,12 +372,11 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper - (void) setTableDetails:(NSDictionary *)tableDetails { NSString *newTableName; - NSInteger i, sortColumnNumberToRestore = NSNotFound; + NSInteger sortColumnNumberToRestore = NSNotFound; #ifndef SP_CODA NSNumber *colWidth; #endif NSArray *columnNames; - NSDictionary *columnDefinition; NSMutableDictionary *preservedColumnWidths = nil; NSTableColumn *theCol; #ifndef SP_CODA @@ -566,8 +565,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper [tableContentView setRowHeight:2.0f+NSSizeToCGSize([@"{ǞṶḹÜ∑zgyf" sizeWithAttributes:@{NSFontAttributeName : tableFont}]).height]; // Add the new columns to the table and filterTable - for ( i = 0 ; i < (NSInteger)[dataColumns count] ; i++ ) { - columnDefinition = NSArrayObjectAtIndex(dataColumns, i); + for (NSDictionary *columnDefinition in dataColumns ) { // Set up the column theCol = [[NSTableColumn alloc] initWithIdentifier:[columnDefinition objectForKey:@"datacolumnindex"]]; @@ -1810,9 +1808,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper */ - (IBAction)addRow:(id)sender { - NSMutableDictionary *column; NSMutableArray *newRow = [NSMutableArray array]; - NSUInteger i; // Check whether table editing is permitted (necessary as some actions - eg table double-click - bypass validation) if ([tableDocumentInstance isWorking] || [tablesListInstance tableType] != SPTableTypeTable) return; @@ -1820,8 +1816,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper // Check whether a save of the current row is required. if ( ![self saveRowOnDeselect] ) return; - for ( i = 0 ; i < [dataColumns count] ; i++ ) { - column = NSArrayObjectAtIndex(dataColumns, i); + for (NSDictionary *column in dataColumns) { if ([column objectForKey:@"default"] == nil || [[column objectForKey:@"default"] isNSNull]) { [newRow addObject:[NSNull null]]; } else if ([[column objectForKey:@"default"] isEqualToString:@""] @@ -3146,10 +3141,8 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper */ - (BOOL)tableContainsBlobOrTextColumns { - NSUInteger i; - - for ( i = 0 ; i < [dataColumns count]; i++ ) { - if ( [tableDataInstance columnIsBlobOrText:[NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"name"]] ) { + for (NSDictionary *column in dataColumns) { + if ( [tableDataInstance columnIsBlobOrText:[column objectForKey:@"name"]] ) { return YES; } } -- cgit v1.2.3 From 0263046930b420e07c312c6fbe0156a4a1ebcdf0 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 4 Oct 2015 06:13:38 +0200 Subject: Remove a duplicate @interface and prepare some functions for unit testing --- .../SPMySQLFramework/Source/SPMySQL Private APIs.h | 7 +------ .../SPMySQLResult Categories/Data Conversion.h | 7 +------ .../SPMySQLResult Categories/Data Conversion.m | 21 ++++++++++++++++----- .../SPMySQLResult Categories/Field Definitions.m | 9 +-------- 4 files changed, 19 insertions(+), 25 deletions(-) diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h b/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h index 2419d7a9..ec85f92b 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h +++ b/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h @@ -99,12 +99,7 @@ @end // SPMySQLResult Data Conversion Private API -@interface SPMySQLResult (Data_Conversion_Private_API) - -+ (void)_initializeDataConversion; -- (id)_getObjectFromBytes:(char *)bytes ofLength:(NSUInteger)length fieldDefinitionIndex:(NSUInteger)fieldIndex previewLength:(NSUInteger)previewLength; - -@end +#import "Data Conversion.h" /** * Set up a static function to allow fast calling of SPMySQLResult data conversion with cached selectors diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.h b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.h index 817d2cb7..7d865226 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.h +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.h @@ -30,12 +30,7 @@ @interface SPMySQLResult (Data_Conversion_Private_API) ++ (void)_initializeDataConversion; - (id)_getObjectFromBytes:(char *)bytes ofLength:(NSUInteger)length fieldDefinitionIndex:(NSUInteger)fieldIndex previewLength:(NSUInteger)previewLength; -static inline SPMySQLResultFieldProcessor _processorForField(MYSQL_FIELD aField); - -static inline NSString * _stringWithBytes(const void *dataBytes, NSUInteger dataLength, NSStringEncoding aStringEncoding, NSUInteger previewLength); -static inline NSString * _bitStringWithBytes(const char *bytes, NSUInteger length, NSUInteger padLength); -static inline NSString * _convertStringData(const void *dataBytes, NSUInteger dataLength, NSStringEncoding aStringEncoding, NSUInteger previewLength); - @end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.m b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.m index 8c5b9181..b2336aaa 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.m @@ -31,6 +31,16 @@ #import "Data Conversion.h" +#ifdef SPMYSQL_FOR_UNIT_TESTING +#define PRIVATE /* public */ +#else +#define PRIVATE static inline +#endif + +PRIVATE SPMySQLResultFieldProcessor _processorForField(MYSQL_FIELD aField); +PRIVATE NSString * _bitStringWithBytes(const char *bytes, NSUInteger length, NSUInteger padLength); +PRIVATE NSString * _convertStringData(const void *dataBytes, NSUInteger dataLength, NSStringEncoding aStringEncoding, NSUInteger previewLength); + static SPMySQLResultFieldProcessor fieldProcessingMap[256]; static id NSNullPointer; static NSStringEncoding NSFromCFStringEncodingBig5; @@ -161,10 +171,12 @@ static NSStringEncoding NSFromCFStringEncodingGBK_95; return nil; } +@end + /** * Returns the field processor to use for a specified field. */ -static inline SPMySQLResultFieldProcessor _processorForField(MYSQL_FIELD aField) +PRIVATE SPMySQLResultFieldProcessor _processorForField(MYSQL_FIELD aField) { // Determine the default field processor to use SPMySQLResultFieldProcessor dataProcessor = fieldProcessingMap[aField.type]; @@ -200,7 +212,7 @@ static inline SPMySQLResultFieldProcessor _processorForField(MYSQL_FIELD aField) * field length. * MySQL stores bit data as string data stored in an 8-bit wide character set. */ -static inline NSString * _bitStringWithBytes(const char *bytes, NSUInteger length, NSUInteger padLength) +PRIVATE NSString * _bitStringWithBytes(const char *bytes, NSUInteger length, NSUInteger padLength) { NSUInteger i = 0; NSUInteger bitLength = length << 3; @@ -242,7 +254,7 @@ static inline NSString * _bitStringWithBytes(const char *bytes, NSUInteger lengt * Converts stored string data - which may contain nul bytes - to a native * Objective-C string, using the current class encoding. */ -static inline NSString * _convertStringData(const void *dataBytes, NSUInteger dataLength, NSStringEncoding aStringEncoding, NSUInteger previewLength) +PRIVATE NSString * _convertStringData(const void *dataBytes, NSUInteger dataLength, NSStringEncoding aStringEncoding, NSUInteger previewLength) { // Fast case - if not using a preview length, or if the data length is shorter, return the requested data. @@ -413,5 +425,4 @@ static inline NSString * _convertStringData(const void *dataBytes, NSUInteger da return previewString; } - -@end +#undef PRIVATE diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Field Definitions.m b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Field Definitions.m index c61b9140..09bd9580 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Field Definitions.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Field Definitions.m @@ -29,6 +29,7 @@ // More info at #import "Field Definitions.h" +#import "SPMySQL Private APIs.h" @interface SPMySQLResult (Field_Definitions_Private_API) @@ -40,14 +41,6 @@ @end -// Import a private declaration from the SPMySQLResult file for use -@interface SPMySQLResult (Private_API) - -- (NSString *)_stringWithBytes:(const void *)bytes length:(NSUInteger)length; -- (NSString *)_lossyStringWithBytes:(const void *)bytes length:(NSUInteger)length wasLossy:(BOOL *)outLossy; - -@end - #define MAGIC_BINARY_CHARSET_NR 63 const SPMySQLResultCharset SPMySQLCharsetMap[] = -- cgit v1.2.3 From 05f9533860eb53e5fd0028cda74f9b6d7b26454d Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 4 Oct 2015 14:39:34 +0200 Subject: Configure SPMySQL project to actually be able to run Unit Tests * Add a new configuration "Unit Tests" * Add a new Target for the Unit Test code * Assign Unit Test Target to the Test step of SPMySQL scheme * Also remove a compiler setting for PowerMac G5 --- .../SPMySQL Unit Tests/DataConversion_Tests.m | 12 +- .../SPMySQLFramework/SPMySQL Unit Tests/Info.plist | 24 ++ .../SPMySQLFramework.xcodeproj/project.pbxproj | 290 ++++++++++++++++++++- 3 files changed, 320 insertions(+), 6 deletions(-) create mode 100644 Frameworks/SPMySQLFramework/SPMySQL Unit Tests/Info.plist diff --git a/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/DataConversion_Tests.m b/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/DataConversion_Tests.m index 385eb0b2..b8256a5c 100644 --- a/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/DataConversion_Tests.m +++ b/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/DataConversion_Tests.m @@ -28,6 +28,12 @@ // // More info at +#import +#import + +// this function is inaccessible outside of unit tests +extern NSString * _bitStringWithBytes(const char *bytes, NSUInteger length, NSUInteger padLength); + @interface DataConversion_Tests : XCTestCase - (void)test_bitStringWithBytes; @@ -40,10 +46,10 @@ { // BIT(1) { - unsigned char y = 1; - unsigned char n = 0; + const char y = '\1'; + const char n = '\0'; XCTAssertEqualObjects(_bitStringWithBytes(&y,sizeof(y),1), @"1"); - XCTAssertEqualObjects(_bitStringWithBytes(&n,sizeof(n),0), @"0"); + XCTAssertEqualObjects(_bitStringWithBytes(&n,sizeof(n),1), @"0"); } // BIT(3) { diff --git a/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/Info.plist b/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/Info.plist new file mode 100644 index 00000000..38544a84 --- /dev/null +++ b/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.sequelpro.spmysql-unittests + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj b/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj index 9acf9417..b3b58052 100644 --- a/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj +++ b/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj @@ -9,6 +9,8 @@ /* Begin PBXBuildFile section */ 17E3A57B1885A286009CF372 /* SPMySQLDataTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 17E3A5791885A286009CF372 /* SPMySQLDataTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; 17E3A57C1885A286009CF372 /* SPMySQLDataTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = 17E3A57A1885A286009CF372 /* SPMySQLDataTypes.m */; }; + 507FF1E51BC0D82300104523 /* DataConversion_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF1811BC0C64100104523 /* DataConversion_Tests.m */; }; + 507FF23B1BC0E8CA00104523 /* SPMySQL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC2EF5B0486A6940098B216 /* SPMySQL.framework */; }; 580A331E14D75CF7000D6933 /* SPMySQLGeometryData.h in Headers */ = {isa = PBXBuildFile; fileRef = 580A331C14D75CF7000D6933 /* SPMySQLGeometryData.h */; settings = {ATTRIBUTES = (Public, ); }; }; 580A331F14D75CF7000D6933 /* SPMySQLGeometryData.m in Sources */ = {isa = PBXBuildFile; fileRef = 580A331D14D75CF7000D6933 /* SPMySQLGeometryData.m */; }; 583C734A17A489CC0056B284 /* SPMySQLStreamingResultStoreDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 583C734917A489CC0056B284 /* SPMySQLStreamingResultStoreDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -75,6 +77,16 @@ 8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 507FF2391BC0E8AF00104523 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8DC2EF4F0486A6940098B216; + remoteInfo = SPMySQL.framework; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ 0867D69BFE84028FC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 0867D6A5FE840307C02AAC07 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; @@ -83,6 +95,9 @@ 17E3A5791885A286009CF372 /* SPMySQLDataTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPMySQLDataTypes.h; sourceTree = ""; }; 17E3A57A1885A286009CF372 /* SPMySQLDataTypes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPMySQLDataTypes.m; sourceTree = ""; }; 32DBCF5E0370ADEE00C91783 /* SPMySQLFramework_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLFramework_Prefix.pch; path = Source/SPMySQLFramework_Prefix.pch; sourceTree = ""; }; + 507FF1811BC0C64100104523 /* DataConversion_Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DataConversion_Tests.m; sourceTree = ""; }; + 507FF1D51BC0D7D300104523 /* SPMySQL Unit Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SPMySQL Unit Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 507FF1D81BC0D7D300104523 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 580A331C14D75CF7000D6933 /* SPMySQLGeometryData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLGeometryData.h; path = Source/SPMySQLGeometryData.h; sourceTree = ""; }; 580A331D14D75CF7000D6933 /* SPMySQLGeometryData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPMySQLGeometryData.m; path = Source/SPMySQLGeometryData.m; sourceTree = ""; }; 583C734917A489CC0056B284 /* SPMySQLStreamingResultStoreDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLStreamingResultStoreDelegate.h; path = Source/SPMySQLStreamingResultStoreDelegate.h; sourceTree = ""; }; @@ -153,6 +168,14 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 507FF1D21BC0D7D300104523 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 507FF23B1BC0E8CA00104523 /* SPMySQL.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8DC2EF560486A6940098B216 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -171,6 +194,7 @@ isa = PBXGroup; children = ( 8DC2EF5B0486A6940098B216 /* SPMySQL.framework */, + 507FF1D51BC0D7D300104523 /* SPMySQL Unit Tests.xctest */, ); name = Products; sourceTree = ""; @@ -184,6 +208,7 @@ 08FB77AEFE84172EC02AAC07 /* Classes */, 58C009D214E31D1300AC489A /* Category Additions */, 32C88DFF0371C24200C91783 /* Other Sources */, + 507FF1801BC0C64100104523 /* Unit Tests */, 089C1665FE841158C02AAC07 /* Resources */, 58428DF514BA5A03000F8438 /* Scripts */, 0867D69AFE84028FC02AAC07 /* Linked Frameworks */, @@ -252,6 +277,16 @@ name = "Other Sources"; sourceTree = ""; }; + 507FF1801BC0C64100104523 /* Unit Tests */ = { + isa = PBXGroup; + children = ( + 507FF1D81BC0D7D300104523 /* Info.plist */, + 507FF1811BC0C64100104523 /* DataConversion_Tests.m */, + ); + name = "Unit Tests"; + path = "SPMySQL Unit Tests"; + sourceTree = ""; + }; 580A331B14D75CCF000D6933 /* Result types */ = { isa = PBXGroup; children = ( @@ -419,6 +454,24 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 507FF1D41BC0D7D300104523 /* SPMySQL Unit Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 507FF1DE1BC0D7D300104523 /* Build configuration list for PBXNativeTarget "SPMySQL Unit Tests" */; + buildPhases = ( + 507FF1D11BC0D7D300104523 /* Sources */, + 507FF1D21BC0D7D300104523 /* Frameworks */, + 507FF1D31BC0D7D300104523 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 507FF23A1BC0E8AF00104523 /* PBXTargetDependency */, + ); + name = "SPMySQL Unit Tests"; + productName = "SPMySQL Unit Tests"; + productReference = 507FF1D51BC0D7D300104523 /* SPMySQL Unit Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 8DC2EF4F0486A6940098B216 /* SPMySQL.framework */ = { isa = PBXNativeTarget; buildConfigurationList = 1DEB91AD08733DA50010E9CD /* Build configuration list for PBXNativeTarget "SPMySQL.framework" */; @@ -445,6 +498,11 @@ isa = PBXProject; attributes = { LastUpgradeCheck = 0510; + TargetAttributes = { + 507FF1D41BC0D7D300104523 = { + CreatedOnToolsVersion = 6.2; + }; + }; }; buildConfigurationList = 1DEB91B108733DA50010E9CD /* Build configuration list for PBXProject "SPMySQLFramework" */; compatibilityVersion = "Xcode 3.2"; @@ -462,11 +520,19 @@ projectRoot = ""; targets = ( 8DC2EF4F0486A6940098B216 /* SPMySQL.framework */, + 507FF1D41BC0D7D300104523 /* SPMySQL Unit Tests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 507FF1D31BC0D7D300104523 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8DC2EF520486A6940098B216 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -478,6 +544,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 507FF1D11BC0D7D300104523 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 507FF1E51BC0D82300104523 /* DataConversion_Tests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8DC2EF540486A6940098B216 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -510,6 +584,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 507FF23A1BC0E8AF00104523 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8DC2EF4F0486A6940098B216 /* SPMySQL.framework */; + targetProxy = 507FF2391BC0E8AF00104523 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 089C1666FE841158C02AAC07 /* InfoPlist.strings */ = { isa = PBXVariantGroup; @@ -532,7 +614,6 @@ DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; GCC_DYNAMIC_NO_PIC = NO; - GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = Source/SPMySQLFramework_Prefix.pch; GENERATE_PKGINFO_FILE = YES; @@ -559,7 +640,6 @@ DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; - GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = Source/SPMySQLFramework_Prefix.pch; INFOPLIST_FILE = Resources/Info.plist; @@ -644,6 +724,198 @@ }; name = Release; }; + 507FF1DF1BC0D7D300104523 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + GCC_DYNAMIC_NO_PIC = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INFOPLIST_FILE = "SPMySQL Unit Tests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.9; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 507FF1E11BC0D7D300104523 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INFOPLIST_FILE = "SPMySQL Unit Tests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.9; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + 507FF1E21BC0D7D300104523 /* Distribution */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INFOPLIST_FILE = "SPMySQL Unit Tests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.9; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Distribution; + }; + 507FF2361BC0E0A800104523 /* Unit Testing */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + CLANG_LINK_OBJC_RUNTIME = NO; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ""; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; + GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; + GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; + GCC_WARN_MISSING_PARENTHESES = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_STRICT_SELECTOR_MATCH = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACH_O_TYPE = mh_dylib; + MACOSX_DEPLOYMENT_TARGET = 10.6; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + VALID_ARCHS = "i386 x86_64"; + }; + name = "Unit Testing"; + }; + 507FF2371BC0E0A800104523 /* Unit Testing */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_DYNAMIC_NO_PIC = NO; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = Source/SPMySQLFramework_Prefix.pch; + GCC_PREPROCESSOR_DEFINITIONS = SPMYSQL_FOR_UNIT_TESTING; + GENERATE_PKGINFO_FILE = YES; + INFOPLIST_FILE = Resources/Info.plist; + INSTALL_PATH = "@executable_path/../Frameworks"; + LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/MySQL Client Libraries/lib\"", + ); + PRODUCT_NAME = SPMySQL; + SKIP_INSTALL = YES; + WRAPPER_EXTENSION = framework; + }; + name = "Unit Testing"; + }; + 507FF2381BC0E0A800104523 /* Unit Testing */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + GCC_DYNAMIC_NO_PIC = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INFOPLIST_FILE = "SPMySQL Unit Tests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.9; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = "Unit Testing"; + }; 586AA55214F5D599007F82BF /* Distribution */ = { isa = XCBuildConfiguration; buildSettings = { @@ -688,7 +960,6 @@ DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; - GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = Source/SPMySQLFramework_Prefix.pch; INFOPLIST_FILE = Resources/Info.plist; @@ -710,6 +981,7 @@ isa = XCConfigurationList; buildConfigurations = ( 1DEB91AE08733DA50010E9CD /* Debug */, + 507FF2371BC0E0A800104523 /* Unit Testing */, 1DEB91AF08733DA50010E9CD /* Release */, 586AA55314F5D599007F82BF /* Distribution */, ); @@ -720,12 +992,24 @@ isa = XCConfigurationList; buildConfigurations = ( 1DEB91B208733DA50010E9CD /* Debug */, + 507FF2361BC0E0A800104523 /* Unit Testing */, 1DEB91B308733DA50010E9CD /* Release */, 586AA55214F5D599007F82BF /* Distribution */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 507FF1DE1BC0D7D300104523 /* Build configuration list for PBXNativeTarget "SPMySQL Unit Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 507FF1DF1BC0D7D300104523 /* Debug */, + 507FF2381BC0E0A800104523 /* Unit Testing */, + 507FF1E11BC0D7D300104523 /* Release */, + 507FF1E21BC0D7D300104523 /* Distribution */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 0867D690FE84028FC02AAC07 /* Project object */; -- cgit v1.2.3 From 670758d69f5df210c9930e9b7fafbbac2dc60d16 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 4 Oct 2015 19:28:09 +0200 Subject: Add a few more unit tests to SPMySQL --- .../SPMySQLStringAdditions_Tests.m | 89 ++++++++++++++++++++++ .../SPMySQLFramework.xcodeproj/project.pbxproj | 12 ++- Frameworks/SPMySQLFramework/Source/SPMySQLResult.m | 2 +- 3 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 Frameworks/SPMySQLFramework/SPMySQL Unit Tests/SPMySQLStringAdditions_Tests.m diff --git a/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/SPMySQLStringAdditions_Tests.m b/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/SPMySQLStringAdditions_Tests.m new file mode 100644 index 00000000..cc1f44dd --- /dev/null +++ b/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/SPMySQLStringAdditions_Tests.m @@ -0,0 +1,89 @@ +// +// SPMySQLStringAdditions_Tests.m +// SPMySQLFramework +// +// Created by Max Lohrmann on 04.10.15. +// +// + +#import +#import +#import + +@interface SPMySQLStringAdditions_Tests : XCTestCase + +- (void)test_mySQLBacktickQuotedString; +- (void)test_mySQLTickQuotedString; +- (void)test_stringForDataBytesLengthEncoding; + +@end + +@implementation SPMySQLStringAdditions_Tests + +- (void)test_mySQLBacktickQuotedString +{ + XCTAssertEqualObjects([@"" mySQLBacktickQuotedString], @"``",@"empty string"); + + XCTAssertEqualObjects([@"tbl1" mySQLBacktickQuotedString], @"`tbl1`", @"regular string"); + + XCTAssertEqualObjects([@"tbl`1" mySQLBacktickQuotedString], @"`tbl``1`",@"string with control character"); + + XCTAssertEqualObjects([@"tbl``" mySQLBacktickQuotedString], @"`tbl`````",@"string with escaped control character at end"); +} + +- (void)test_mySQLTickQuotedString +{ + XCTAssertEqualObjects([@"" mySQLTickQuotedString], @"''",@"empty string"); + + XCTAssertEqualObjects([@"tbl1" mySQLTickQuotedString], @"'tbl1'", @"regular string"); + + XCTAssertEqualObjects([@"tbl'1" mySQLTickQuotedString], @"'tbl''1'",@"string with control character"); + + XCTAssertEqualObjects([@"tbl''" mySQLTickQuotedString], @"'tbl'''''",@"string with escaped control character at end"); +} + +- (void)test_stringForDataBytesLengthEncoding +{ + { + const char chr = '\0'; + NSString *conv = [NSString stringForDataBytes:&chr length:0 encoding:NSISOLatin1StringEncoding]; + XCTAssertEqualObjects(conv, @"",@"empty string test"); + } + { + const char *cstr = "an ASCII C string"; + NSString *conv = [NSString stringForDataBytes:cstr length:strlen(cstr) encoding:NSASCIIStringEncoding]; + XCTAssertEqualObjects(conv, @"an ASCII C string", @"simple ASCII string test"); + } + { + // the euro sign is the tricky part + // ISO-8859-1 (aka Latin1): not supported, codepoint 0x80 is not in use + // ISO-8859-1 + ISO/IEC 6429: not supported, codepoint 0x80 is PAD control character + // ISO-8859-15 (aka Latin9): € is at 0xA4, codepoint 0x80 is PAD control character + // Windows cp1252 (aka latin1 in mysql): € is at 0x80, codepoint 0xA4 is "¤" + const char cstr[] = {'\xE4','-','\xDF','-','\x80','\0'}; + NSString *conv = [NSString stringForDataBytes:cstr length:strlen(cstr) encoding:NSWindowsCP1252StringEncoding]; + XCTAssertEqualObjects(conv, @"ä-ß-€",@"handling of cp1252 special characters"); + + unsigned char latin9 = 0xA4; + NSString *conv2 = [NSString stringForDataBytes:&latin9 length:1 encoding:CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingISOLatin9)]; + XCTAssertEqualObjects(conv2, @"€",@"handling of iso-8859-15 special characters"); + } + { + const char *cstr = "エスキューエル"; + NSString *conv = [NSString stringForDataBytes:cstr length:strlen(cstr) encoding:NSUTF8StringEncoding]; + XCTAssertEqualObjects(conv, @"エスキューエル",@"handling of valid utf-8 string"); + } + { + // this is a test for a certain mysql issue: + // mysql limits field names to 255 characters and will even cut multibyte chars in the middle, + // if neccesary. This will create invalid characters which cause NSString + // to fail and return nil on the whole string. Since we know that, we can + // at least try to return something. + char cstr[] = {'\xE3','\x82','\xA8','\xE3','\x82','\xB9','\xE3','\x82','\xAD','\xE3','\x83','\xA5','\xE3','\x83','\xBC','\xE3','\x82','\xA8','\xE3','\x83','\xAB','\0'}; // エスキューエル + cstr[strlen(cstr)-2] = '\0'; //simulate cutting off the string + NSString *conv = [NSString stringForDataBytes:cstr length:strlen(cstr) encoding:NSUTF8StringEncoding]; + XCTAssertNotNil(conv, @"handling of invalid utf8 sequences"); + } +} + +@end diff --git a/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj b/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj index b3b58052..32dca24c 100644 --- a/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj +++ b/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 17E3A57C1885A286009CF372 /* SPMySQLDataTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = 17E3A57A1885A286009CF372 /* SPMySQLDataTypes.m */; }; 507FF1E51BC0D82300104523 /* DataConversion_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF1811BC0C64100104523 /* DataConversion_Tests.m */; }; 507FF23B1BC0E8CA00104523 /* SPMySQL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC2EF5B0486A6940098B216 /* SPMySQL.framework */; }; + 507FF23D1BC157B500104523 /* SPMySQLStringAdditions_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF23C1BC157B500104523 /* SPMySQLStringAdditions_Tests.m */; }; 580A331E14D75CF7000D6933 /* SPMySQLGeometryData.h in Headers */ = {isa = PBXBuildFile; fileRef = 580A331C14D75CF7000D6933 /* SPMySQLGeometryData.h */; settings = {ATTRIBUTES = (Public, ); }; }; 580A331F14D75CF7000D6933 /* SPMySQLGeometryData.m in Sources */ = {isa = PBXBuildFile; fileRef = 580A331D14D75CF7000D6933 /* SPMySQLGeometryData.m */; }; 583C734A17A489CC0056B284 /* SPMySQLStreamingResultStoreDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 583C734917A489CC0056B284 /* SPMySQLStreamingResultStoreDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -98,6 +99,7 @@ 507FF1811BC0C64100104523 /* DataConversion_Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DataConversion_Tests.m; sourceTree = ""; }; 507FF1D51BC0D7D300104523 /* SPMySQL Unit Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SPMySQL Unit Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 507FF1D81BC0D7D300104523 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 507FF23C1BC157B500104523 /* SPMySQLStringAdditions_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPMySQLStringAdditions_Tests.m; sourceTree = ""; }; 580A331C14D75CF7000D6933 /* SPMySQLGeometryData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLGeometryData.h; path = Source/SPMySQLGeometryData.h; sourceTree = ""; }; 580A331D14D75CF7000D6933 /* SPMySQLGeometryData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPMySQLGeometryData.m; path = Source/SPMySQLGeometryData.m; sourceTree = ""; }; 583C734917A489CC0056B284 /* SPMySQLStreamingResultStoreDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLStreamingResultStoreDelegate.h; path = Source/SPMySQLStreamingResultStoreDelegate.h; sourceTree = ""; }; @@ -282,6 +284,7 @@ children = ( 507FF1D81BC0D7D300104523 /* Info.plist */, 507FF1811BC0C64100104523 /* DataConversion_Tests.m */, + 507FF23C1BC157B500104523 /* SPMySQLStringAdditions_Tests.m */, ); name = "Unit Tests"; path = "SPMySQL Unit Tests"; @@ -548,6 +551,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 507FF23D1BC157B500104523 /* SPMySQLStringAdditions_Tests.m in Sources */, 507FF1E51BC0D82300104523 /* DataConversion_Tests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -731,7 +735,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_ARC = NO; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -765,7 +769,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_ARC = NO; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -795,7 +799,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_ARC = NO; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -889,7 +893,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_ARC = NO; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLResult.m b/Frameworks/SPMySQLFramework/Source/SPMySQLResult.m index 5f54960c..2e1cb2ba 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLResult.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLResult.m @@ -318,7 +318,7 @@ static id NSNullPointer; { return [[[NSString alloc] initWithBytes:bytes length:length encoding:stringEncoding] autorelease]; } - +#warning duplicate code with Data Conversion.m stringForDataBytes:length:encoding: (↑, ↓) - (NSString *)_lossyStringWithBytes:(const void *)bytes length:(NSUInteger)length wasLossy:(BOOL *)outLossy { if(!bytes || !length) return @""; //to match -[NSString initWithBytes:length:encoding:] -- cgit v1.2.3 From 80f8cae8ea9db39c1d4d4dee0bd7ae3f756ad62f Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 5 Oct 2015 00:01:26 +0200 Subject: Additional debug code for #2163 --- Source/SPDataStorage.h | 3 ++ Source/SPDataStorage.m | 121 +++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 99 insertions(+), 25 deletions(-) diff --git a/Source/SPDataStorage.h b/Source/SPDataStorage.h index bb45f71b..d09eb696 100644 --- a/Source/SPDataStorage.h +++ b/Source/SPDataStorage.h @@ -47,6 +47,9 @@ NSUInteger numberOfColumns; NSUInteger editedRowCount; + + NSString *_debugInfo; + uint64_t _debugTime; } /* Setting result store */ diff --git a/Source/SPDataStorage.m b/Source/SPDataStorage.m index 17cc3a04..bae17111 100644 --- a/Source/SPDataStorage.m +++ b/Source/SPDataStorage.m @@ -32,13 +32,18 @@ #import "SPObjectAdditions.h" #import #include +#include @interface SPDataStorage (Private_API) - (void) _checkNewRow:(NSMutableArray *)aRow; +- (void)_recordClearingUnloadedColumnsAt:(uint64_t)now from:(NSArray *)callStack; +- (void)_assesUnloadedColumnsIsSet; @end +static uint64_t _elapsedMilliSecondsSinceAbsoluteTime(uint64_t comparisonTime); + @implementation SPDataStorage static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore, NSUInteger rowIndex) @@ -60,7 +65,13 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore NSUInteger i; editedRowCount = 0; SPClear(editedRows); - if (unloadedColumns) free(unloadedColumns), unloadedColumns = NULL; + @synchronized(self) { + if (unloadedColumns) { + [self _recordClearingUnloadedColumnsAt:mach_absolute_time() from:[NSThread callStackSymbols]]; + free(unloadedColumns), unloadedColumns = NULL; + } + } + if (dataStorage) { @@ -81,9 +92,11 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore [self resultStoreDidFinishLoadingData:dataStorage]; } - unloadedColumns = calloc(numberOfColumns, sizeof(BOOL)); - for (i = 0; i < numberOfColumns; i++) { - unloadedColumns[i] = NO; + @synchronized(self) { + unloadedColumns = calloc(numberOfColumns, sizeof(BOOL)); + for (i = 0; i < numberOfColumns; i++) { + unloadedColumns[i] = NO; + } } } @@ -110,10 +123,12 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore NSMutableArray *dataArray = SPMySQLResultStoreGetRow(dataStorage, anIndex); // Modify unloaded cells as appropriate - for (NSUInteger i = 0; i < numberOfColumns; i++) { - NSAssert(unloadedColumns != NULL, @"unloadedColumns not loaded!"); - if (unloadedColumns[i]) { - CFArraySetValueAtIndex((CFMutableArrayRef)dataArray, i, [SPNotLoaded notLoaded]); + @synchronized(self) { + [self _assesUnloadedColumnsIsSet]; + for (NSUInteger i = 0; i < numberOfColumns; i++) { + if (unloadedColumns[i]) { + CFArraySetValueAtIndex((CFMutableArrayRef)dataArray, i, [SPNotLoaded notLoaded]); + } } } @@ -140,9 +155,11 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore } // If the specified column is not loaded, return a SPNotLoaded reference - NSAssert(unloadedColumns != NULL, @"unloadedColumns not loaded!"); - if (unloadedColumns[columnIndex]) { - return [SPNotLoaded notLoaded]; + @synchronized(self) { + [self _assesUnloadedColumnsIsSet]; + if (unloadedColumns[columnIndex]) { + return [SPNotLoaded notLoaded]; + } } // Return the content @@ -175,9 +192,11 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore } // If the specified column is not loaded, return a SPNotLoaded reference - NSAssert(unloadedColumns != NULL, @"unloadedColumns not loaded!"); - if (unloadedColumns[columnIndex]) { - return [SPNotLoaded notLoaded]; + @synchronized(self) { + [self _assesUnloadedColumnsIsSet]; + if (unloadedColumns[columnIndex]) { + return [SPNotLoaded notLoaded]; + } } // Return the content @@ -203,9 +222,11 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore [NSException raise:NSRangeException format:@"Requested storage column (col %llu) beyond bounds (%llu)", (unsigned long long)columnIndex, (unsigned long long)numberOfColumns]; } - NSAssert(unloadedColumns != NULL, @"unloadedColumns not loaded!"); - if (unloadedColumns[columnIndex]) { - return YES; + @synchronized(self) { + [self _assesUnloadedColumnsIsSet]; + if (unloadedColumns[columnIndex]) { + return YES; + } } return [dataStorage cellIsNullAtRow:rowIndex column:columnIndex]; @@ -236,10 +257,12 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore targetRow = SPMySQLResultStoreGetRow(dataStorage, state->state); // Modify unloaded cells as appropriate - for (NSUInteger i = 0; i < numberOfColumns; i++) { - NSAssert(unloadedColumns != NULL, @"unloadedColumns not loaded!"); - if (unloadedColumns[i]) { - CFArraySetValueAtIndex((CFMutableArrayRef)targetRow, i, [SPNotLoaded notLoaded]); + @synchronized(self) { + [self _assesUnloadedColumnsIsSet]; + for (NSUInteger i = 0; i < numberOfColumns; i++) { + if (unloadedColumns[i]) { + CFArraySetValueAtIndex((CFMutableArrayRef)targetRow, i, [SPNotLoaded notLoaded]); + } } } } @@ -397,8 +420,10 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore if (columnIndex >= numberOfColumns) { [NSException raise:NSRangeException format:@"Invalid column set as unloaded; requested column index (%llu) beyond bounds (%llu)", (unsigned long long)columnIndex, (unsigned long long)numberOfColumns]; } - NSAssert(unloadedColumns != NULL, @"unloadedColumns not loaded!"); - unloadedColumns[columnIndex] = YES; + @synchronized(self) { + [self _assesUnloadedColumnsIsSet]; + unloadedColumns[columnIndex] = YES; + } } #pragma mark - Basic information @@ -451,6 +476,9 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore numberOfColumns = 0; editedRowCount = 0; + + _debugInfo = nil; + _debugTime = mach_absolute_time(); } return self; } @@ -458,8 +486,19 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore - (void) dealloc { SPClear(dataStorage); SPClear(editedRows); - if (unloadedColumns) free(unloadedColumns), unloadedColumns = NULL; - + @synchronized(self) { + if (unloadedColumns) { + [self _recordClearingUnloadedColumnsAt:mach_absolute_time() from:[NSThread callStackSymbols]]; + free(unloadedColumns), unloadedColumns = NULL; + } + } + // this is very very unlikely, but if another thread had been waiting on the lock + // right before we free'd unloadedColumns, it should get it before we can release + // _debugInfo, too. + @synchronized(self) { + SPClear(_debugInfo); + } + [super dealloc]; } @@ -474,5 +513,37 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore } } +// DO NOT CALL THIS METHOD UNLESS YOU CURRENTLY HAVE A LOCK ON SELF!!! +- (void)_recordClearingUnloadedColumnsAt:(uint64_t)now from:(NSArray *)callStack +{ + _debugTime = now; + SPClear(_debugInfo); + _debugInfo = [[NSString alloc] initWithFormat:@"Thread: %@, Stack: %@",[NSThread currentThread],callStack]; +} + +// DO NOT CALL THIS METHOD UNLESS YOU CURRENTLY HAVE A LOCK ON SELF!!! +- (void)_assesUnloadedColumnsIsSet +{ + if(unloadedColumns != NULL) + return; + + uint64_t timeDiff = _elapsedMilliSecondsSinceAbsoluteTime(_debugTime); + + NSString *msg; + if(!_debugInfo) + msg = [NSString stringWithFormat:@"unloadedColumns is not set and never has been since the object was created %llums ago.",timeDiff]; + else + msg = [NSString stringWithFormat:@"unloadedColumns was last cleared %llums ago at %@",timeDiff,_debugInfo]; + + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:msg userInfo:nil]; +} @end + +static uint64_t _elapsedMilliSecondsSinceAbsoluteTime(uint64_t comparisonTime) +{ + uint64_t elapsedTime_t = mach_absolute_time() - comparisonTime; + Nanoseconds elapsedTime = AbsoluteToNanoseconds(*(AbsoluteTime *)&(elapsedTime_t)); + + return (UnsignedWideToUInt64(elapsedTime) / 1000000ULL); +} -- cgit v1.2.3 From d4641ec90fe1b50fca0256e26338d955290fd8b7 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 5 Oct 2015 02:27:32 +0200 Subject: Trying to fix a range of crashes when closing a connection window Caused by a use-after-free of an unretained ivar --- Source/SPDatabaseDocument.m | 1 + Source/SPFunctions.h | 6 +++++- Source/SPWindowController.h | 10 ++++++++++ Source/SPWindowController.m | 34 ++++++++++++++++++++++++++++++---- Source/SPWindowControllerDelegate.m | 3 ++- 5 files changed, 48 insertions(+), 6 deletions(-) diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index 5c0553f8..dd3baae3 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -6290,6 +6290,7 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; - (void)dealloc { + NSAssert([NSThread isMainThread], @"Calling %s from a background thread is not supported!",__func__); #ifndef SP_CODA /* Unregister observers */ // Unregister observers [prefs removeObserver:self forKeyPath:SPDisplayTableViewVerticalGridlines]; diff --git a/Source/SPFunctions.h b/Source/SPFunctions.h index f2ca9c7e..b68964ca 100644 --- a/Source/SPFunctions.h +++ b/Source/SPFunctions.h @@ -28,6 +28,10 @@ // // More info at - +/** + * Synchronously execute a block on the main thread. + * This function can be called from a background thread as well as from + * the main thread. + */ void SPMainQSync(void (^block)(void)); diff --git a/Source/SPWindowController.h b/Source/SPWindowController.h index c99a2ff2..a86a85f9 100644 --- a/Source/SPWindowController.h +++ b/Source/SPWindowController.h @@ -49,7 +49,17 @@ // Database connection management - (IBAction)addNewConnection:(id)sender; - (IBAction)moveSelectedTabInNewWindow:(id)sender; + +/** + * @danger THIS IS NOT RETAINED!!! + * + * Ever only directly use it on the main thread! + * Do not cache it without retaining first! + * For background threads get it and retain it via the main thread! + * Release it on the main thread again. + */ - (SPDatabaseDocument *)selectedTableDocument; + - (void)updateSelectedTableDocument; - (void)updateAllTabTitles:(id)sender; - (IBAction)closeTab:(id)sender; diff --git a/Source/SPWindowController.m b/Source/SPWindowController.m index db94f5ce..68c89ad8 100644 --- a/Source/SPWindowController.m +++ b/Source/SPWindowController.m @@ -53,7 +53,8 @@ enum { - (void)_updateProgressIndicatorForItem:(NSTabViewItem *)theItem; - (void)_createTitleBarLineHidingView; - (void)_updateLineHidingViewState; - +- (void)_switchOutSelectedTableDocument:(SPDatabaseDocument *)newDoc; +- (void)_selectedTableDocumentDeallocd:(NSNotification *)notification; @end @implementation SPWindowController @@ -63,7 +64,7 @@ enum { - (void)awakeFromNib { - selectedTableDocument = nil; + [self _switchOutSelectedTableDocument:nil]; [[self window] setCollectionBehavior:[[self window] collectionBehavior] | NSWindowCollectionBehaviorFullScreenPrimary]; @@ -141,7 +142,7 @@ enum { */ - (void)updateSelectedTableDocument { - selectedTableDocument = [[tabView selectedTabViewItem] identifier]; + [self _switchOutSelectedTableDocument:[[tabView selectedTabViewItem] identifier]]; [selectedTableDocument didBecomeActiveTabInWindow]; } @@ -388,7 +389,7 @@ enum { */ - (BOOL)respondsToSelector:(SEL)theSelector { - return ([super respondsToSelector:theSelector] || (selectedTableDocument && [selectedTableDocument respondsToSelector:theSelector])); + return ([super respondsToSelector:theSelector] || [selectedTableDocument respondsToSelector:theSelector]); } /** @@ -529,10 +530,35 @@ enum { } } + +- (void)_switchOutSelectedTableDocument:(SPDatabaseDocument *)newDoc +{ + NSAssert([NSThread isMainThread], @"Switching the selectedTableDocument via a background thread is not supported!"); + + // shortcut if there is nothing to do + if(selectedTableDocument == newDoc) return; + + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + if(selectedTableDocument) { + [nc removeObserver:self name:SPDocumentWillCloseNotification object:selectedTableDocument]; + selectedTableDocument = nil; + } + if(newDoc) { + [nc addObserver:self selector:@selector(_selectedTableDocumentDeallocd:) name:SPDocumentWillCloseNotification object:newDoc]; + selectedTableDocument = newDoc; + } +} + +- (void)_selectedTableDocumentDeallocd:(NSNotification *)notification +{ + [self _switchOutSelectedTableDocument:nil]; +} + #pragma mark - - (void)dealloc { + [self _switchOutSelectedTableDocument:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self]; [NSObject cancelPreviousPerformRequestsWithTarget:self]; diff --git a/Source/SPWindowControllerDelegate.m b/Source/SPWindowControllerDelegate.m index 47959f1b..3e94cebf 100644 --- a/Source/SPWindowControllerDelegate.m +++ b/Source/SPWindowControllerDelegate.m @@ -41,6 +41,7 @@ - (void)_updateProgressIndicatorForItem:(NSTabViewItem *)theItem; - (void)_updateLineHidingViewState; +- (void)_switchOutSelectedTableDocument:(SPDatabaseDocument *)newDoc; @end @@ -186,7 +187,7 @@ { if ([[PSMTabDragAssistant sharedDragAssistant] isDragging]) return; - selectedTableDocument = [tabViewItem identifier]; + [self _switchOutSelectedTableDocument:[tabViewItem identifier]]; [selectedTableDocument didBecomeActiveTabInWindow]; if ([[self window] isKeyWindow]) [selectedTableDocument tabDidBecomeKey]; -- cgit v1.2.3 From 31de04a0c45639b2b78405aa4ab3e4696268cc73 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 6 Oct 2015 01:31:09 +0200 Subject: Fix broken keychain access with SSH on 10.6 (fixes #2268) (From the department of commits to be rolled back soon) --- Source/SPKeychain.m | 39 ++++++++++++++++++++---------------- Source/SPOSInfo.m | 5 +++++ sequel-pro.xcodeproj/project.pbxproj | 2 ++ 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/Source/SPKeychain.m b/Source/SPKeychain.m index ac7bd390..48283359 100644 --- a/Source/SPKeychain.m +++ b/Source/SPKeychain.m @@ -31,6 +31,7 @@ #import "SPKeychain.h" #import "SPAlertSheets.h" +#import "SPOSInfo.h" #import #import @@ -212,33 +213,38 @@ - (BOOL)passwordExistsForName:(NSString *)name account:(NSString *)account { #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 - NSMutableDictionary *query = [NSMutableDictionary dictionary]; - - [query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; - [query setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes]; - [query setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit]; - - [query setObject:account forKey:(id)kSecAttrAccount]; - [query setObject:name forKey:(id)kSecAttrService]; - - CFDictionaryRef result = NULL; - - return SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef *)&result) == errSecSuccess; -#else + // "kSecClassGenericPassword" was introduced with the 10.7 SDK. + // It won't work on 10.6 either (meaning this code never matches properly there). + // (That's why there are compile time and runtime checks here) + if([SPOSInfo isOSVersionAtLeastMajor:10 minor:7 patch:0]) { + NSMutableDictionary *query = [NSMutableDictionary dictionary]; + + [query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; + [query setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes]; + [query setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit]; + + [query setObject:account forKey:(id)kSecAttrAccount]; + [query setObject:name forKey:(id)kSecAttrService]; + + CFDictionaryRef result = NULL; + + return SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef *)&result) == errSecSuccess; + } +#endif SecKeychainItemRef item; SecKeychainSearchRef search = NULL; NSInteger numberOfItemsFound = 0; SecKeychainAttributeList list; SecKeychainAttribute attributes[2]; - + // Check supplied variables and replaces nils with empty strings if (!name) name = @""; if (!account) account = @""; - + attributes[0].tag = kSecAccountItemAttr; attributes[0].data = (void *)[account UTF8String]; // Account name attributes[0].length = (UInt32)strlen([account UTF8String]); // Length of account name (bytes) - + attributes[1].tag = kSecServiceItemAttr; attributes[1].data = (void *)[name UTF8String]; // Service name attributes[1].length = (UInt32)strlen([name UTF8String]); // Length of service name (bytes) @@ -257,7 +263,6 @@ if (search) CFRelease(search); return (numberOfItemsFound > 0); -#endif } /** diff --git a/Source/SPOSInfo.m b/Source/SPOSInfo.m index 8d91c067..b862e898 100644 --- a/Source/SPOSInfo.m +++ b/Source/SPOSInfo.m @@ -30,6 +30,11 @@ #import "SPOSInfo.h" +// Needed because this class is also compiled with SequelProTunnelAssistant which can't access SPConstants.h +#ifndef __MAC_10_10 +#define __MAC_10_10 101000 +#endif + #if __MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_10 // This code is available since 10.8 but public only since 10.10 typedef struct { diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index bdd39761..02a15375 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -191,6 +191,7 @@ 506CE9311A311C6C0039F736 /* SPTableContentFilterController.m in Sources */ = {isa = PBXBuildFile; fileRef = 506CE9301A311C6C0039F736 /* SPTableContentFilterController.m */; }; 507FF1121BBCC57600104523 /* SPFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF1111BBCC57600104523 /* SPFunctions.m */; }; 507FF1621BBF0D5000104523 /* SPTableCopyTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 112730551180788A000737FD /* SPTableCopyTest.m */; }; + 507FF2421BC33BBC00104523 /* SPOSInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 50EAB5B71A8FBB08008F627A /* SPOSInfo.m */; }; 50A9F8B119EAD4B90053E571 /* SPGotoDatabaseController.m in Sources */ = {isa = PBXBuildFile; fileRef = 50A9F8B019EAD4B90053E571 /* SPGotoDatabaseController.m */; }; 50D3C3491A75B8A800B5429C /* GotoDatabaseDialog.xib in Resources */ = {isa = PBXBuildFile; fileRef = 50D3C34B1A75B8A800B5429C /* GotoDatabaseDialog.xib */; }; 50D3C3521A77135F00B5429C /* SPParserUtils.c in Sources */ = {isa = PBXBuildFile; fileRef = 50D3C3501A77135F00B5429C /* SPParserUtils.c */; }; @@ -3140,6 +3141,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 507FF2421BC33BBC00104523 /* SPOSInfo.m in Sources */, 586F457B0FDB269E00B428D7 /* RegexKitLite.m in Sources */, 58CDB3410FCE141900F8ACA3 /* SequelProTunnelAssistant.m in Sources */, 58CDB3420FCE142500F8ACA3 /* SPKeychain.m in Sources */, -- cgit v1.2.3 From 8170ca8faa5d328e86f0163e0c74a42fc246a04c Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 6 Oct 2015 21:27:35 +0200 Subject: Fix connection view input fields accepting multi-line input (#2269) --- Interfaces/English.lproj/ConnectionView.xib | 44 ++++++++++++++--------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Interfaces/English.lproj/ConnectionView.xib b/Interfaces/English.lproj/ConnectionView.xib index a6bfe95c..85f9728a 100644 --- a/Interfaces/English.lproj/ConnectionView.xib +++ b/Interfaces/English.lproj/ConnectionView.xib @@ -753,7 +753,7 @@ YES -1804599231 - 272630784 + 272630848 @@ -813,7 +813,7 @@ YES -1804599231 - 272630784 + 272630848 optional @@ -853,7 +853,7 @@ YES -1804599231 - 272630784 + 272630848 @@ -892,7 +892,7 @@ YES -1804599231 - 272630784 + 272630848 @@ -931,7 +931,7 @@ YES -1804599231 - 272630784 + 272630848 optional @@ -971,7 +971,7 @@ YES -1804599231 - 272630784 + 272630848 3306 @@ -1286,7 +1286,7 @@ YES -1804599231 - 272630784 + 272630848 @@ -1325,7 +1325,7 @@ YES -1804599231 - 272630784 + 272630848 @@ -1364,7 +1364,7 @@ YES -1804599231 - 272630784 + 272630848 optional @@ -1404,7 +1404,7 @@ YES -1804599231 - 272630784 + 272630848 optional @@ -1444,7 +1444,7 @@ YES -1804599231 - 272630784 + 272630848 optional @@ -1755,7 +1755,7 @@ YES -1804599231 - 272630784 + 272630848 @@ -1796,7 +1796,7 @@ YES -1804599231 - 272630784 + 272630848 @@ -1837,7 +1837,7 @@ YES -1804599231 - 272630784 + 272630848 @@ -1878,7 +1878,7 @@ YES -1804599231 - 272630784 + 272630848 optional @@ -1920,7 +1920,7 @@ YES -1804599231 - 272630784 + 272630848 3306 @@ -1962,7 +1962,7 @@ YES -1804599231 - 272630784 + 272630848 @@ -2003,7 +2003,7 @@ YES -1804599231 - 272630784 + 272630848 @@ -2044,7 +2044,7 @@ YES -1804599231 - 272630784 + 272630848 @@ -2085,7 +2085,7 @@ YES -1804599231 - 272630784 + 272630848 optional @@ -2127,7 +2127,7 @@ YES -1804599231 - 272630784 + 272630848 optional @@ -2214,7 +2214,7 @@ YES -2076180416 - 272631296 + 272631360 -- cgit v1.2.3 From 9943a8baad6739809e7f8e8ae5a9c9daaf1d6899 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 6 Oct 2015 23:30:04 +0200 Subject: Fix the way Sequel Pro was constructing command line arguments for SSH (#2273) --- Source/SPSSHTunnel.m | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/Source/SPSSHTunnel.m b/Source/SPSSHTunnel.m index 4a0ec308..390c516c 100644 --- a/Source/SPSSHTunnel.m +++ b/Source/SPSSHTunnel.m @@ -317,7 +317,8 @@ static unsigned short getRandomPort(); // Prepare to set up the arguments for the task taskArguments = [[NSMutableArray alloc] init]; - +#define TA(_name,_value) [taskArguments addObjectsFromArray:@[_name,_value]] + // Enable verbose mode for message parsing [taskArguments addObject:@"-v"]; @@ -329,7 +330,7 @@ static unsigned short getRandomPort(); if (connectionMuxingEnabled) { // Enable automatic connection muxing/sharing, for faster connections - [taskArguments addObject:@"-o ControlMaster=auto"]; + 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 @@ -337,39 +338,39 @@ static unsigned short getRandomPort(); 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); - [taskArguments addObject:[NSString stringWithFormat:@"-o ControlPath=%@/SPSSH-%@", [NSFileManager temporaryDirectory], [[[NSData dataWithBytes:hashedPathResult length:16] dataToHexString] substringToIndex:8]]]; + NSString *hashedString = [[[NSData dataWithBytes:hashedPathResult length:16] dataToHexString] substringToIndex:8]; + TA(@"-o",([NSString stringWithFormat:@"ControlPath=%@/SPSSH-%@", [NSFileManager temporaryDirectory], hashedString])); } else { // Disable muxing if requested - [taskArguments addObject:@"-S none"]; - [taskArguments addObject:@"-o ControlMaster=no"]; + TA(@"-S", @"none"); + TA(@"-o", @"ControlMaster=no"); } // If the port forwarding fails, exit - as this is the primary use case for the instance - [taskArguments addObject:@"-o ExitOnForwardFailure=yes"]; + TA(@"-o",@"ExitOnForwardFailure=yes"); // Specify a connection timeout based on the preferences value - [taskArguments addObject:[NSString stringWithFormat:@"-o ConnectTimeout=%ld", (long)connectionTimeout]]; + TA(@"-o",([NSString stringWithFormat:@"ConnectTimeout=%ld", (long)connectionTimeout])); // Allow three password prompts - [taskArguments addObject:@"-o NumberOfPasswordPrompts=3"]; + TA(@"-o",@"NumberOfPasswordPrompts=3"); // Specify an identity file if available if (identityFilePath) { - [taskArguments addObject:@"-i"]; - [taskArguments addObject:identityFilePath]; + TA(@"-i", identityFilePath); } // If keepalive is set in the preferences, use the same value for the SSH tunnel if (useKeepAlive && keepAliveInterval) { - [taskArguments addObject:@"-o TCPKeepAlive=no"]; - [taskArguments addObject:[NSString stringWithFormat:@"-o ServerAliveInterval=%ld", (long)ceil(keepAliveInterval)]]; - [taskArguments addObject:@"-o ServerAliveCountMax=1"]; + TA(@"-o", @"TCPKeepAlive=no"); + TA(@"-o", ([NSString stringWithFormat:@"ServerAliveInterval=%ld", (long)ceil(keepAliveInterval)])); + TA(@"-o", @"ServerAliveCountMax=1"); } // Specify the port, host, and authentication details if (sshPort) { - [taskArguments addObject:[NSString stringWithFormat:@"-p %ld", (long)sshPort]]; + TA(@"-p", ([NSString stringWithFormat:@"%ld", (long)sshPort])); } if ([sshLogin length]) { [taskArguments addObject:[NSString stringWithFormat:@"%@@%@", sshLogin, sshHost]]; @@ -377,10 +378,10 @@ static unsigned short getRandomPort(); [taskArguments addObject:sshHost]; } if (useHostFallback) { - [taskArguments addObject:[NSString stringWithFormat:@"-L %ld:127.0.0.1:%ld", (long)localPort, (long)remotePort]]; - [taskArguments addObject:[NSString stringWithFormat:@"-L %ld:%@:%ld", (long)localPortFallback, remoteHost, (long)remotePort]]; + 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 { - [taskArguments addObject:[NSString stringWithFormat:@"-L %ld:%@:%ld", (long)localPort, remoteHost, (long)remotePort]]; + TA(@"-L", ([NSString stringWithFormat:@"%ld:%@:%ld", (long)localPort, remoteHost, (long)remotePort])); } [task setArguments:taskArguments]; @@ -451,6 +452,7 @@ static unsigned short getRandomPort(); [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; SPClear(taskEnvironment); +#undef TA SPClear(taskArguments); [pool release]; -- cgit v1.2.3 From e3b8f07f34cd630bc285357d016e38c1926e362a Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 8 Oct 2015 02:36:45 +0200 Subject: * Turn some successive onMainThread ops into a block * Move a lock() that could become unbalanced and theoretically cause havoc --- Source/SPDataImport.m | 53 ++++++++++++++++++++++++++------------------------- Source/SPFileHandle.m | 3 ++- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/Source/SPDataImport.m b/Source/SPDataImport.m index 5cf67976..8ce41afc 100644 --- a/Source/SPDataImport.m +++ b/Source/SPDataImport.m @@ -160,15 +160,12 @@ */ - (void)closeAndStopProgressSheet { - if (![NSThread isMainThread]) { - [self performSelectorOnMainThread:@selector(closeAndStopProgressSheet) withObject:nil waitUntilDone:YES]; - return; - } - - [NSApp endSheet:singleProgressSheet]; - [singleProgressSheet orderOut:nil]; - [[singleProgressBar onMainThread] stopAnimation:self]; - [[singleProgressBar onMainThread] setMaxValue:100]; + SPMainQSync(^{ + [NSApp endSheet:singleProgressSheet]; + [singleProgressSheet orderOut:nil]; + [singleProgressBar stopAnimation:self]; + [singleProgressBar setMaxValue:100]; + }); } #pragma mark - @@ -762,16 +759,18 @@ if ([csvFileHandle compressionFormat] == SPBzip2Compression) useIndeterminate = YES; // Reset progress interface - [[errorsView onMainThread] setString:@""]; - [[singleProgressTitle onMainThread] setStringValue:NSLocalizedString(@"Importing CSV", @"text showing that the application is importing CSV")]; - [[singleProgressText onMainThread] setStringValue:NSLocalizedString(@"Reading...", @"text showing that app is reading dump")]; - [[singleProgressBar onMainThread] setIndeterminate:YES]; - [[singleProgressBar onMainThread] setUsesThreadedAnimation:YES]; - [[singleProgressBar onMainThread] startAnimation:self]; - - // Open the progress sheet - [[NSApp onMainThread] beginSheet:singleProgressSheet modalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:nil contextInfo:nil]; - [[singleProgressSheet onMainThread] makeKeyWindow]; + SPMainQSync(^{ + [errorsView setString:@""]; + [singleProgressTitle setStringValue:NSLocalizedString(@"Importing CSV", @"text showing that the application is importing CSV")]; + [singleProgressText setStringValue:NSLocalizedString(@"Reading...", @"text showing that app is reading dump")]; + [singleProgressBar setIndeterminate:YES]; + [singleProgressBar setUsesThreadedAnimation:YES]; + [singleProgressBar startAnimation:self]; + + // Open the progress sheet + [NSApp beginSheet:singleProgressSheet modalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:nil contextInfo:nil]; + [singleProgressSheet makeKeyWindow]; + }); [tableDocumentInstance setQueryMode:SPImportExportQueryMode]; @@ -936,11 +935,13 @@ } // Reset progress interface and open the progress sheet - [[singleProgressBar onMainThread] setIndeterminate:useIndeterminate]; - [[singleProgressBar onMainThread] setMaxValue:fileTotalLength]; - [[singleProgressBar onMainThread] startAnimation:self]; - [[NSApp onMainThread] beginSheet:singleProgressSheet modalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:nil contextInfo:nil]; - [[singleProgressSheet onMainThread] makeKeyWindow]; + SPMainQSync(^{ + [singleProgressBar setIndeterminate:useIndeterminate]; + [singleProgressBar setMaxValue:fileTotalLength]; + [singleProgressBar startAnimation:self]; + [NSApp beginSheet:singleProgressSheet modalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:nil contextInfo:nil]; + [singleProgressSheet makeKeyWindow]; + }); // Set up index sets for use during row enumeration for (i = 0; i < [fieldMappingArray count]; i++) { @@ -1183,7 +1184,7 @@ // Select the new table // Update current database tables - [tablesListInstance performSelectorOnMainThread:@selector(updateTables:) withObject:self waitUntilDone:YES]; + [[tablesListInstance onMainThread] updateTables:self]; // Re-query the structure of all databases in the background [NSThread detachNewThreadWithName:SPCtxt(@"SPNavigatorController database structure querier",tableDocumentInstance) target:[tableDocumentInstance databaseStructureRetrieval] selector:@selector(queryDbStructureWithUserInfo:) object:@{@"forceUpdate" : @YES}]; @@ -1674,7 +1675,7 @@ cleanup: - (void)showErrorSheetWithMessage:(NSString*)message { if (![NSThread isMainThread]) { - [self performSelectorOnMainThread:@selector(showErrorSheetWithMessage:) withObject:message waitUntilDone:YES]; + [[self onMainThread] showErrorSheetWithMessage:message]; return; } diff --git a/Source/SPFileHandle.m b/Source/SPFileHandle.m index 62204252..3971e18b 100644 --- a/Source/SPFileHandle.m +++ b/Source/SPFileHandle.m @@ -299,9 +299,10 @@ // Throw an exception if the file is closed if (fileIsClosed) [NSException raise:NSInternalInconsistencyException format:@"Cannot write to a file handle after it has been closed"]; + pthread_mutex_lock(&bufferLock); + // Add the data to the buffer if ([data length]) { - pthread_mutex_lock(&bufferLock); [buffer appendData:data]; allDataWritten = NO; bufferDataLength += [data length]; -- cgit v1.2.3 From 1dedadcaba2cd474a251a24d893dc3b7b2a719a7 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 8 Oct 2015 13:50:38 +0200 Subject: * Change a method name to fit standard naming conventions * Remove a redundant ivar (and while we are at it, restructure some code) * And then there was this gem: (...) if (fileMode == O_RDONLY) { int i, c; char bzbuf[4]; const char *charFileMode = fileMode == O_WRONLY ? "wb" : "rb"; (...) --- Source/SPDataImport.m | 4 +- Source/SPExportFile.m | 2 +- Source/SPExporter.m | 2 +- Source/SPFileHandle.h | 21 ++--- Source/SPFileHandle.m | 209 +++++++++++++++++++++++--------------------------- 5 files changed, 108 insertions(+), 130 deletions(-) diff --git a/Source/SPDataImport.m b/Source/SPDataImport.m index 8ce41afc..64f314a9 100644 --- a/Source/SPDataImport.m +++ b/Source/SPDataImport.m @@ -389,7 +389,7 @@ [[NSFileManager defaultManager] removeItemAtPath:filename error:nil]; return; } - fileIsCompressed = [sqlFileHandle isCompressed]; + fileIsCompressed = ([sqlFileHandle compressionFormat] != SPNoCompression); // Grab the file length fileTotalLength = (NSUInteger)[[[[NSFileManager defaultManager] attributesOfItemAtPath:filename error:NULL] objectForKey:NSFileSize] longLongValue]; @@ -752,7 +752,7 @@ // Grab the file length and status fileTotalLength = (NSUInteger)[[[[NSFileManager defaultManager] attributesOfItemAtPath:filename error:NULL] objectForKey:NSFileSize] longLongValue]; if (!fileTotalLength) fileTotalLength = 1; - fileIsCompressed = [csvFileHandle isCompressed]; + fileIsCompressed = ([csvFileHandle compressionFormat] != SPNoCompression); // If importing a bzipped file, use indeterminate progress bars as no progress is available BOOL useIndeterminate = NO; diff --git a/Source/SPExportFile.m b/Source/SPExportFile.m index 132e1028..5d1c040d 100644 --- a/Source/SPExportFile.m +++ b/Source/SPExportFile.m @@ -191,7 +191,7 @@ return; } - [[self exportFileHandle] setShouldWriteWithCompressionFormat:fileCompressionFormat]; + [[self exportFileHandle] setCompressionFormat:fileCompressionFormat]; } #pragma mark - diff --git a/Source/SPExporter.m b/Source/SPExporter.m index 195d900d..db5cf809 100644 --- a/Source/SPExporter.m +++ b/Source/SPExporter.m @@ -95,7 +95,7 @@ exportOutputCompressFile = compress; - [[[self exportOutputFile] exportFileHandle] setShouldWriteWithCompressionFormat:(compress) ? [self exportOutputCompressionFormat] : SPNoCompression]; + [[[self exportOutputFile] exportFileHandle] setCompressionFormat:(compress) ? [self exportOutputCompressionFormat] : SPNoCompression]; } /** diff --git a/Source/SPFileHandle.h b/Source/SPFileHandle.h index 23162385..5622b1b6 100644 --- a/Source/SPFileHandle.h +++ b/Source/SPFileHandle.h @@ -28,6 +28,7 @@ // // More info at +union SPSomeFileHandle; /** * @class SPFileHandle SPFileHandle.h * @@ -39,7 +40,7 @@ */ @interface SPFileHandle : NSObject { - void *wrappedFile; + union SPSomeFileHandle *wrappedFile; char *wrappedFilePath; NSMutableData *buffer; @@ -53,7 +54,6 @@ BOOL dataWritten; BOOL allDataWritten; BOOL fileIsClosed; - BOOL useCompression; SPFileCompressionFormat compressionFormat; } @@ -69,7 +69,7 @@ #pragma mark Initialisation // Returns a file handle initialised with a file -- (id)initWithFile:(void *)theFile fromPath:(const char *)path mode:(int)mode; +- (id)initWithFile:(FILE *)theFile fromPath:(const char *)path mode:(int)mode; #pragma mark - #pragma mark Data reading @@ -87,7 +87,11 @@ #pragma mark Data writing // Set whether data should be written in the supplied compression format (defaults to NO on a fresh object) -- (void)setShouldWriteWithCompressionFormat:(SPFileCompressionFormat)useCompressionFormat; +// This has no influence on reading data. +- (void)setCompressionFormat:(SPFileCompressionFormat)useCompressionFormat; + +// Returns the compression format being used. Currently gzip or bzip2 only. +- (SPFileCompressionFormat)compressionFormat; // Write the provided data to the file - (void)writeData:(NSData *)data; @@ -98,13 +102,4 @@ // Prevents further access to the file - (void)closeFile; -#pragma mark - -#pragma mark File information - -// Returns whether compression is enabled on the file -- (BOOL)isCompressed; - -// Returns the compression format being used. Currently gzip or bzip2 only. -- (SPFileCompressionFormat)compressionFormat; - @end diff --git a/Source/SPFileHandle.m b/Source/SPFileHandle.m index 3971e18b..3e0489a4 100644 --- a/Source/SPFileHandle.m +++ b/Source/SPFileHandle.m @@ -37,6 +37,12 @@ // waits until some has been written out. This can affect speed and memory usage. #define SPFH_MAX_WRITE_BUFFER_SIZE 1048576 +union SPSomeFileHandle { + FILE *file; + BZFILE *bzfile; + gzFile *gzfile; +}; + @interface SPFileHandle () - (void)_writeBufferToData; @@ -57,14 +63,14 @@ * theFile is a FILE when compression is disabled, a gzFile when gzip compression is enabled * or a BZFILE when bzip2 compression is enabled. */ -- (id)initWithFile:(void *)theFile fromPath:(const char *)path mode:(int)mode +- (id)initWithFile:(FILE *)theFile fromPath:(const char *)path mode:(int)mode { if ((self = [super init])) { dataWritten = NO; allDataWritten = YES; fileIsClosed = NO; - wrappedFile = theFile; + wrappedFile = malloc(sizeof(*wrappedFile)); //FIXME ivar can be moved to .m file with "modern objc", replacing the opaque struct pointer wrappedFilePath = malloc(strlen(path) + 1); strcpy(wrappedFilePath, path); @@ -83,65 +89,62 @@ bufferPosition = 0; endOfFile = NO; - useCompression = NO; compressionFormat = SPNoCompression; + processingThread = nil; // If in read mode, set up the buffer if (fileMode == O_RDONLY) { - - int i, c; - char bzbuf[4]; - const char *charFileMode = fileMode == O_WRONLY ? "wb" : "rb"; - - BZFILE *bzfile; - gzFile *gzfile = gzopen(path, charFileMode); - - // Set gzip buffer - gzbuffer(gzfile, 131072); - - // Get the first 4 bytes from the file - for (i = 0; (c = getc(wrappedFile)) != EOF && i < 4; bzbuf[i++] = c); - - rewind(wrappedFile); - - // Test to see if the file is gzip compressed - BOOL isGzip = !gzdirect(gzfile); - - // Test to see if the first 2 bytes extracted from the file match the Bzip2 signature/magic number - // (BZ). The 3rd byte should be either 'h' (Huffman encoding) or 0 (Bzip1 - deprecated) to - // indicate the Bzip version. Finally, the 4th byte should be a number between 1 and 9 that indicates - // the block size used. - - BOOL isBzip2 = ((bzbuf[0] == 'B') && (bzbuf[1] == 'Z')) && - ((bzbuf[2] == 'h') || (bzbuf[2] == '0')) && - ((bzbuf[3] >= 0x31) && (bzbuf[3] <= 0x39)); - - if (isBzip2) bzfile = BZ2_bzopen(path, charFileMode); - - useCompression = isGzip || isBzip2; - - if (useCompression) { - if (isGzip) { + // Test for GZIP (by opening the file with gz and checking what happens) + { + gzFile *gzfile = gzopen(path, "rb"); + + // Set gzip buffer + gzbuffer(gzfile, 131072); + + // Test to see if the file is gzip compressed + if(!gzdirect(gzfile)) { compressionFormat = SPGzipCompression; - wrappedFile = gzfile; + wrappedFile->gzfile = gzfile; } - else if (isBzip2) { - compressionFormat = SPBzip2Compression; - wrappedFile = bzfile; + else { + // ...not gzip gzclose(gzfile); } + } + // Test for BZ (by checking the file header) + if(compressionFormat == SPNoCompression) { + char bzbuf[4]; + int i, c; - fclose(theFile); + // Get the first 4 bytes from the file + for (i = 0; (c = getc(theFile)) != EOF && i < 4; bzbuf[i++] = c); + + rewind(theFile); + + // Test to see if the first 2 bytes extracted from the file match the Bzip2 signature/magic number + // (BZ). The 3rd byte should be either 'h' (Huffman encoding) or 0 (Bzip1 - deprecated) to + // indicate the Bzip version. Finally, the 4th byte should be a number between 1 and 9 that indicates + // the block size used. + + BOOL isBzip2 = ((bzbuf[0] == 'B') && (bzbuf[1] == 'Z')) && + ((bzbuf[2] == 'h') || (bzbuf[2] == '0')) && + ((bzbuf[3] >= 0x31) && (bzbuf[3] <= 0x39)); + + if (isBzip2) { + compressionFormat = SPBzip2Compression; + wrappedFile->bzfile = BZ2_bzopen(path, "rb"); + } + } + // Default to plain + if(compressionFormat == SPNoCompression) { + wrappedFile->file = theFile; } else { - gzclose(gzfile); + fclose(theFile); } - - processingThread = nil; } // In write mode, set up a thread to handle writing in the background else if (fileMode == O_WRONLY) { - useCompression = NO; processingThread = [[NSThread alloc] initWithTarget:self selector:@selector(_writeBufferToData) object:nil]; [processingThread setName:@"SPFileHandle data writing thread"]; [processingThread start]; @@ -204,17 +207,15 @@ { long dataLength = 0; void *data = malloc(length); - - if (useCompression) { - if (compressionFormat == SPGzipCompression) { - dataLength = gzread(wrappedFile, data, (unsigned)length); - } - else if (compressionFormat == SPBzip2Compression) { - dataLength = BZ2_bzread(wrappedFile, data, (int)length); - } + + if (compressionFormat == SPGzipCompression) { + dataLength = gzread(wrappedFile->gzfile, data, (unsigned)length); + } + else if (compressionFormat == SPBzip2Compression) { + dataLength = BZ2_bzread(wrappedFile->bzfile, data, (int)length); } else { - dataLength = fread(data, 1, length, wrappedFile); + dataLength = fread(data, 1, length, wrappedFile->file); } return [NSMutableData dataWithBytesNoCopy:data length:dataLength freeWhenDone:YES]; @@ -235,13 +236,16 @@ */ - (NSUInteger)realDataReadLength { - if ((fileMode == O_WRONLY) || (compressionFormat == SPBzip2Compression)) return 0; + if (fileMode == O_WRONLY) return 0; - if (useCompression && (compressionFormat == SPGzipCompression)) { - return gzoffset(wrappedFile); + if (compressionFormat == SPGzipCompression) { + return gzoffset(wrappedFile->gzfile); + } + else if(compressionFormat == SPBzip2Compression) { + return 0; } else { - return ftell(wrappedFile); + return ftell(wrappedFile->file); } } @@ -253,40 +257,34 @@ * to NO on a fresh object. If this is called after data has been * written, an exception is thrown. */ -- (void)setShouldWriteWithCompressionFormat:(SPFileCompressionFormat)useCompressionFormat +- (void)setCompressionFormat:(SPFileCompressionFormat)useCompressionFormat { if (compressionFormat == useCompressionFormat) return; // Regardless of the supplied argument, close the current file according to how it was previously opened - if (useCompression) { - if (compressionFormat == SPGzipCompression) { - gzclose(wrappedFile); - } - else if (compressionFormat == SPBzip2Compression) { - BZ2_bzclose(wrappedFile); - } + if (compressionFormat == SPGzipCompression) { + gzclose(wrappedFile->gzfile); + } + else if (compressionFormat == SPBzip2Compression) { + BZ2_bzclose(wrappedFile->bzfile); } else { - fclose(wrappedFile); + fclose(wrappedFile->file); } if (dataWritten) [NSException raise:NSInternalInconsistencyException format:@"Cannot change compression settings when data has already been written."]; - useCompression = ((useCompressionFormat == SPGzipCompression) || (useCompressionFormat == SPBzip2Compression)); - compressionFormat = useCompressionFormat; - if (useCompression) { - if (compressionFormat == SPGzipCompression) { - wrappedFile = gzopen(wrappedFilePath, "wb"); - gzbuffer(wrappedFile, 131072); - } - else if (compressionFormat == SPBzip2Compression) { - wrappedFile = BZ2_bzopen(wrappedFilePath, "wb"); - } - } + if (compressionFormat == SPGzipCompression) { + wrappedFile->gzfile = gzopen(wrappedFilePath, "wb"); + gzbuffer(wrappedFile->gzfile, 131072); + } + else if (compressionFormat == SPBzip2Compression) { + wrappedFile->bzfile = BZ2_bzopen(wrappedFilePath, "wb"); + } else { - wrappedFile = fopen(wrappedFilePath, "wb"); + wrappedFile->file = fopen(wrappedFilePath, "wb"); } } @@ -345,16 +343,14 @@ if (!fileIsClosed) { [self synchronizeFile]; - if (useCompression) { - if (compressionFormat == SPGzipCompression) { - gzclose(wrappedFile); - } - else if (compressionFormat == SPBzip2Compression) { - BZ2_bzclose(wrappedFile); - } - } + if (compressionFormat == SPGzipCompression) { + gzclose(wrappedFile->gzfile); + } + else if (compressionFormat == SPBzip2Compression) { + BZ2_bzclose(wrappedFile->bzfile); + } else { - fclose(wrappedFile); + fclose(wrappedFile->file); } if (processingThread) { @@ -371,14 +367,6 @@ #pragma mark - #pragma mark File information -/** - * Returns whether compression is enabled on the file. - */ -- (BOOL)isCompressed -{ - return useCompression; -} - /** * Returns the compression format being used. Currently gzip or bzip2 only. */ @@ -418,22 +406,16 @@ // Write out the data long bufferLengthWrittenOut = 0; - - if (useCompression) { - switch (compressionFormat) - { - case SPGzipCompression: - bufferLengthWrittenOut = gzwrite(wrappedFile, [dataToBeWritten bytes], (unsigned)[dataToBeWritten length]); - break; - case SPBzip2Compression: - bufferLengthWrittenOut = BZ2_bzwrite(wrappedFile, (void *)[dataToBeWritten bytes], (int)[dataToBeWritten length]); - break; - default: - break; - } - } - else { - bufferLengthWrittenOut = fwrite([dataToBeWritten bytes], 1, [dataToBeWritten length], wrappedFile); + + 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 @@ -470,6 +452,7 @@ if (processingThread) SPClear(processingThread); + free(wrappedFile); free(wrappedFilePath); SPClear(buffer); -- cgit v1.2.3 From 71c013de73875aa69d727fe6a4dde8a23a381f23 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 8 Oct 2015 14:52:23 +0200 Subject: Move a mysql_affected_rows() to see how the 0x338 crash behaves (#2258) --- .../Source/SPMySQLConnection Categories/Querying & Preparation.m | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m index 1b7ec58e..2abb8a35 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m @@ -288,6 +288,7 @@ // Lock the connection while it's actively in use [self _lockConnection]; + unsigned long long theAffectedRowCount; while (queryAttemptsAllowed > 0) { // While recording the overall execution time (including network lag!), run @@ -296,6 +297,11 @@ queryStatus = mysql_real_query(mySQLConnection, queryBytes, queryBytesLength); queryExecutionTime = _elapsedSecondsSinceAbsoluteTime(queryStartTime); lastConnectionUsedTime = mach_absolute_time(); + + // "An integer greater than zero indicates the number of rows affected or retrieved. + // Zero indicates that no records were updated for an UPDATE statement, no rows matched the WHERE clause in the query or that no query has yet been executed. + // -1 indicates that the query returned an error or that, for a SELECT query, mysql_affected_rows() was called prior to calling mysql_store_result()." + theAffectedRowCount = mysql_affected_rows(mySQLConnection); // If the query succeeded, no need to re-attempt. if (!queryStatus) { @@ -327,11 +333,11 @@ return nil; } [self _lockConnection]; + NSAssert(mySQLConnection != NULL, @"mySQLConnection has disappeared while checking it!"); queryAttemptsAllowed--; } - unsigned long long theAffectedRowCount = mysql_affected_rows(mySQLConnection); id theResult = nil; // On success, if there is a query result, retrieve the result data type -- cgit v1.2.3 From 602c224a4084c5cb804e1f30c7b4fe5fe068d91b Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 8 Oct 2015 22:18:18 +0200 Subject: Fix uncompressed export causing a crash (issue introduced by me 2 commits ago in 1dedadcaba2cd474a251a24d893dc3b7b2a719a7) --- Source/SPFileHandle.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/SPFileHandle.m b/Source/SPFileHandle.m index 3e0489a4..49bcd4a4 100644 --- a/Source/SPFileHandle.m +++ b/Source/SPFileHandle.m @@ -145,6 +145,7 @@ union SPSomeFileHandle { } // In write mode, set up a thread to handle writing in the background else if (fileMode == O_WRONLY) { + wrappedFile->file = theFile; // can be changed later via setCompressionFormat: processingThread = [[NSThread alloc] initWithTarget:self selector:@selector(_writeBufferToData) object:nil]; [processingThread setName:@"SPFileHandle data writing thread"]; [processingThread start]; -- cgit v1.2.3 From 2cb875fb99e0c017b545b8e8427eb565e15cca1e Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 9 Oct 2015 00:18:09 +0200 Subject: Add timing info for _scrollViewDidChangeBounds: --- Source/SPObjectAdditions.m | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Source/SPObjectAdditions.m b/Source/SPObjectAdditions.m index f29aa3b4..5f7c73ab 100644 --- a/Source/SPObjectAdditions.m +++ b/Source/SPObjectAdditions.m @@ -50,7 +50,9 @@ static NSMutableDictionary *gScrollViewDealloc; { NSMutableString *msg = [NSMutableString string]; - [msg appendFormat:@"%s tripped!\n\n",__PRETTY_FUNCTION__]; + [msg appendFormat:@"%s tripped!\n",__PRETTY_FUNCTION__]; + + [msg appendFormat:@" at %@ (mach time: %lf)\n\n",[NSDate date],[NSDate monotonicTimeInterval]]; retryDescribe: [msg appendFormat:@"passed object (class <%@>): %@\n\n",[obj className],obj]; @@ -136,6 +138,7 @@ retryDescribe: if(notificationSelector == @selector(_scrollViewDidChangeBounds:) && [notificationName isEqualToString:NSViewBoundsDidChangeNotification]) { NSString *key = [NSString stringWithFormat:@"snd=%p,obs=%p",notificationSender,notificationObserver]; NSMutableString *val = [NSMutableString string]; + [val appendFormat:@"at %@ (mach time: %lf)\n",[NSDate date],[NSDate monotonicTimeInterval]]; [val appendFormat:@"observer: %1$p (class %2$@) description: %1$@\n",notificationObserver,[notificationObserver className]]; if([notificationObserver isKindOfClass:[NSView class]]) { [val appendFormat:@" view info: id=%@, tag=%ld\n",[(NSView *)notificationObserver identifier], [(NSView *)notificationObserver tag]]; @@ -176,7 +179,7 @@ retryDescribe: - (void)sp_dealloc { NSString *key = [NSString stringWithFormat:@"=%p",self]; - NSString *val = [NSString stringWithFormat:@"\ndealloc backtrace:\n%@\n\n",[NSThread callStackSymbols]]; + NSString *val = [NSString stringWithFormat:@"\n%p dealloc\n at %@ (mach time: %lf)\n backtrace:\n%@\n\n",self,[NSDate date],[NSDate monotonicTimeInterval],[NSThread callStackSymbols]]; [gScrollViewDealloc setObject:val forKey:key]; -- cgit v1.2.3 From 01ef3ad33d260119ee61b949414f038be2c8d00e Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 10 Oct 2015 03:49:58 +0200 Subject: Change two magic strings into constants --- Source/GeneratePreviewForURL.m | 6 +++--- Source/SPConstants.h | 3 +++ Source/SPConstants.m | 3 +++ Source/SPContentFilterManager.m | 4 ++-- Source/SPDatabaseDocument.m | 14 +++++++------- Source/SPQueryFavoriteManager.m | 4 ++-- 6 files changed, 20 insertions(+), 14 deletions(-) diff --git a/Source/GeneratePreviewForURL.m b/Source/GeneratePreviewForURL.m index ef20b852..2209f746 100644 --- a/Source/GeneratePreviewForURL.m +++ b/Source/GeneratePreviewForURL.m @@ -84,7 +84,7 @@ OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, } // Dispatch different spf formats - if([[spf objectForKey:@"format"] isEqualToString:@"connection"]) { + if([[spf objectForKey:SPFFormatKey] isEqualToString:@"connection"]) { template = [NSString stringWithContentsOfFile:[[NSBundle bundleWithIdentifier:@"com.sequelpro.SequelPro.qlgenerator"] pathForResource:@"SPQLPluginConnectionTemplate" ofType:@"html"] encoding:NSUTF8StringEncoding error:&templateReadError]; @@ -140,7 +140,7 @@ OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, [dateFormatter release]; } - else if([[spf objectForKey:@"format"] isEqualToString:@"content filters"]) { + else if([[spf objectForKey:SPFFormatKey] isEqualToString:@"content filters"]) { template = [NSString stringWithContentsOfFile:[[NSBundle bundleWithIdentifier:@"com.sequelpro.SequelPro.qlgenerator"] pathForResource:@"SPQLPluginContentFiltersTemplate" ofType:@"html"] encoding:NSUTF8StringEncoding error:&templateReadError]; @@ -156,7 +156,7 @@ OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, ]; } - else if([[spf objectForKey:@"format"] isEqualToString:@"query favorites"]) { + else if([[spf objectForKey:SPFFormatKey] isEqualToString:@"query favorites"]) { template = [NSString stringWithContentsOfFile:[[NSBundle bundleWithIdentifier:@"com.sequelpro.SequelPro.qlgenerator"] pathForResource:@"SPQLPluginQueryFavoritesTemplate" ofType:@"html"] encoding:NSUTF8StringEncoding error:&templateReadError]; diff --git a/Source/SPConstants.h b/Source/SPConstants.h index f2816745..80df6ba4 100644 --- a/Source/SPConstants.h +++ b/Source/SPConstants.h @@ -492,6 +492,9 @@ extern NSString *SPFavoriteSSLCACertFileLocationKey; extern NSString *SPFavoriteUseCompressionKey; extern NSString *SPConnectionFavoritesChangedNotification; +extern NSString *SPFFormatKey; +extern NSString *SPFVersionKey; + // Favorites import/export extern NSString *SPFavoritesDataRootKey; diff --git a/Source/SPConstants.m b/Source/SPConstants.m index 42631dab..ca8164e3 100644 --- a/Source/SPConstants.m +++ b/Source/SPConstants.m @@ -302,6 +302,9 @@ NSString *SPFavoriteSSLCACertFileLocationKey = @"sslCACertFileLocati NSString *SPFavoriteUseCompressionKey = @"useCompression"; NSString *SPConnectionFavoritesChangedNotification = @"SPConnectionFavoritesChanged"; +NSString *SPFFormatKey = @"format"; +NSString *SPFVersionKey = @"version"; + // Favorites import/export NSString *SPFavoritesDataRootKey = @"SPConnectionFavorites"; diff --git a/Source/SPContentFilterManager.m b/Source/SPContentFilterManager.m index a8e747e2..dca1fda8 100644 --- a/Source/SPContentFilterManager.m +++ b/Source/SPContentFilterManager.m @@ -930,8 +930,8 @@ static NSString *SPExportFilterAction = @"SPExportFilter"; NSMutableArray *filterData = [NSMutableArray array]; - [spfdata setObject:@1 forKey:@"version"]; - [spfdata setObject:@"content filters" forKey:@"format"]; + [spfdata setObject:@1 forKey:SPFVersionKey]; + [spfdata setObject:@"content filters" forKey:SPFFormatKey]; [spfdata setObject:@NO forKey:@"encrypted"]; NSIndexSet *indexes = [contentFilterTableView selectedRowIndexes]; diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index dd3baae3..57ecdd97 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -3143,8 +3143,8 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; [info setObject:[NSNumber numberWithBool:[[spfDocData_temp objectForKey:@"save_password"] boolValue]] forKey:@"save_password"]; [info setObject:[NSNumber numberWithBool:[[spfDocData_temp objectForKey:@"include_session"] boolValue]] forKey:@"include_session"]; [info setObject:[NSNumber numberWithBool:[[spfDocData_temp objectForKey:@"save_editor_content"] boolValue]] forKey:@"save_editor_content"]; - [info setObject:@1 forKey:@"version"]; - [info setObject:@"connection bundle" forKey:@"format"]; + [info setObject:@1 forKey:SPFVersionKey]; + [info setObject:@"connection bundle" forKey:SPFFormatKey]; // Loop through all windows for(NSWindow *window in [SPAppDelegate orderedDatabaseConnectionWindows]) { @@ -3303,7 +3303,7 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; } // For dispatching later - if(![[spf objectForKey:@"format"] isEqualToString:@"connection"]) { + if(![[spf objectForKey:SPFFormatKey] isEqualToString:@"connection"]) { NSLog(@"SPF file format is not 'connection'."); [spf release]; return NO; @@ -3352,8 +3352,8 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; NSMutableDictionary *spfData = [NSMutableDictionary dictionary]; // Add basic details - [spfStructure setObject:@1 forKey:@"version"]; - [spfStructure setObject:@"connection" forKey:@"format"]; + [spfStructure setObject:@1 forKey:SPFVersionKey]; + [spfStructure setObject:@"connection" forKey:SPFFormatKey]; [spfStructure setObject:@"mysql" forKey:@"rdbms_type"]; if([self mySQLVersion]) [spfStructure setObject:[self mySQLVersion] forKey:@"rdbms_version"]; @@ -4817,12 +4817,12 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; } // If the .spf format is unhandled, error. - if (![[spf objectForKey:@"format"] isEqualToString:@"connection"]) { + if (![[spf objectForKey:SPFFormatKey] isEqualToString:@"connection"]) { NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Warning", @"warning")] defaultButton:NSLocalizedString(@"OK", @"OK button") alternateButton:nil otherButton:nil - informativeTextWithFormat:NSLocalizedString(@"The chosen file “%@” contains ‘%@’ data.", @"message while reading a spf file which matches non-supported formats."), path, [spf objectForKey:@"format"]]; + informativeTextWithFormat:NSLocalizedString(@"The chosen file “%@” contains ‘%@’ data.", @"message while reading a spf file which matches non-supported formats."), path, [spf objectForKey:SPFFormatKey]]; [alert setAlertStyle:NSWarningAlertStyle]; [spf release]; diff --git a/Source/SPQueryFavoriteManager.m b/Source/SPQueryFavoriteManager.m index 3738fe0d..b1ea9a49 100644 --- a/Source/SPQueryFavoriteManager.m +++ b/Source/SPQueryFavoriteManager.m @@ -917,8 +917,8 @@ NSMutableArray *favoriteData = [NSMutableArray array]; - [spfdata setObject:@1 forKey:@"version"]; - [spfdata setObject:@"query favorites" forKey:@"format"]; + [spfdata setObject:@1 forKey:SPFVersionKey]; + [spfdata setObject:@"query favorites" forKey:SPFFormatKey]; [spfdata setObject:@NO forKey:@"encrypted"]; NSIndexSet *indexes = [favoritesTableView selectedRowIndexes]; -- cgit v1.2.3 From 44af78202545ec911de052c5eec361f2296afc28 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 10 Oct 2015 05:54:30 +0200 Subject: Rename a variable that was desperately trying to cause coding errors --- Interfaces/English.lproj/ExportDialog.xib | 8 ++++---- Source/SPExportController.h | 2 +- Source/SPExportFilenameUtilities.m | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Interfaces/English.lproj/ExportDialog.xib b/Interfaces/English.lproj/ExportDialog.xib index 3c3d6a2f..2f39243b 100644 --- a/Interfaces/English.lproj/ExportDialog.xib +++ b/Interfaces/English.lproj/ExportDialog.xib @@ -3309,7 +3309,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 - exportCustomFilenameTokensField + exportCustomFilenameTokenPool @@ -7351,7 +7351,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 NSComboBox NSTextField NSTokenField - NSTokenField + NSTokenField NSView NSButton NSButton @@ -7451,8 +7451,8 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 exportCustomFilenameTokenField NSTokenField - - exportCustomFilenameTokensField + + exportCustomFilenameTokenPool NSTokenField diff --git a/Source/SPExportController.h b/Source/SPExportController.h index 188642f4..bc706f46 100644 --- a/Source/SPExportController.h +++ b/Source/SPExportController.h @@ -92,7 +92,7 @@ IBOutlet NSButton *exportCustomFilenameViewLabelButton; IBOutlet NSView *exportCustomFilenameView; IBOutlet NSTokenField *exportCustomFilenameTokenField; - IBOutlet NSTokenField *exportCustomFilenameTokensField; + IBOutlet NSTokenField *exportCustomFilenameTokenPool; // SQL IBOutlet NSButton *exportSQLIncludeStructureCheck; diff --git a/Source/SPExportFilenameUtilities.m b/Source/SPExportFilenameUtilities.m index a65b8e53..d8f1198f 100644 --- a/Source/SPExportFilenameUtilities.m +++ b/Source/SPExportFilenameUtilities.m @@ -121,7 +121,7 @@ } } - [exportCustomFilenameTokensField setStringValue:[exportTokens componentsJoinedByString:@","]]; + [exportCustomFilenameTokenPool setStringValue:[exportTokens componentsJoinedByString:@","]]; } /** @@ -130,7 +130,7 @@ */ - (id)tokenObjectForString:(NSString *)stringToTokenize { - if ([[exportCustomFilenameTokensField objectValue] containsObject:stringToTokenize]) { + if ([[exportCustomFilenameTokenPool objectValue] containsObject:stringToTokenize]) { SPExportFileNameTokenObject *newToken = [[SPExportFileNameTokenObject alloc] init]; [newToken setTokenContent:stringToTokenize]; -- cgit v1.2.3 From 319eee397f894160aa5d6132d7d07881a75a762e Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 12 Oct 2015 01:09:23 +0200 Subject: Change how the custom filename pattern in export dialog is handled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Namely: * They were previously stored in the users locale. Now they are stored using a language independent id * Just typing a token in your language will no longer work. If you want to manually type a token use: {host}, {database}, and so on… (all in English) * Copy & Paste of tokens will use the new form, too (so a user running SP in English can simply share a custom pattern with a user running SP in German) * The localized token names can now contain spaces --- Interfaces/English.lproj/ExportDialog.xib | 8 + Source/SPConstants.h | 15 +- Source/SPConstants.m | 13 ++ Source/SPExportController.h | 3 + Source/SPExportController.m | 29 +++- Source/SPExportControllerDelegate.m | 253 +++++++++++++++++++++++------- Source/SPExportFileNameTokenObject.h | 6 +- Source/SPExportFileNameTokenObject.m | 26 ++- Source/SPExportFilenameUtilities.h | 4 +- Source/SPExportFilenameUtilities.m | 229 ++++++++------------------- 10 files changed, 351 insertions(+), 235 deletions(-) diff --git a/Interfaces/English.lproj/ExportDialog.xib b/Interfaces/English.lproj/ExportDialog.xib index 2f39243b..5cea2358 100644 --- a/Interfaces/English.lproj/ExportDialog.xib +++ b/Interfaces/English.lproj/ExportDialog.xib @@ -3635,6 +3635,14 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 1239 + + + delegate + + + + SPs-so-H4L + delegate diff --git a/Source/SPConstants.h b/Source/SPConstants.h index 80df6ba4..e53d09b4 100644 --- a/Source/SPConstants.h +++ b/Source/SPConstants.h @@ -259,6 +259,7 @@ extern NSString *SPFavoritesPasteboardDragType; extern NSString *SPContentFilterPasteboardDragType; extern NSString *SPNavigatorPasteboardDragType; extern NSString *SPNavigatorTableDataPasteboardDragType; +extern NSString *SPExportCustomFileNameTokenPlistType; // File extensions extern NSString *SPFileExtensionDefault; @@ -398,7 +399,19 @@ extern NSString *SPImportClipboardTempFileNamePrefix; extern NSString *SPSQLExportUseCompression; extern NSString *SPNoBOMforSQLdumpFile; extern NSString *SPExportLastDirectory; -extern NSString *SPExportFilenameFormat; +extern NSString *SPExportFilenameFormat; // legacy +extern NSString *SPExportFilenameFormatIntl; // new, user language independent version + +// Export filename tokens +extern NSString *SPFileNameDatabaseTokenName; +extern NSString *SPFileNameHostTokenName; +extern NSString *SPFileNameDateTokenName; +extern NSString *SPFileNameYearTokenName; +extern NSString *SPFileNameMonthTokenName; +extern NSString *SPFileNameDayTokenName; +extern NSString *SPFileNameTimeTokenName; +extern NSString *SPFileNameFavoriteTokenName; +extern NSString *SPFileNameTableTokenName; // Misc extern NSString *SPContentFilters; diff --git a/Source/SPConstants.m b/Source/SPConstants.m index ca8164e3..893c725f 100644 --- a/Source/SPConstants.m +++ b/Source/SPConstants.m @@ -47,6 +47,7 @@ NSString *SPFavoritesPasteboardDragType = @"SPFavoritesPasteboard"; NSString *SPContentFilterPasteboardDragType = @"SPContentFilterPasteboard"; NSString *SPNavigatorPasteboardDragType = @"SPNavigatorPasteboardDragType"; NSString *SPNavigatorTableDataPasteboardDragType = @"SPNavigatorTableDataPasteboardDragType"; +NSString *SPExportCustomFileNameTokenPlistType = @"SPExportCustomFileNameTokenPlist"; // File extensions NSString *SPFileExtensionDefault = @"spf"; @@ -199,6 +200,18 @@ NSString *SPSQLExportUseCompression = @"SQLExportUseCompression"; NSString *SPNoBOMforSQLdumpFile = @"NoBOMforSQLdumpFile"; NSString *SPExportLastDirectory = @"SPExportLastDirectory"; NSString *SPExportFilenameFormat = @"SPExportFilenameFormat"; +NSString *SPExportFilenameFormatIntl = @"CustomExportFilenameFormat"; + +// Export filename tokens +NSString *SPFileNameDatabaseTokenName = @"database"; +NSString *SPFileNameHostTokenName = @"host"; +NSString *SPFileNameDateTokenName = @"date"; +NSString *SPFileNameYearTokenName = @"year"; +NSString *SPFileNameMonthTokenName = @"month"; +NSString *SPFileNameDayTokenName = @"day"; +NSString *SPFileNameTimeTokenName = @"time"; +NSString *SPFileNameFavoriteTokenName = @"favorite"; +NSString *SPFileNameTableTokenName = @"table"; // Misc NSString *SPContentFilters = @"ContentFilters"; diff --git a/Source/SPExportController.h b/Source/SPExportController.h index bc706f46..d36c8f81 100644 --- a/Source/SPExportController.h +++ b/Source/SPExportController.h @@ -230,6 +230,9 @@ NSInteger heightOffset2; NSUInteger windowMinWidth; NSUInteger windowMinHeigth; + + NSDictionary *localizedTokenNames; + } /** diff --git a/Source/SPExportController.m b/Source/SPExportController.m index b249505a..bbbec9bd 100644 --- a/Source/SPExportController.m +++ b/Source/SPExportController.m @@ -124,6 +124,18 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; windowMinHeigth = [[self window] minSize].height; prefs = [NSUserDefaults standardUserDefaults]; + + localizedTokenNames = [@{ + SPFileNameHostTokenName: NSLocalizedString(@"Host", @"export filename host token"), + SPFileNameDatabaseTokenName: NSLocalizedString(@"Database", @"export filename database token"), + SPFileNameTableTokenName: NSLocalizedString(@"Table", @"table"), + SPFileNameDateTokenName: NSLocalizedString(@"Date", @"export filename date token"), + SPFileNameYearTokenName: NSLocalizedString(@"Year", @"export filename date token"), + SPFileNameMonthTokenName: NSLocalizedString(@"Month", @"export filename date token"), + SPFileNameDayTokenName: NSLocalizedString(@"Day", @"export filename date token"), + SPFileNameTimeTokenName: NSLocalizedString(@"Time", @"export filename time token"), + SPFileNameFavoriteTokenName: NSLocalizedString(@"Favorite", @"export filename favorite name token") + } retain]; } return self; @@ -680,7 +692,7 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; // Check whether to save the export filename. Save it if it's not blank and contains at least one // token - this suggests it's not a one-off filename if ([[exportCustomFilenameTokenField stringValue] length] < 1) { - [prefs removeObjectForKey:SPExportFilenameFormat]; + [prefs removeObjectForKey:SPExportFilenameFormatIntl]; } else { BOOL saveFilename = NO; @@ -692,7 +704,7 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; if ([aToken isKindOfClass:[SPExportFileNameTokenObject class]]) saveFilename = YES; } - if (saveFilename) [prefs setObject:[NSKeyedArchiver archivedDataWithRootObject:representedObjects] forKey:SPExportFilenameFormat]; + if (saveFilename) [prefs setObject:[NSKeyedArchiver archivedDataWithRootObject:representedObjects] forKey:SPExportFilenameFormatIntl]; } // If we are about to perform a table export, cache the current number of tables within the list, @@ -950,14 +962,15 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; */ - (void)_setPreviousExportFilenameAndPath { + id o; // Restore the export filename if it exists, and update the display - if ([prefs objectForKey:SPExportFilenameFormat]) { - [exportCustomFilenameTokenField setObjectValue:[NSKeyedUnarchiver unarchiveObjectWithData:[prefs objectForKey:SPExportFilenameFormat]]]; + if ((o = [prefs objectForKey:SPExportFilenameFormatIntl])) { + [exportCustomFilenameTokenField setObjectValue:[NSKeyedUnarchiver unarchiveObjectWithData:o]]; } // If a directory has previously been selected, reselect it - if ([prefs objectForKey:SPExportLastDirectory]) { - [exportPathField setStringValue:[prefs objectForKey:SPExportLastDirectory]]; + if ((o = [prefs objectForKey:SPExportLastDirectory])) { + [exportPathField setStringValue:o]; } else { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSAllDomainsMask, YES); @@ -1071,8 +1084,8 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; SPClear(exportFiles); SPClear(operationQueue); SPClear(exportFilename); - - if (previousConnectionEncoding) SPClear(previousConnectionEncoding); + SPClear(localizedTokenNames); + SPClear(previousConnectionEncoding); [super dealloc]; } diff --git a/Source/SPExportControllerDelegate.m b/Source/SPExportControllerDelegate.m index 43b1804f..748fec1d 100644 --- a/Source/SPExportControllerDelegate.m +++ b/Source/SPExportControllerDelegate.m @@ -32,6 +32,9 @@ #import "SPExportFilenameUtilities.h" #import "SPExportFileNameTokenObject.h" +#define IS_TOKEN(x) [x isKindOfClass:[SPExportFileNameTokenObject class]] +#define IS_STRING(x) [x isKindOfClass:[NSString class]] + // Defined to suppress warnings @interface SPExportController (SPExportControllerPrivateAPI) @@ -39,6 +42,8 @@ - (void)_toggleSQLExportTableNameTokenAvailability; - (void)_updateExportFormatInformation; - (void)_switchTab; +- (NSArray *)_updateTokensForMixedContent:(NSArray *)tokens; +- (void)_tokenizeCustomFilenameTokenField; @end @@ -97,78 +102,80 @@ */ - (NSTokenStyle)tokenField:(NSTokenField *)tokenField styleForRepresentedObject:(id)representedObject { - if ([representedObject isKindOfClass:[SPExportFileNameTokenObject class]]) return NSDefaultTokenStyle; + if (IS_TOKEN(representedObject)) return NSDefaultTokenStyle; return NSPlainTextTokenStyle; } -/** - * Take the default suggestion of new tokens - all untokenized text, as no tokenizing character is set - and - * split into many shorter tokens, using non-alphanumeric characters as (preserved) breaks. This preserves - * all supplied characters and allows tokens to be typed. - */ -- (NSArray *)tokenField:(NSTokenField *)tokenField shouldAddObjects:(NSArray *)tokens atIndex:(NSUInteger)index +- (BOOL)tokenField:(NSTokenField *)tokenField writeRepresentedObjects:(NSArray *)objects toPasteboard:(NSPasteboard *)pboard { - NSUInteger i, j; - NSInteger k; - NSMutableArray *processedTokens = [NSMutableArray array]; - NSCharacterSet *alphanumericSet = [NSCharacterSet alphanumericCharacterSet]; - id groupToken; - - for (NSString *inputToken in tokens) - { - j = 0; - - for (i = 0; i < [inputToken length]; i++) - { - if (![alphanumericSet characterIsMember:[inputToken characterAtIndex:i]]) { - if (i > j) { - [processedTokens addObject:[self tokenObjectForString:[inputToken substringWithRange:NSMakeRange(j, i - j)]]]; - } - - [processedTokens addObject:[inputToken substringWithRange:NSMakeRange(i, 1)]]; - - j = i + 1; - } + NSMutableArray *mixed = [NSMutableArray arrayWithCapacity:[objects count]]; + NSMutableString *flatted = [NSMutableString string]; + + for(id item in objects) { + if(IS_TOKEN(item)) { + [mixed addObject:@{@"tokenId": [item tokenId]}]; + [flatted appendFormat:@"{%@}",[item tokenId]]; } - - if (j < i) { - [processedTokens addObject:[self tokenObjectForString:[inputToken substringWithRange:NSMakeRange(j, i - j)]]]; + else if(IS_STRING(item)) { + [mixed addObject:item]; + [flatted appendString:item]; } - } - - // Check to see whether unprocessed strings can be combined to form tokens - for (i = 1; i < [processedTokens count]; i++) { - - // If this is a token object, skip - if ([[processedTokens objectAtIndex:i] isKindOfClass:[SPExportFileNameTokenObject class]]) { - continue; + else { + [NSException raise:NSInternalInconsistencyException format:@"tokenField %@ contains unexpected object %@",tokenField,item]; } + } + + [pboard setString:flatted forType:NSPasteboardTypeString]; + [pboard setPropertyList:mixed forType:SPExportCustomFileNameTokenPlistType]; + return YES; +} - for (k = i - 1; k >= 0; k--) { - - // If this is a token object, stop processing - if ([[processedTokens objectAtIndex:k] isKindOfClass:[SPExportFileNameTokenObject class]]) { - break; +- (NSArray *)tokenField:(NSTokenField *)tokenField readFromPasteboard:(NSPasteboard *)pboard +{ + NSArray *items = [pboard propertyListForType:SPExportCustomFileNameTokenPlistType]; + // if we have our preferred object type use it + if(items) { + NSMutableArray *res = [NSMutableArray arrayWithCapacity:[items count]]; + for (id item in items) { + if (IS_STRING(item)) { + [res addObject:item]; } - - // Check whether the group of items make up a token - groupToken = [self tokenObjectForString:[[processedTokens subarrayWithRange:NSMakeRange(k, 1 + i - k)] componentsJoinedByString:@""]]; - if ([groupToken isKindOfClass:[SPExportFileNameTokenObject class]]) { - [processedTokens replaceObjectsInRange:NSMakeRange(k, 1 + i - k) withObjectsFromArray:@[groupToken]]; - i = k + 1; - break; + else if([item isKindOfClass:[NSDictionary class]]) { + NSString *name = [item objectForKey:@"tokenId"]; + if(name) { + SPExportFileNameTokenObject *tok = [SPExportFileNameTokenObject tokenWithId:name]; + [res addObject:tok]; + } + } + else { + [NSException raise:NSInternalInconsistencyException format:@"pasteboard %@ contains unexpected object %@",pboard,item]; } } + return res; } + // if the string came from another app, paste it literal, tokenfield will take care of any conversions + NSString *raw = [pboard stringForType:NSPasteboardTypeString]; + if(raw) { + return @[raw]; + } + + return nil; +} - return processedTokens; +/** + * Take the default suggestion of new tokens - all untokenized text, as no tokenizing character is set - and + * split/recombine strings that contain tokens. This preserves all supplied characters and allows tokens to be typed. + */ +- (NSArray *)tokenField:(NSTokenField *)tokenField shouldAddObjects:(NSArray *)tokens atIndex:(NSUInteger)index +{ + return [self _updateTokensForMixedContent:tokens]; } - (NSString *)tokenField:(NSTokenField *)tokenField displayStringForRepresentedObject:(id)representedObject { - if ([representedObject isKindOfClass:[SPExportFileNameTokenObject class]]) { - return [(SPExportFileNameTokenObject *)representedObject tokenContent]; + if (IS_TOKEN(representedObject)) { + return [localizedTokenNames objectForKey:[(SPExportFileNameTokenObject *)representedObject tokenId]]; } return representedObject; @@ -188,10 +195,16 @@ */ - (void)controlTextDidChange:(NSNotification *)notification { + // this method can either be called by typing, or by copy&paste. + // In the latter case tokenization will already be done by now. if ([notification object] == exportCustomFilenameTokenField) { [self updateDisplayedExportFilename]; - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(tokenizeCustomFilenameTokenField) object:nil]; - [self performSelector:@selector(tokenizeCustomFilenameTokenField) withObject:nil afterDelay:0.5]; + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_tokenizeCustomFilenameTokenField) object:nil]; + // do not queue a call if the key causing this change was the return key. + // This is to prevent a loop with _tokenizeCustomFilenameTokenField. + if([[NSApp currentEvent] type] != NSKeyDown || [[NSApp currentEvent] keyCode] != 0x24) { + [self performSelector:@selector(_tokenizeCustomFilenameTokenField) withObject:nil afterDelay:0.5]; + } } } @@ -205,4 +218,132 @@ } } +#pragma mark - + +/** + * Takes a mixed array of strings and tokens and converts + * any valid tokens inside the strings into real tokens + */ +- (NSArray *)_updateTokensForMixedContent:(NSArray *)tokens +{ + //if two consecutive tokens are strings, merge them + NSMutableArray *mergedTokens = [NSMutableArray array]; + for (id inputToken in tokens) + { + if(IS_TOKEN(inputToken)) { + [mergedTokens addObject:inputToken]; + } + else if(IS_STRING(inputToken)) { + id prev = [mergedTokens lastObject]; + if(IS_STRING(prev)) { + [mergedTokens removeLastObject]; + [mergedTokens addObject:[prev stringByAppendingString:inputToken]]; + } + else { + [mergedTokens addObject:inputToken]; + } + } + } + + // create a mapping dict of tokenId => token + NSMutableDictionary *replacement = [NSMutableDictionary dictionary]; + for (SPExportFileNameTokenObject *realToken in [exportCustomFilenameTokenPool objectValue]) { + NSString *serializedName = [NSString stringWithFormat:@"{%@}",[realToken tokenId]]; + [replacement setObject:realToken forKey:serializedName]; + } + + //now we can look for real tokens to convert inside the strings + NSMutableArray *processedTokens = [NSMutableArray array]; + for (id token in mergedTokens) { + if(IS_TOKEN(token)) { + [processedTokens addObject:token]; + continue; + } + + NSString *remainder = token; + while(true) { + NSRange openCurl = [remainder rangeOfString:@"{"]; + if(openCurl.location == NSNotFound) { + break; + } + NSString *before = [remainder substringToIndex:openCurl.location]; + if([before length]) { + [processedTokens addObject:before]; + } + remainder = [remainder substringFromIndex:openCurl.location]; + NSRange closeCurl = [remainder rangeOfString:@"}"]; + if(closeCurl.location == NSNotFound) { + break; //we've hit an unterminated token + } + NSString *tokenString = [remainder substringToIndex:closeCurl.location+1]; + SPExportFileNameTokenObject *tokenObject = [replacement objectForKey:tokenString]; + if(tokenObject) { + [processedTokens addObject:tokenObject]; + } + else { + [processedTokens addObject:tokenString]; // no token with this name, add it as string + } + remainder = [remainder substringFromIndex:closeCurl.location+1]; + } + if([remainder length]) { + [processedTokens addObject:remainder]; + } + } + + return processedTokens; +} + +- (void)_tokenizeCustomFilenameTokenField +{ + // if we are currently inside or at the end of a string segment we can + // call for tokenization to happen by simulating a return press + + if ([exportCustomFilenameTokenField currentEditor] == nil) return; + + NSRange selectedRange = [[exportCustomFilenameTokenField currentEditor] selectedRange]; + + if (selectedRange.location == NSNotFound) return; + if (selectedRange.location == 0) return; // the beginning of the field is not valid for tokenization + if (selectedRange.length > 0) return; + + NSUInteger start = 0; + for(id obj in [exportCustomFilenameTokenField objectValue]) { + NSUInteger length; + BOOL isText = NO; + if(IS_STRING(obj)) { + length = [obj length]; + isText = YES; + } + else if(IS_TOKEN(obj)) { + length = 1; // tokens are seen as one char by the textview + } + else { + [NSException raise:NSInternalInconsistencyException format:@"Unknown object type in token field: %@",obj]; + } + NSUInteger end = start+length; + if(selectedRange.location >= start && selectedRange.location <= end) { + if(!isText) return; // cursor is at the end of a token + break; + } + start += length; + } + + // All conditions met - synthesize the return key to trigger tokenization. + NSEvent *tokenizingEvent = [NSEvent keyEventWithType:NSKeyDown + location:NSMakePoint(0,0) + modifierFlags:0 + timestamp:0 + windowNumber:[[exportCustomFilenameTokenField window] windowNumber] + context:[NSGraphicsContext currentContext] + characters:nil + charactersIgnoringModifiers:nil + isARepeat:NO + keyCode:0x24]; + + [NSApp postEvent:tokenizingEvent atStart:NO]; +} + @end + +#undef IS_TOKEN +#undef IS_STRING diff --git a/Source/SPExportFileNameTokenObject.h b/Source/SPExportFileNameTokenObject.h index 5a63b56b..8c9b8a80 100644 --- a/Source/SPExportFileNameTokenObject.h +++ b/Source/SPExportFileNameTokenObject.h @@ -30,9 +30,11 @@ @interface SPExportFileNameTokenObject : NSObject { - NSString *tokenContent; + NSString *tokenId; } -@property (retain) NSString *tokenContent; ++ (id)tokenWithId:(NSString *)token; + +@property (nonatomic,copy) NSString *tokenId; @end diff --git a/Source/SPExportFileNameTokenObject.m b/Source/SPExportFileNameTokenObject.m index 5e91049b..9c6338f3 100644 --- a/Source/SPExportFileNameTokenObject.m +++ b/Source/SPExportFileNameTokenObject.m @@ -32,7 +32,27 @@ @implementation SPExportFileNameTokenObject -@synthesize tokenContent; +@synthesize tokenId; + ++ (id)tokenWithId:(NSString *)token +{ + SPExportFileNameTokenObject *obj = [[SPExportFileNameTokenObject alloc] init]; + [obj setTokenId:token]; + return [obj autorelease]; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%p {%@}>",self,[self tokenId]]; +} + +- (BOOL)isEqual:(id)object +{ + if([object isKindOfClass:[SPExportFileNameTokenObject class]]) { + return [[self tokenId] isEqualToString:[object tokenId]]; + } + return [super isEqual:object]; +} #pragma mark - #pragma mark NSCoding compatibility @@ -40,7 +60,7 @@ - (id)initWithCoder:(NSCoder *)decoder { if ((self = [super init])) { - [self setTokenContent:[decoder decodeObjectForKey:@"TokenContent"]]; + [self setTokenId:[decoder decodeObjectForKey:@"tokenId"]]; } return self; @@ -48,7 +68,7 @@ - (void)encodeWithCoder:(NSCoder *)encoder { - [encoder encodeObject:[self tokenContent] forKey:@"TokenContent"]; + [encoder encodeObject:[self tokenId] forKey:@"tokenId"]; } @end diff --git a/Source/SPExportFilenameUtilities.h b/Source/SPExportFilenameUtilities.h index 7483316e..978dfc2c 100644 --- a/Source/SPExportFilenameUtilities.h +++ b/Source/SPExportFilenameUtilities.h @@ -41,10 +41,10 @@ - (void)updateDisplayedExportFilename; - (void)updateAvailableExportFilenameTokens; -- (id)tokenObjectForString:(NSString *)stringToTokenize; -- (void)tokenizeCustomFilenameTokenField; - (NSString *)generateDefaultExportFilename; - (NSString *)currentDefaultExportFileExtension; - (NSString *)expandCustomFilenameFormatUsingTableName:(NSString *)table; +- (NSString *)customFilenamePathExtension; +- (BOOL)isTableTokenAllowed; @end diff --git a/Source/SPExportFilenameUtilities.m b/Source/SPExportFilenameUtilities.m index d8f1198f..e71ede3b 100644 --- a/Source/SPExportFilenameUtilities.m +++ b/Source/SPExportFilenameUtilities.m @@ -50,7 +50,8 @@ //note that there will be no tableName if the export is done from a query result without a database selected (or empty). filename = [self expandCustomFilenameFormatUsingTableName:[[tablesListInstance tables] objectOrNilAtIndex:1]]; - if (![[filename pathExtension] length] && [extension length] > 0) filename = [filename stringByAppendingPathExtension:extension]; + + if (![[self customFilenamePathExtension] length] && [extension length] > 0) filename = [filename stringByAppendingPathExtension:extension]; } else { filename = [self generateDefaultExportFilename]; @@ -59,11 +60,29 @@ [exportCustomFilenameViewLabelButton setTitle:[NSString stringWithFormat:NSLocalizedString(@"Customize Filename (%@)", @"customize file name label"), filename]]; } -/** - * Updates the available export filename tokens based on the currently selected options. - */ -- (void)updateAvailableExportFilenameTokens -{ +- (NSString *)customFilenamePathExtension +{ + NSMutableString *flatted = [NSMutableString string]; + + // This time we replace every token with "/a". This has the following effect: + // "{host}.{database}" -> "/a./a" -> extension="" + // "{database}_{date}.sql" -> "/a_/a.sql" -> extension="sql" + // That seems to be the easiest way to let NSString correctly determine if an extension is present + for (id filenamePart in [exportCustomFilenameTokenField objectValue]) + { + if([filenamePart isKindOfClass:[NSString class]]) + [flatted appendString:filenamePart]; + else if([filenamePart isKindOfClass:[SPExportFileNameTokenObject class]]) + [flatted appendString:@"/a"]; + else + [NSException raise:NSInternalInconsistencyException format:@"unknown object in token list: %@",filenamePart]; + } + + return [flatted pathExtension]; +} + +- (BOOL)isTableTokenAllowed +{ NSUInteger i = 0; BOOL removeTable = NO; @@ -71,19 +90,7 @@ BOOL isCSV = exportType == SPCSVExport; BOOL isDot = exportType == SPDotExport; BOOL isXML = exportType == SPXMLExport; - - 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"), - NSLocalizedString(@"favorite", @"export filename favorite name token"), - nil]; - + // Determine whether to remove the table from the tokens list if (exportSource == SPQueryExport || isDot) { removeTable = YES; @@ -102,150 +109,46 @@ } } - if (removeTable) { - [exportTokens removeObject:NSLocalizedString(@"table", @"table")]; - NSArray *tokenParts = [exportCustomFilenameTokenField objectValue]; - - for (id token in [exportCustomFilenameTokenField objectValue]) - { - if ([token isKindOfClass:[SPExportFileNameTokenObject class]]) { - if ([[token tokenContent] isEqualToString:NSLocalizedString(@"table", @"table")]) { - NSMutableArray *newTokens = [NSMutableArray arrayWithArray:tokenParts]; - - [newTokens removeObjectAtIndex:[tokenParts indexOfObject:token]]; - - [exportCustomFilenameTokenField setObjectValue:newTokens]; - break; - } - } - } - } - - [exportCustomFilenameTokenPool setStringValue:[exportTokens componentsJoinedByString:@","]]; + return (removeTable == NO); } /** - * Take a supplied string and return the token for it - a SPExportFileNameTokenObject if the token - * has been recognized, or the supplied NSString if unmatched. - */ -- (id)tokenObjectForString:(NSString *)stringToTokenize -{ - if ([[exportCustomFilenameTokenPool objectValue] containsObject:stringToTokenize]) { - SPExportFileNameTokenObject *newToken = [[SPExportFileNameTokenObject alloc] init]; - - [newToken setTokenContent:stringToTokenize]; - - return [newToken autorelease]; - } - - return stringToTokenize; -} - -/** - * Tokenize the filename field. - * - * This is called on a delay after text entry to update the tokens during text entry. - * There's no API to perform tokenizing, but the same result can be achieved by using the return key; - * however, this only works if the cursor is after text, not after a token. + * Updates the available export filename tokens based on the currently selected options. */ -- (void)tokenizeCustomFilenameTokenField -{ - NSCharacterSet *alphanumericSet = [NSCharacterSet alphanumericCharacterSet]; - - if ([exportCustomFilenameTokenField currentEditor] == nil) return; - - NSRange selectedRange = [[exportCustomFilenameTokenField currentEditor] selectedRange]; +- (void)updateAvailableExportFilenameTokens +{ + SPExportFileNameTokenObject *tableObject; + NSMutableArray *exportTokens = [NSMutableArray arrayWithObjects: + [SPExportFileNameTokenObject tokenWithId:SPFileNameDatabaseTokenName], + [SPExportFileNameTokenObject tokenWithId:SPFileNameHostTokenName], + [SPExportFileNameTokenObject tokenWithId:SPFileNameDateTokenName], + [SPExportFileNameTokenObject tokenWithId:SPFileNameYearTokenName], + [SPExportFileNameTokenObject tokenWithId:SPFileNameMonthTokenName], + [SPExportFileNameTokenObject tokenWithId:SPFileNameDayTokenName], + [SPExportFileNameTokenObject tokenWithId:SPFileNameTimeTokenName], + [SPExportFileNameTokenObject tokenWithId:SPFileNameFavoriteTokenName], + (tableObject = [SPExportFileNameTokenObject tokenWithId:SPFileNameTableTokenName]), + nil + ]; - if (selectedRange.location == NSNotFound) return; - if (selectedRange.length > 0) return; - - // Retrieve the object value of the token field. This consists of plain text and recognised tokens interspersed. - NSArray *representedObjects = [exportCustomFilenameTokenField objectValue]; - - // Walk through the strings - not the tokens - and determine whether any need tokenizing, - // including scanning for groups of strings which make up a single token - BOOL tokenizingRequired = NO; - NSUInteger i, j; - NSInteger k; - id tokenCheck; - NSMutableArray *tokenParts = [NSMutableArray array]; - - // Add all tokens, words, and separators to the array to process - for (id eachObject in representedObjects) { - if ([eachObject isKindOfClass:[SPExportFileNameTokenObject class]]) { - [tokenParts addObject:eachObject]; - } else { - for (i = 0, j = 0; i < [(NSString *)eachObject length]; i++) { - if ([alphanumericSet characterIsMember:[eachObject characterAtIndex:i]]) { - continue; - } - if (i > j) { - [tokenParts addObject:[eachObject substringWithRange:NSMakeRange(j, i - j)]]; - } - [tokenParts addObject:[eachObject substringWithRange:NSMakeRange(i, 1)]]; - j = i + 1; - } - if (j < i) { - [tokenParts addObject:[eachObject substringWithRange:NSMakeRange(j, i - j)]]; - } - } - } - - // Walk through the array to process, scanning it for words or groups which are tokens - for (i = 0; i < [tokenParts count]; i++) { - for (k = i; k >= 0; k--) { - - // Don't process existing token objects - if ([[tokenParts objectAtIndex:k] isKindOfClass:[SPExportFileNameTokenObject class]]) { + if (![self isTableTokenAllowed]) { + [exportTokens removeObject:tableObject]; + NSArray *tokenParts = [exportCustomFilenameTokenField objectValue]; + + for (id token in tokenParts) + { + if([token isEqual:tableObject]) { + NSMutableArray *newTokens = [NSMutableArray arrayWithArray:tokenParts]; + + [newTokens removeObject:tableObject]; //removes all occurances + + [exportCustomFilenameTokenField setObjectValue:newTokens]; break; } - - // Check whether this item, or group of adjacent items, make up a token - tokenCheck = [self tokenObjectForString:[[tokenParts subarrayWithRange:NSMakeRange(k, 1 + i - k)] componentsJoinedByString:@""]]; - if ([tokenCheck isKindOfClass:[SPExportFileNameTokenObject class]]) { - tokenizingRequired = YES; - } } } - // If no tokenizing is required, don't process any further. - if (!tokenizingRequired) return; - - // Detect where the cursor is currently located. If it's at the end of a token, also return - - // or the enter key would result in closing the sheet. - NSUInteger stringPosition = 0; - - for (id representedObject in representedObjects) - { - if ([representedObject isKindOfClass:[SPExportFileNameTokenObject class]]) { - stringPosition++; - } - else { - stringPosition += [(NSString *)representedObject length]; - } - - if (selectedRange.location <= stringPosition) { - if ([representedObject isKindOfClass:[SPExportFileNameTokenObject class]]) return; - break; - } - } - - // All conditions met - synthesize the return key to trigger tokenization. - NSEvent *tokenizingEvent = [NSEvent keyEventWithType:NSKeyDown - location:NSMakePoint(0,0) - modifierFlags:0 - timestamp:0 - windowNumber:[[exportCustomFilenameTokenField window] windowNumber] - context:[NSGraphicsContext currentContext] - characters:nil - charactersIgnoringModifiers:nil - isARepeat:NO - keyCode:0x24]; - - [NSApp postEvent:tokenizingEvent atStart:NO]; - - // Update the filename preview - [self updateDisplayedExportFilename]; + [exportCustomFilenameTokenPool setObjectValue:exportTokens]; } /** @@ -343,43 +246,43 @@ for (id filenamePart in representedFilenameParts) { if ([filenamePart isKindOfClass:[SPExportFileNameTokenObject class]]) { - NSString *tokenContent = [filenamePart tokenContent]; + NSString *tokenContent = [filenamePart tokenId]; - if ([tokenContent isEqualToString:NSLocalizedString(@"host", @"export filename host token")]) { + if ([tokenContent isEqualToString:SPFileNameHostTokenName]) { [string appendStringOrNil:[tableDocumentInstance host]]; } - else if ([tokenContent isEqualToString:NSLocalizedString(@"database", @"export filename database token")]) { + else if ([tokenContent isEqualToString:SPFileNameDatabaseTokenName]) { [string appendStringOrNil:[tableDocumentInstance database]]; } - else if ([tokenContent isEqualToString:NSLocalizedString(@"table", @"table")]) { + else if ([tokenContent isEqualToString:SPFileNameTableTokenName]) { [string appendStringOrNil:table]; } - else if ([tokenContent isEqualToString:NSLocalizedString(@"date", @"export filename date token")]) { + else if ([tokenContent isEqualToString:SPFileNameDateTokenName]) { [dateFormatter setDateStyle:NSDateFormatterShortStyle]; [dateFormatter setTimeStyle:NSDateFormatterNoStyle]; [string appendString:[dateFormatter stringFromDate:[NSDate date]]]; } - else if ([tokenContent isEqualToString:NSLocalizedString(@"year", @"export filename date token")]) { + else if ([tokenContent isEqualToString:SPFileNameYearTokenName]) { [string appendString:[[NSDate date] descriptionWithCalendarFormat:@"%Y" timeZone:nil locale:nil]]; } - else if ([tokenContent isEqualToString:NSLocalizedString(@"month", @"export filename date token")]) { + else if ([tokenContent isEqualToString:SPFileNameMonthTokenName]) { [string appendString:[[NSDate date] descriptionWithCalendarFormat:@"%m" timeZone:nil locale:nil]]; } - else if ([tokenContent isEqualToString:NSLocalizedString(@"day", @"export filename date token")]) { + else if ([tokenContent isEqualToString:SPFileNameDayTokenName]) { [string appendString:[[NSDate date] descriptionWithCalendarFormat:@"%d" timeZone:nil locale:nil]]; } - else if ([tokenContent isEqualToString:NSLocalizedString(@"time", @"export filename time token")]) { + else if ([tokenContent isEqualToString:SPFileNameTimeTokenName]) { [dateFormatter setDateStyle:NSDateFormatterNoStyle]; [dateFormatter setTimeStyle:NSDateFormatterShortStyle]; [string appendString:[dateFormatter stringFromDate:[NSDate date]]]; } - else if ([tokenContent isEqualToString:NSLocalizedString(@"favorite", @"export filename favorite name token")]) { + else if ([tokenContent isEqualToString:SPFileNameFavoriteTokenName]) { [string appendStringOrNil:[tableDocumentInstance name]]; } } -- cgit v1.2.3 From 275afcdc8e93a1eb6e89825c28a74c69125917ce Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 12 Oct 2015 01:43:00 +0200 Subject: Add an option to save the current export dialog settings to a file --- Interfaces/English.lproj/ExportDialog.xib | 59 ++++- Source/SPExportSettingsPersistence.h | 44 ++++ Source/SPExportSettingsPersistence.m | 380 ++++++++++++++++++++++++++++++ sequel-pro.xcodeproj/project.pbxproj | 6 + 4 files changed, 487 insertions(+), 2 deletions(-) create mode 100644 Source/SPExportSettingsPersistence.h create mode 100644 Source/SPExportSettingsPersistence.m diff --git a/Interfaces/English.lproj/ExportDialog.xib b/Interfaces/English.lproj/ExportDialog.xib index 5cea2358..856dc6d9 100644 --- a/Interfaces/English.lproj/ExportDialog.xib +++ b/Interfaces/English.lproj/ExportDialog.xib @@ -1229,10 +1229,33 @@ NO + + + 265 + {{461, 344}, {43, 28}} + + _NS:9 + YES + + 67108864 + 134348800 + S… + + _NS:9 + + -2038284288 + 129 + + + 200 + 25 + + NO + 265 - {{408, 344}, {94, 28}} + {{369, 344}, {94, 28}} YES @@ -1275,7 +1298,7 @@ 266 - {{127, 349}, {278, 19}} + {{127, 349}, {239, 19}} YES @@ -3595,6 +3618,14 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 1273 + + + exportCurrentSettings: + + + + SsW-ax-uuL + delegate @@ -3988,6 +4019,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 + Exporter View @@ -5193,6 +5225,19 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 + + 77c-r9-26W + + + + + + + + DcJ-W4-asV + + + @@ -5540,6 +5585,16 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + + ToolTip + + ToolTip + + Save the current export settings to disk + + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin diff --git a/Source/SPExportSettingsPersistence.h b/Source/SPExportSettingsPersistence.h new file mode 100644 index 00000000..2f9104c4 --- /dev/null +++ b/Source/SPExportSettingsPersistence.h @@ -0,0 +1,44 @@ +// +// SPExportSettingsPersistence.h +// sequel-pro +// +// Created by Max Lohrmann on 09.10.15. +// Copyright (c) 2015 Max Lohrmann. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at + +#import +#import "SPExportController.h" + +@interface SPExportController (SPExportSettingsPersistence) + +- (IBAction)exportCurrentSettings:(id)sender; +- (NSDictionary *)currentSettingsAsDictionary; + +/** + * @return A serialized form of the "custom filename" field + */ +- (NSDictionary *)currentCustomFilenameAsArray; + +@end diff --git a/Source/SPExportSettingsPersistence.m b/Source/SPExportSettingsPersistence.m new file mode 100644 index 00000000..68963856 --- /dev/null +++ b/Source/SPExportSettingsPersistence.m @@ -0,0 +1,380 @@ +// +// SPExportSettingsPersistence.m +// sequel-pro +// +// Created by Max Lohrmann on 09.10.15. +// Copyright (c) 2015 Max Lohrmann. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at + +#import "SPExportSettingsPersistence.h" +#import "SPExportFileNameTokenObject.h" + +/** + * converts a ([obj state] == NSOnState) to @YES / @NO + * (because doing @([obj state] == NSOnState) will result in an integer 0/1) + */ +static inline NSNumber *IsOn(id obj); + +@interface SPExportController (SPExportSettingsPersistence_Private) + ++ (NSString *)describeExportSource:(SPExportSource)es; ++ (NSString *)describeExportType:(SPExportType)et; ++ (NSString *)describeCompressionFormat:(SPFileCompressionFormat)cf; ++ (NSString *)describeXMLExportFormat:(SPXMLExportFormat)xf; ++ (NSString *)describeSQLExportInsertDivider:(SPSQLExportInsertDivider)eid; + +- (NSDictionary *)exporterSettings; +- (NSDictionary *)csvSettings; +- (NSDictionary *)dotSettings; +- (NSDictionary *)xmlSettings; +- (NSDictionary *)sqlSettings; + +- (id)exporterSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type; +- (id)dotSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type; +- (id)xmlSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type; +- (id)csvSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type; +- (id)sqlSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type; + +@end + +#pragma mark - + +@implementation SPExportController (SPExportSettingsPersistence) + +#define NAMEOF(x) case x: return @#x + ++ (NSString *)describeExportSource:(SPExportSource)es +{ + switch (es) { + NAMEOF(SPFilteredExport); + NAMEOF(SPQueryExport); + NAMEOF(SPTableExport); + } + return nil; +} + ++ (NSString *)describeExportType:(SPExportType)et +{ + switch (et) { + NAMEOF(SPSQLExport); + NAMEOF(SPCSVExport); + NAMEOF(SPXMLExport); + NAMEOF(SPDotExport); + NAMEOF(SPPDFExport); + NAMEOF(SPHTMLExport); + NAMEOF(SPExcelExport); + } + return nil; +} + ++ (NSString *)describeCompressionFormat:(SPFileCompressionFormat)cf +{ + switch (cf) { + NAMEOF(SPNoCompression); + NAMEOF(SPGzipCompression); + NAMEOF(SPBzip2Compression); + } + return nil; +} + ++ (NSString *)describeXMLExportFormat:(SPXMLExportFormat)xf +{ + switch (xf) { + NAMEOF(SPXMLExportMySQLFormat); + NAMEOF(SPXMLExportPlainFormat); + } + return nil; +} + ++ (NSString *)describeSQLExportInsertDivider:(SPSQLExportInsertDivider)eid +{ + switch (eid) { + NAMEOF(SPSQLInsertEveryNDataBytes); + NAMEOF(SPSQLInsertEveryNRows); + } + return nil; +} + +#undef NAMEOF + +- (IBAction)exportCurrentSettings:(id)sender +{ + //show save file dialog + NSSavePanel *panel = [NSSavePanel savePanel]; + + [panel setAllowedFileTypes:@[SPFileExtensionDefault]]; + + [panel setExtensionHidden:NO]; + [panel setAllowsOtherFileTypes:NO]; + [panel setCanSelectHiddenExtension:YES]; + [panel setCanCreateDirectories:YES]; + + [panel beginSheetModalForWindow:[self window] completionHandler:^(NSInteger returnCode) { + if(returnCode != NSFileHandlingPanelOKButton) return; + + NSError *err = nil; + NSData *plist = [NSPropertyListSerialization dataWithPropertyList:[self currentSettingsAsDictionary] + format:NSPropertyListXMLFormat_v1_0 + options:0 + error:&err]; + if(!err) { + [plist writeToURL:[panel URL] options:NSAtomicWrite error:&err]; + if(!err) return; + } + + NSAlert *alert = [NSAlert alertWithError:err]; + [alert setAlertStyle:NSCriticalAlertStyle]; + [alert runModal]; + }]; +} + +- (NSArray *)currentCustomFilenameAsArray +{ + NSArray *tokenListIn = [exportCustomFilenameTokenField objectValue]; + NSMutableArray *tokenListOut = [NSMutableArray arrayWithCapacity:[tokenListIn count]]; + + for (id obj in tokenListIn) { + if([obj isKindOfClass:[NSString class]]) { + [tokenListOut addObject:obj]; + } + else if([obj isKindOfClass:[SPExportFileNameTokenObject class]]) { + NSDictionary *tokenProperties = @{@"tokenId": [obj tokenId]}; + // in the future the dict can be used to store per-token settings + [tokenListOut addObject:tokenProperties]; + } + else { + SPLog(@"unknown object in token list: %@",obj); + } + } + + return tokenListOut; +} + +- (NSDictionary *)currentSettingsAsDictionary +{ + NSMutableDictionary *root = [NSMutableDictionary dictionary]; + + [root setObject:@"export settings" forKey:SPFFormatKey]; + [root setObject:@1 forKey:SPFVersionKey]; + + [root setObject:[exportPathField stringValue] forKey:@"exportPath"]; + + [root setObject:[[self class] describeExportSource:exportSource] forKey:@"exportSource"]; + [root setObject:[[self class] describeExportType:exportType] forKey:@"exportType"]; + + if([[exportCustomFilenameTokenField stringValue] length] > 0) { + [root setObject:[self currentCustomFilenameAsArray] forKey:@"customFilename"]; + } + + [root setObject:[self exporterSettings] forKey:@"settings"]; + + if(exportSource == SPTableExport) { + NSMutableDictionary *perObjectSettings = [NSMutableDictionary dictionaryWithCapacity:[tables count]]; + + for (NSMutableArray *table in tables) { + NSString *key = [table objectAtIndex:0]; + id settings = [self exporterSpecificSettingsForSchemaObject:key ofType:SPTableTypeTable]; + if(settings) + [perObjectSettings setObject:settings forKey:key]; + } + + [root setObject:perObjectSettings forKey:@"schemaObjects"]; + } + + [root setObject:IsOn(exportProcessLowMemoryButton) forKey:@"lowMemoryStreaming"]; + [root setObject:[[self class] describeCompressionFormat:(SPFileCompressionFormat)[exportOutputCompressionFormatPopupButton indexOfSelectedItem]] forKey:@"compressionFormat"]; + + return root; +} + +- (NSDictionary *)exporterSettings +{ + switch (exportType) { + case SPCSVExport: + return [self csvSettings]; + case SPSQLExport: + return [self sqlSettings]; + case SPXMLExport: + return [self xmlSettings]; + case SPDotExport: + return [self dotSettings]; + case SPExcelExport: + case SPHTMLExport: + case SPPDFExport: + default: + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:@"exportType not implemented!" + userInfo:@{@"exportType": @(exportType)}]; + } +} + +- (NSDictionary *)csvSettings +{ + return @{ + @"exportToMultipleFiles": IsOn(exportFilePerTableCheck), + @"CSVIncludeFieldNames": IsOn(exportCSVIncludeFieldNamesCheck), + @"CSVFieldsTerminated": [exportCSVFieldsTerminatedField stringValue], + @"CSVFieldsWrapped": [exportCSVFieldsWrappedField stringValue], + @"CSVLinesTerminated": [exportCSVLinesTerminatedField stringValue], + @"CSVFieldsEscaped": [exportCSVFieldsEscapedField stringValue], + @"CSVNULLValuesAsText": [exportCSVNULLValuesAsTextField stringValue] + }; +} + +- (NSDictionary *)dotSettings +{ + return @{@"DotForceLowerTableNames": IsOn(exportDotForceLowerTableNamesCheck)}; +} + +- (NSDictionary *)xmlSettings +{ + return @{ + @"exportToMultipleFiles": IsOn(exportFilePerTableCheck), + @"XMLFormat": [[self class] describeXMLExportFormat:(SPXMLExportFormat)[exportXMLFormatPopUpButton indexOfSelectedItem]], + @"XMLOutputIncludeStructure": IsOn(exportXMLIncludeStructure), + @"XMLOutputIncludeContent": IsOn(exportXMLIncludeContent), + @"XMLNULLString": [exportXMLNULLValuesAsTextField stringValue] + }; +} + +- (NSDictionary *)sqlSettings +{ + BOOL includeStructure = ([exportSQLIncludeStructureCheck state] == NSOnState); + + NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:@{ + @"SQLIncludeStructure": IsOn(exportSQLIncludeStructureCheck), + @"SQLIncludeContent": IsOn(exportSQLIncludeContentCheck), + @"SQLIncludeErrors": IsOn(exportSQLIncludeErrorsCheck), + @"SQLUseUTF8BOM": IsOn(exportUseUTF8BOMButton), + @"SQLBLOBFieldsAsHex": IsOn(exportSQLBLOBFieldsAsHexCheck), + @"SQLInsertNValue": @([exportSQLInsertNValueTextField integerValue]), + @"SQLInsertDivider": [[self class] describeSQLExportInsertDivider:(SPSQLExportInsertDivider)[exportSQLInsertDividerPopUpButton indexOfSelectedItem]] + }]; + + if(includeStructure) { + [dict addEntriesFromDictionary:@{ + @"SQLIncludeAutoIncrementValue": IsOn(exportSQLIncludeAutoIncrementValueButton), + @"SQLIncludeDropSyntax": IsOn(exportSQLIncludeDropSyntaxCheck) + }]; + } + + return dict; +} + +- (id)exporterSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type +{ + switch (exportType) { + case SPCSVExport: + return [self csvSpecificSettingsForSchemaObject:name ofType:type]; + case SPSQLExport: + return [self sqlSpecificSettingsForSchemaObject:name ofType:type]; + case SPXMLExport: + return [self xmlSpecificSettingsForSchemaObject:name ofType:type]; + case SPDotExport: + return [self dotSpecificSettingsForSchemaObject:name ofType:type]; + case SPExcelExport: + case SPHTMLExport: + case SPPDFExport: + default: + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:@"exportType not implemented!" + userInfo:@{@"exportType": @(exportType)}]; + } +} + +- (id)dotSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type +{ + // Dot is a graph of the whole database - nothing to pick from + return nil; +} + +- (id)xmlSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type +{ + // XML per table setting is only yes/no + if(type == SPTableTypeTable) { + // we have to look through the table views' rows to find the current checkbox value... + for (NSArray *table in tables) { + if([[table objectAtIndex:0] isEqualTo:name]) { + return @([[table objectAtIndex:2] boolValue]); + } + } + } + return nil; +} + +- (id)csvSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type +{ + // CSV per table setting is only yes/no + if(type == SPTableTypeTable) { + // we have to look through the table views rows to find the current checkbox value... + for (NSArray *table in tables) { + if([[table objectAtIndex:0] isEqualTo:name]) { + return @([[table objectAtIndex:2] boolValue]); + } + } + } + return nil; +} + +- (id)sqlSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type +{ + BOOL structure = ([exportSQLIncludeStructureCheck state] == NSOnState); + BOOL content = ([exportSQLIncludeContentCheck state] == NSOnState); + BOOL drop = ([exportSQLIncludeDropSyntaxCheck state] == NSOnState); + + // SQL allows per table setting of structure/content/drop table + if(type == SPTableTypeTable) { + // we have to look through the table views rows to find the current checkbox value... + for (NSArray *table in tables) { + if([[table objectAtIndex:0] isEqualTo:name]) { + NSMutableArray *flags = [NSMutableArray arrayWithCapacity:3]; + + if (structure && [[table objectAtIndex:1] boolValue]) { + [flags addObject:@"structure"]; + } + + if (content && [[table objectAtIndex:2] boolValue]) { + [flags addObject:@"content"]; + } + + if (drop && [[table objectAtIndex:3] boolValue]) { + [flags addObject:@"drop"]; + } + + return flags; + } + } + } + return nil; +} + +@end + +#pragma mark - + +NSNumber *IsOn(id obj) +{ + return (([obj state] == NSOnState)? @YES : @NO); +} diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index 02a15375..9a78e182 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -192,6 +192,7 @@ 507FF1121BBCC57600104523 /* SPFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF1111BBCC57600104523 /* SPFunctions.m */; }; 507FF1621BBF0D5000104523 /* SPTableCopyTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 112730551180788A000737FD /* SPTableCopyTest.m */; }; 507FF2421BC33BBC00104523 /* SPOSInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 50EAB5B71A8FBB08008F627A /* SPOSInfo.m */; }; + 507FF26A1BC8450100104523 /* SPExportSettingsPersistence.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF2691BC8450100104523 /* SPExportSettingsPersistence.m */; }; 50A9F8B119EAD4B90053E571 /* SPGotoDatabaseController.m in Sources */ = {isa = PBXBuildFile; fileRef = 50A9F8B019EAD4B90053E571 /* SPGotoDatabaseController.m */; }; 50D3C3491A75B8A800B5429C /* GotoDatabaseDialog.xib in Resources */ = {isa = PBXBuildFile; fileRef = 50D3C34B1A75B8A800B5429C /* GotoDatabaseDialog.xib */; }; 50D3C3521A77135F00B5429C /* SPParserUtils.c in Sources */ = {isa = PBXBuildFile; fileRef = 50D3C3501A77135F00B5429C /* SPParserUtils.c */; }; @@ -906,6 +907,8 @@ 506CE9301A311C6C0039F736 /* SPTableContentFilterController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableContentFilterController.m; sourceTree = ""; }; 507FF1101BBCC4C400104523 /* SPFunctions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPFunctions.h; sourceTree = ""; }; 507FF1111BBCC57600104523 /* SPFunctions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPFunctions.m; sourceTree = ""; }; + 507FF2681BC8450100104523 /* SPExportSettingsPersistence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPExportSettingsPersistence.h; sourceTree = ""; }; + 507FF2691BC8450100104523 /* SPExportSettingsPersistence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExportSettingsPersistence.m; sourceTree = ""; }; 50A9F8AF19EAD4B90053E571 /* SPGotoDatabaseController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPGotoDatabaseController.h; sourceTree = ""; }; 50A9F8B019EAD4B90053E571 /* SPGotoDatabaseController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPGotoDatabaseController.m; sourceTree = ""; }; 50D3C34A1A75B8A800B5429C /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/GotoDatabaseDialog.xib; sourceTree = ""; }; @@ -2268,6 +2271,8 @@ 173C837C11AAD2C500B8B084 /* Delegate Protocols */, 173C837D11AAD2D300B8B084 /* Delegate Categories */, 50D3C3831A8177D900B5429C /* SPExportController+SharedPrivateAPI.h */, + 507FF2681BC8450100104523 /* SPExportSettingsPersistence.h */, + 507FF2691BC8450100104523 /* SPExportSettingsPersistence.m */, ); name = "Data Export"; sourceTree = ""; @@ -3223,6 +3228,7 @@ BC29C37F10501EFD00DD6C6E /* SPQueryController.m in Sources */, 5822D3091061833C00CE2157 /* SPCSVParser.m in Sources */, BC675A141072039C00C5ACD4 /* SPContentFilterManager.m in Sources */, + 507FF26A1BC8450100104523 /* SPExportSettingsPersistence.m in Sources */, 17292443107AC41000B21980 /* SPXMLExporter.m in Sources */, 582A01E9107C0C170027D42B /* SPNotLoaded.m in Sources */, 173284EA1088FEDE0062E892 /* SPConstants.m in Sources */, -- cgit v1.2.3 From d1323f0606377919d8246a451d8b07335c2a0583 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 12 Oct 2015 02:00:46 +0200 Subject: Change width of export dialog to fit tokenlist --- Interfaces/English.lproj/ExportDialog.xib | 74 +++++++++++++++---------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/Interfaces/English.lproj/ExportDialog.xib b/Interfaces/English.lproj/ExportDialog.xib index 856dc6d9..e7148f29 100644 --- a/Interfaces/English.lproj/ExportDialog.xib +++ b/Interfaces/English.lproj/ExportDialog.xib @@ -61,13 +61,13 @@ 4111 2 - {{610, 273}, {517, 498}} + {{610, 273}, {750, 498}} 611845120 Export Window NSWindow - {517, 498} + {730, 498} 256 @@ -75,7 +75,7 @@ 274 - {{-8, 69}, {533, 423}} + {{-8, 69}, {766, 423}} @@ -84,7 +84,7 @@ 274 - {{10, 33}, {513, 377}} + {{10, 33}, {746, 377}} @@ -149,7 +149,7 @@ 289 - {{397, 12}, {104, 32}} + {{630, 12}, {104, 32}} 1 @@ -172,7 +172,7 @@ 289 - {{293, 12}, {104, 32}} + {{526, 12}, {104, 32}} YES @@ -339,12 +339,12 @@ NO - {{1, 1}, {478, 65}} + {{1, 1}, {711, 65}} - {{-3, -4}, {480, 67}} + {{-3, -4}, {713, 67}} {0, 0} @@ -398,7 +398,7 @@ NO - {{21, -16}, {474, 61}} + {{21, -16}, {707, 61}} NSView @@ -456,7 +456,7 @@ 290 - {{17, 22}, {274, 14}} + {{17, 22}, {507, 14}} YES @@ -476,7 +476,7 @@ 1 - {517, 498} + {750, 498} @@ -858,7 +858,7 @@ NeXT Encapsulated PostScript v1.2 pasteboard type NeXT TIFF v4.0 pasteboard type - {200, 22} + {413, 22} YES @@ -904,7 +904,7 @@ 289 - {{168, -2}, {32, 25}} + {{381, -2}, {32, 25}} 1 YES @@ -930,7 +930,7 @@ 289 - {{137, -2}, {32, 25}} + {{350, -2}, {32, 25}} YES @@ -953,11 +953,11 @@ NO - {{1, 1}, {199, 21}} + {{1, 1}, {412, 21}} - {{21, 20}, {201, 23}} + {{21, 20}, {414, 23}} {0, 0} @@ -1065,7 +1065,7 @@ 266 - {{29, 59}, {482, 19}} + {{29, 59}, {699, 19}} YES @@ -1089,7 +1089,7 @@ 266 - {{29, 86}, {485, 14}} + {{29, 86}, {702, 14}} YES @@ -1115,7 +1115,7 @@ 258 - {{21, 4}, {450, 22}} + {{21, 4}, {667, 22}} YES @@ -1131,11 +1131,11 @@ 1 - {{1, 1}, {486, 38}} + {{1, 1}, {703, 38}} - {{26, 10}, {488, 40}} + {{26, 10}, {705, 40}} {0, 0} @@ -1156,11 +1156,11 @@ NO - {{1, 1}, {529, 112}} + {{1, 1}, {746, 112}} - {{-11, -4}, {531, 114}} + {{-11, -4}, {748, 114}} {0, 0} @@ -1181,14 +1181,14 @@ NO - {{0, 207}, {517, 108}} + {{0, 207}, {730, 108}} NSView 266 - {{32, 316}, {467, 28}} + {{32, 316}, {680, 28}} YES @@ -1232,7 +1232,7 @@ 265 - {{461, 344}, {43, 28}} + {{674, 344}, {43, 28}} _NS:9 YES @@ -1255,7 +1255,7 @@ 265 - {{369, 344}, {94, 28}} + {{582, 344}, {94, 28}} YES @@ -1276,7 +1276,7 @@ 10 - {{20, 312}, {477, 5}} + {{20, 312}, {690, 5}} {0, 0} @@ -1298,7 +1298,7 @@ 266 - {{127, 349}, {239, 19}} + {{127, 349}, {452, 19}} YES @@ -1358,7 +1358,7 @@ 17 - {{225, 16}, {276, 268}} + {{438, 16}, {276, 268}} @@ -2806,7 +2806,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 4352 - {199, 221} + {412, 221} YES NO @@ -2814,7 +2814,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 256 - {199, 17} + {412, 17} @@ -2827,7 +2827,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 tables - 142 + 355 10 1000 @@ -2982,7 +2982,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 1 - {{1, 17}, {199, 221}} + {{1, 17}, {412, 221}} @@ -3017,7 +3017,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 - {{1, 0}, {199, 17}} + {{1, 0}, {412, 17}} @@ -3026,7 +3026,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 - {{21, 42}, {201, 239}} + {{21, 42}, {414, 239}} 133650 @@ -3040,7 +3040,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 1 - {517, 378} + {730, 378} NSView -- cgit v1.2.3 From 8c7fc9deaa3d005e0bf2afd6db5d5fa9bb63cf62 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 12 Oct 2015 23:14:34 +0200 Subject: Fix an issue where copying the contents of a TEXT/BLOB field from a custom query result would not contain all data if keyboard navigation was used (#2283) --- Source/SPCustomQuery.m | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/SPCustomQuery.m b/Source/SPCustomQuery.m index c344f078..c3d76d54 100644 --- a/Source/SPCustomQuery.m +++ b/Source/SPCustomQuery.m @@ -2111,7 +2111,10 @@ - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex { if (aTableView == customQueryView) { - return [self _resultDataItemAtRow:rowIndex columnIndex:[[tableColumn identifier] integerValue] preserveNULLs:NO asPreview:YES]; + NSUInteger columnIndex = [[tableColumn identifier] integerValue]; + // if a user enters the field by keyboard navigation they might want to copy the contents without invoking the field editor sheet first + BOOL forEditing = ([customQueryView editedColumn] == (NSInteger)columnIndex && [customQueryView editedRow] == rowIndex); + return [self _resultDataItemAtRow:rowIndex columnIndex:[[tableColumn identifier] integerValue] preserveNULLs:NO asPreview:(forEditing != YES)]; } return @""; @@ -3996,6 +3999,7 @@ */ - (id)_resultDataItemAtRow:(NSInteger)row columnIndex:(NSUInteger)column preserveNULLs:(BOOL)preserveNULLs asPreview:(BOOL)asPreview; { +#warning duplicate code with SPTableContentDataSource.m tableView:objectValueForTableColumn:… id value = nil; // While the table is being loaded, additional validation is required - data @@ -4006,11 +4010,7 @@ pthread_mutex_lock(&resultDataLock); if (row < resultDataCount && column < [resultData columnCount]) { - if (asPreview) { - value = SPDataStoragePreviewAtRowAndColumn(resultData, row, column, 150); - } else { - value = SPDataStorageObjectAtRowAndColumn(resultData, row, column); - } + value = SPDataStoragePreviewAtRowAndColumn(resultData, row, column, 150); } pthread_mutex_unlock(&resultDataLock); -- cgit v1.2.3 From 63b23073b99097f03a57eb8f0e11a906cc4313d6 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 13 Oct 2015 14:55:04 +0200 Subject: Use a better RNG for IV when encrypting session files --- Source/SPDataAdditions.m | 10 ++++++---- Source/SPFunctions.h | 7 +++++++ Source/SPFunctions.m | 30 ++++++++++++++++++++++++++++++ sequel-pro.xcodeproj/project.pbxproj | 4 ++++ 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/Source/SPDataAdditions.m b/Source/SPDataAdditions.m index 8002595c..8b2207c5 100644 --- a/Source/SPDataAdditions.m +++ b/Source/SPDataAdditions.m @@ -37,6 +37,7 @@ #include #include #include +#import "SPFunctions.h" uint32_t LimitUInt32(NSUInteger i); @@ -74,11 +75,12 @@ uint32_t LimitUInt32(NSUInteger i); { // Create a random 128-bit initialization vector // IV is block "-1" of plaintext data, therefore it is blockSize long - srand((unsigned int)time(NULL)); - NSInteger ivIndex; unsigned char iv[kCCBlockSizeAES128]; - for (ivIndex = 0; ivIndex < kCCBlockSizeAES128; ivIndex++) - iv[ivIndex] = rand() & 0xff; + if(SPBetterRandomBytes(iv,sizeof(iv)) != 0) + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:@"Getting random data bytes failed!" + userInfo:@{@"errno":@(errno)}]; + NSData *ivData = [NSData dataWithBytes:iv length:sizeof(iv)]; // Create the key from first 128-bits of the 160-bit password hash diff --git a/Source/SPFunctions.h b/Source/SPFunctions.h index b68964ca..e462b8cb 100644 --- a/Source/SPFunctions.h +++ b/Source/SPFunctions.h @@ -35,3 +35,10 @@ */ void SPMainQSync(void (^block)(void)); +/** + * Copies count bytes into buf provided by caller + * @param buf Base address to copy to + * @param count Number of bytes to copy + * @return 0 on success or -1 if something went wrong, check errno + */ +int SPBetterRandomBytes(uint8_t *buf, size_t count); diff --git a/Source/SPFunctions.m b/Source/SPFunctions.m index f485d36a..851c2422 100644 --- a/Source/SPFunctions.m +++ b/Source/SPFunctions.m @@ -29,6 +29,8 @@ // More info at #import "SPFunctions.h" +#import +#import "SPOSInfo.h" void SPMainQSync(void (^block)(void)) { @@ -39,3 +41,31 @@ void SPMainQSync(void (^block)(void)) dispatch_sync(dispatch_get_main_queue(), block); } } + +int SPBetterRandomBytes(uint8_t *buf, size_t count) +{ +#if MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_10_7 + if([SPOSInfo isOSVersionAtLeastMajor:10 minor:7 patch:0]) { + return SecRandomCopyBytes(kSecRandomDefault, count, buf); + } +#endif + // Version for 10.6 + // https://developer.apple.com/library/prerelease/mac/documentation/Security/Conceptual/cryptoservices/RandomNumberGenerationAPIs/RandomNumberGenerationAPIs.html#//apple_ref/doc/uid/TP40011172-CH12-SW1 + FILE *fp = fopen("/dev/random", "r"); + + if (!fp) return -1; + + size_t i; + for (i=0; i Date: Tue, 13 Oct 2015 14:57:00 +0200 Subject: Fix another case of "background thread updating UI" --- Source/SPDatabaseDocument.m | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index 57ecdd97..66a56ea7 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -110,6 +110,7 @@ enum { #import "SPCharsetCollationHelper.h" #import "SPGotoDatabaseController.h" +#import "SPFunctions.h" #import @@ -4818,7 +4819,7 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; // If the .spf format is unhandled, error. if (![[spf objectForKey:SPFFormatKey] isEqualToString:@"connection"]) { - NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Warning", @"warning")] + NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Unknown file format", @"warning")] defaultButton:NSLocalizedString(@"OK", @"OK button") alternateButton:nil otherButton:nil @@ -5038,21 +5039,24 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; } - // Select view - if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_STRUCTURE"]) - [self viewStructure:self]; - else if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_CONTENT"]) - [self viewContent:self]; - else if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_CUSTOMQUERY"]) - [self viewQuery:self]; - else if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_STATUS"]) - [self viewStatus:self]; - else if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_RELATIONS"]) - [self viewRelations:self]; - else if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_TRIGGERS"]) - [self viewTriggers:self]; - - [self updateWindowTitle:self]; + // update UI on main thread + SPMainQSync(^{ + // Select view + if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_STRUCTURE"]) + [self viewStructure:self]; + else if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_CONTENT"]) + [self viewContent:self]; + else if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_CUSTOMQUERY"]) + [self viewQuery:self]; + else if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_STATUS"]) + [self viewStatus:self]; + else if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_RELATIONS"]) + [self viewRelations:self]; + else if([[spfSession objectForKey:@"view"] isEqualToString:@"SP_VIEW_TRIGGERS"]) + [self viewTriggers:self]; + + [self updateWindowTitle:self]; + }); // dealloc spfSession data SPClear(spfSession); -- cgit v1.2.3 From ec60a32215bb07f5ccd9b8061a88112985acaf8b Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 14 Oct 2015 16:06:51 +0200 Subject: Add mysql:// URL handler (fixes #1681) --- Resources/Plists/Info.plist | 8 ++++++++ Source/SPAppController.m | 44 ++++++++++++++++++++++++++++++++++++++++- Source/SPConnectionController.h | 1 + Source/SPConnectionController.m | 7 ++++++- Source/SPDatabaseDocument.h | 1 + Source/SPDatabaseDocument.m | 32 +++++++++++++++++++----------- 6 files changed, 80 insertions(+), 13 deletions(-) diff --git a/Resources/Plists/Info.plist b/Resources/Plists/Info.plist index 2ba52aa3..a0b1401b 100644 --- a/Resources/Plists/Info.plist +++ b/Resources/Plists/Info.plist @@ -173,6 +173,14 @@ sequelpro + + CFBundleURLName + MySQL URL scheme + CFBundleURLSchemes + + mysql + + CFBundleVersion diff --git a/Source/SPAppController.m b/Source/SPAppController.m index 9ff2cad0..d733b48a 100644 --- a/Source/SPAppController.m +++ b/Source/SPAppController.m @@ -765,15 +765,57 @@ { NSURL *url = [NSURL URLWithString:[[event paramDescriptorForKeyword:keyDirectObject] stringValue]]; - if (url) { + if ([[url scheme] isEqualToString:@"sequelpro"]) { [self handleEventWithURL:url]; } + else if([[url scheme] isEqualToString:@"mysql"]) { + [self handleMySQLConnectWithURL:url]; + } else { NSBeep(); NSLog(@"Error in sequelpro URL scheme"); } } +- (void)handleMySQLConnectWithURL:(NSURL *)url +{ + if(![[url scheme] isEqualToString:@"mysql"]) { + SPLog(@"unsupported url scheme: %@",url); + return; + } + + // make connection window + [self newTab:nil]; + SPDatabaseDocument *doc = [self frontDocument]; + + NSMutableDictionary *details = [NSMutableDictionary dictionary]; + + NSValue *connect = @NO; + + [details setObject:@"SPTCPIPConnection" forKey:@"type"]; + if([url port]) + [details setObject:[url port] forKey:@"port"]; + + if([url user]) + [details setObject:[url user] forKey:@"user"]; + + if([url password]) { + [details setObject:[url password] forKey:@"password"]; + connect = @YES; + } + + if([[url host] length] && ![[url host] isEqualToString:@"localhost"]) + [details setObject:[url host] forKey:@"host"]; + else + [details setObject:@"127.0.0.1" forKey:@"host"]; + + NSArray *pc = [url pathComponents]; + if([pc count] > 1) // first object is "/" + [details setObject:[pc objectAtIndex:1] forKey:@"database"]; + + [doc setState:@{@"connection":details,@"auto_connect": connect} fromFile:NO]; +} + - (void)handleEventWithURL:(NSURL*)url { NSString *command = [url host]; diff --git a/Source/SPConnectionController.h b/Source/SPConnectionController.h index 99488600..f154ef77 100644 --- a/Source/SPConnectionController.h +++ b/Source/SPConnectionController.h @@ -255,6 +255,7 @@ - (IBAction)duplicateFavorite:(id)sender; - (IBAction)renameNode:(id)sender; - (IBAction)makeSelectedFavoriteDefault:(id)sender; +- (void)selectQuickConnectItem; // Import/export favorites - (IBAction)importFavorites:(id)sender; diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 0340fd8e..5168d2c6 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -1013,6 +1013,11 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, [prefs setInteger:favoriteID forKey:SPDefaultFavorite]; } + +- (void)selectQuickConnectItem +{ + return [self _selectNode:quickConnectItem]; +} #pragma mark - #pragma mark Import/export favorites @@ -1704,7 +1709,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, [self _reloadFavoritesViewData]; // Select Quick Connect item to prevent empty selection - [self _selectNode:quickConnectItem]; + [self selectQuickConnectItem]; [connectionResizeContainer setHidden:NO]; [connectionInstructionsTextField setStringValue:NSLocalizedString(@"Enter connection details below, or choose a favorite", @"enter connection details label")]; diff --git a/Source/SPDatabaseDocument.h b/Source/SPDatabaseDocument.h index a29f91cc..7419b408 100644 --- a/Source/SPDatabaseDocument.h +++ b/Source/SPDatabaseDocument.h @@ -515,6 +515,7 @@ // State saving and setting - (NSDictionary *) stateIncludingDetails:(NSDictionary *)detailsToReturn; - (BOOL)setState:(NSDictionary *)stateDetails; +- (BOOL)setState:(NSDictionary *)stateDetails fromFile:(BOOL)spfBased; - (BOOL)setStateFromConnectionFile:(NSString *)path; - (void)restoreSession; #endif diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index 66a56ea7..23d4a529 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -4633,12 +4633,17 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; return stateDetails; } +- (BOOL)setState:(NSDictionary *)stateDetails +{ + return [self setState:stateDetails fromFile:YES]; +} + /** * Set the state of the document to the supplied dictionary, which should * at least contain a "connection" dictionary of details. * Returns whether the state was set successfully. */ -- (BOOL)setState:(NSDictionary *)stateDetails +- (BOOL)setState:(NSDictionary *)stateDetails fromFile:(BOOL)spfBased { NSDictionary *connection = nil; NSInteger connectionType = -1; @@ -4655,15 +4660,20 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; [self updateWindowTitle:self]; - // Deselect all favorites on the connection controller, - // and clear and reset the connection state. - [[connectionController favoritesOutlineView] deselectAll:connectionController]; - [connectionController updateFavoriteSelection:self]; - - // Suppress the possibility to choose an other connection from the favorites - // if a connection should initialized by SPF file. Otherwise it could happen - // that the SPF file runs out of sync. - [[connectionController favoritesOutlineView] setEnabled:NO]; + if(spfBased) { + // Deselect all favorites on the connection controller, + // and clear and reset the connection state. + [[connectionController favoritesOutlineView] deselectAll:connectionController]; + [connectionController updateFavoriteSelection:self]; + + // Suppress the possibility to choose an other connection from the favorites + // if a connection should initialized by SPF file. Otherwise it could happen + // that the SPF file runs out of sync. + [[connectionController favoritesOutlineView] setEnabled:NO]; + } + else { + [connectionController selectQuickConnectItem]; + } // Set the correct connection type if ([connection objectForKey:@"type"]) { @@ -4774,7 +4784,7 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; // Autoconnect if appropriate if ([stateDetails objectForKey:@"auto_connect"] && [[stateDetails valueForKey:@"auto_connect"] boolValue]) { - [connectionController initiateConnection:self]; + [self connect]; } if (keychain) [keychain release]; -- cgit v1.2.3 From 71eaebae6b68c585809582823c9b3f7b8eae53c8 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 14 Oct 2015 18:00:22 +0200 Subject: Constrain the Table Info Comment/Create split view so that neither side can be completely hidden (fixes #2285) --- Interfaces/English.lproj/DBView.xib | 20 +++++++++++++++++--- Source/SPDatabaseDocument.h | 1 + Source/SPDatabaseDocument.m | 4 ++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Interfaces/English.lproj/DBView.xib b/Interfaces/English.lproj/DBView.xib index 4ed8a6cf..d4c1e723 100644 --- a/Interfaces/English.lproj/DBView.xib +++ b/Interfaces/English.lproj/DBView.xib @@ -13313,6 +13313,14 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA LR5-fT-2qb + + + tableInfoSplitView + + + + n3i-dz-XZx + addDatabase: @@ -25280,6 +25288,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA com.apple.InterfaceBuilder.CocoaPlugin + SPSplitView com.apple.InterfaceBuilder.CocoaPlugin @@ -28435,6 +28444,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA SPTableData id NSScrollView + SPSplitView NSTableView id SPTableStructure @@ -28674,6 +28684,10 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA tableInfoScrollView NSScrollView + + tableInfoSplitView + SPSplitView + tableInfoTable NSTableView @@ -29165,7 +29179,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA NSComboBox NSTextField NSTokenField - NSTokenField + NSTokenField NSView NSButton NSButton @@ -29265,8 +29279,8 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA exportCustomFilenameTokenField NSTokenField - - exportCustomFilenameTokensField + + exportCustomFilenameTokenPool NSTokenField diff --git a/Source/SPDatabaseDocument.h b/Source/SPDatabaseDocument.h index 7419b408..b094a65f 100644 --- a/Source/SPDatabaseDocument.h +++ b/Source/SPDatabaseDocument.h @@ -155,6 +155,7 @@ IBOutlet NSTableView *tableInfoTable; IBOutlet SPSplitView *contentViewSplitter; + IBOutlet SPSplitView *tableInfoSplitView; IBOutlet NSPopUpButton *encodingPopUp; #endif diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index 23d4a529..464325d4 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -302,6 +302,10 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; // Set collapsible behaviour on the table list so collapsing behaviour handles resize issus [contentViewSplitter setCollapsibleSubviewIndex:0]; + + // Set a minimum size on both text views on the table info page + [tableInfoSplitView setMinSize:20 ofSubviewAtIndex:0]; + [tableInfoSplitView setMinSize:20 ofSubviewAtIndex:1]; // Set up the connection controller connectionController = [[SPConnectionController alloc] initWithDocument:self]; -- cgit v1.2.3 From 8f5f361da4cc30a72641c8ae72db5cce03ce234f Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 14 Oct 2015 20:22:43 +0200 Subject: Change double click handling in the favorite list. So it only acts on double clicks that are actually made on an item. --- Source/SPConnectionController.h | 2 +- Source/SPConnectionController.m | 5 +++-- Source/SPFavoritesOutlineView.h | 9 +++++++++ Source/SPFavoritesOutlineView.m | 32 ++++++++++++++++++++++++++++---- 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/Source/SPConnectionController.h b/Source/SPConnectionController.h index f154ef77..ffface82 100644 --- a/Source/SPConnectionController.h +++ b/Source/SPConnectionController.h @@ -228,7 +228,7 @@ #ifndef SP_CODA // Interface interaction -- (IBAction)nodeDoubleClicked:(id)sender; +- (void)nodeDoubleClicked:(id)sender; - (IBAction)chooseKeyLocation:(id)sender; - (IBAction)showHelp:(id)sender; - (IBAction)updateSSLInterface:(id)sender; diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 5168d2c6..dd677ee4 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -53,6 +53,7 @@ #import "SPFavoriteColorSupport.h" #import "SPNamedNode.h" #import "SPWindowController.h" +#import "SPFavoritesOutlineView.h" #import @@ -343,10 +344,10 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, /** * Registered to be the double click action of the favorites outline view. */ -- (IBAction)nodeDoubleClicked:(id)sender +- (void)nodeDoubleClicked:(id)sender { #ifndef SP_CODA - SPTreeNode *node = [self selectedFavoriteNode]; + SPTreeNode *node = [favoritesOutlineView itemForDoubleAction]; if (node) { if (node == quickConnectItem) { diff --git a/Source/SPFavoritesOutlineView.h b/Source/SPFavoritesOutlineView.h index 2c3d475c..78222f38 100644 --- a/Source/SPFavoritesOutlineView.h +++ b/Source/SPFavoritesOutlineView.h @@ -34,8 +34,17 @@ { BOOL isOSVersionAtLeast10_7_0; BOOL justGainedFocus; + + id _itemForDoubleAction; } @property (assign) BOOL justGainedFocus; +/** + * The item on which the last doubleAction (double click or enter/return key) was performed. + * May be nil. + * This is only valid during the call to the doubleAction target! + */ +@property (nonatomic,readonly,assign) id itemForDoubleAction; + @end diff --git a/Source/SPFavoritesOutlineView.m b/Source/SPFavoritesOutlineView.m index 2e111925..8dbec5da 100644 --- a/Source/SPFavoritesOutlineView.m +++ b/Source/SPFavoritesOutlineView.m @@ -31,11 +31,18 @@ #import "SPFavoritesOutlineView.h" #import "SPConnectionControllerDelegate.h" +@interface SPFavoritesOutlineView () + +@property (nonatomic,readwrite,assign) id itemForDoubleAction; //make setter private + +@end + static NSUInteger SPFavoritesOutlineViewUnindent = 6; @implementation SPFavoritesOutlineView @synthesize justGainedFocus; +@synthesize itemForDoubleAction = _itemForDoubleAction; - (void)awakeFromNib { @@ -83,12 +90,13 @@ static NSUInteger SPFavoritesOutlineViewUnindent = 6; // Enter or Return initiates a connection to the selected favorite, which is the same as double-clicking // one, so call the same selector. if (([self numberOfSelectedRows] == 1) && (([event keyCode] == 36) || ([event keyCode] == 76))) { - [[self delegate] performSelector:[self doubleAction]]; - + [self setItemForDoubleAction:[self itemAtRow:[self selectedRow]]]; + [NSApp sendAction:[self doubleAction] to:[self delegate] from:self]; + [self setItemForDoubleAction:nil]; return; - + } // If the Tab key is used, change focus rather than entering edit mode. - } else if ([[event characters] length] && [[event characters] characterAtIndex:0] == NSTabCharacter) { + if ([[event characters] length] && [[event characters] characterAtIndex:0] == NSTabCharacter) { if (([event modifierFlags] & NSShiftKeyMask) != NSShiftKeyMask) { [[self window] selectKeyViewFollowingView:self]; } @@ -102,6 +110,22 @@ static NSUInteger SPFavoritesOutlineViewUnindent = 6; [super keyDown:event]; } +- (void)mouseDown:(NSEvent *)theEvent +{ + if([theEvent type] == NSLeftMouseDown && [theEvent clickCount] == 2) { + // The tricky thing is that [self clickedRow] is set from [NSTableView mouseDown], so right now it's not populated. + // We can't use [self selectedRow] either, as clicking on empty space does not update the selection. + NSPoint clickAt = [theEvent locationInWindow]; + NSPoint relClickAt = [self convertPoint:clickAt fromView:nil]; + NSInteger rowNum = [self rowAtPoint:relClickAt]; + if(rowNum > -1) [self setItemForDoubleAction:[self itemAtRow:rowNum]]; + } + + [super mouseDown:theEvent]; + + [self setItemForDoubleAction:nil]; // not much overhead, therefore unconditional +} + /** * To prevent right-clicking in a column's 'group' heading, ask the delegate if we support selecting it * as this normally doesn't apply to left-clicks. If we do support selecting this row, simply pass on the event. -- cgit v1.2.3 From 264babe2f0d1b5c7713c1a9df0417e4ec5b84bd2 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 14 Oct 2015 21:58:51 +0200 Subject: Add code to verify that a SSL key file actually contains a usable RSA key --- Source/SPConnectionController.m | 48 ++++++++++++++++++- Source/SPDataAdditions.h | 9 ++++ Source/SPDataAdditions.m | 53 +++++++++++++++++++++ UnitTests/SPDataAdditionsTests.m | 92 ++++++++++++++++++++++++++++++++++++ sequel-pro.xcodeproj/project.pbxproj | 4 ++ 5 files changed, 205 insertions(+), 1 deletion(-) diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index dd677ee4..1df7f9b9 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -431,7 +431,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, keySelectionPanel = [[NSOpenPanel openPanel] retain]; // retain/release needed on OS X ≤ 10.6 according to Apple doc [keySelectionPanel setShowsHiddenFiles:[prefs boolForKey:SPHiddenKeyFileVisibilityKey]]; [keySelectionPanel setAccessoryView:accessoryView]; - + [keySelectionPanel setDelegate:self]; [keySelectionPanel beginSheetModalForWindow:[dbDocument parentWindow] completionHandler:^(NSInteger returnCode) { NSString *abbreviatedFileName = [[[keySelectionPanel URL] path] stringByAbbreviatingWithTildeInPath]; @@ -486,6 +486,52 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, #endif } +- (BOOL)panel:(id)sender validateURL:(NSURL *)url error:(NSError **)outError +{ + if([keySelectionPanel accessoryView] == sslKeyFileLocationHelp) { + // mysql limits yaSSL to PEM format files and + // yaSSL only supports RSA type keys, with the exact string below on a single line + NSError *err = nil; + NSData *file = [NSData dataWithContentsOfURL:url options:NSDataReadingMappedIfSafe error:&err]; + if(err) { + *outError = err; + return NO; + } + __block BOOL rsaStart = NO; + __block BOOL rsaEnd = NO; + [file enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { + if(!rsaStart) { + const char rsaHead[] = "-----BEGIN RSA PRIVATE KEY-----"; + size_t rsaLen = strlen(rsaHead); + if(line.length != rsaLen) return; + if(memcmp(rsaHead, ([file bytes]+line.location), rsaLen) == 0) { + rsaStart = YES; + } + } + else { + const char rsaFoot[] = "-----END RSA PRIVATE KEY-----"; + size_t rsaLen = strlen(rsaFoot); + if(line.length != rsaLen) return; + if(memcmp(rsaFoot, ([file bytes]+line.location), rsaLen) == 0) { + rsaEnd = YES; + *stop = YES; + } + } + }]; + + if(rsaStart && rsaEnd) return YES; + + *outError = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:@{ + NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedString(@"“%@” is not a valid private key file.", @""),[url lastPathComponent]], + NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Make sure the file contains an RSA private key and is using PEM encoding.", @""), + NSURLErrorKey: url + }]; + return NO; + } + //unknown, accept by default + return YES; +} + /** * Show connection help webpage. */ diff --git a/Source/SPDataAdditions.h b/Source/SPDataAdditions.h index 5158c270..cd8374f6 100644 --- a/Source/SPDataAdditions.h +++ b/Source/SPDataAdditions.h @@ -28,6 +28,13 @@ // // More info at +typedef NS_OPTIONS(NSUInteger, SPLineTerminator) { + SPLineTerminatorAny = 0, + SPLineTerminatorCR = 1, + SPLineTerminatorLF = 2, + SPLineTerminatorCRLF = 4, +}; + @interface NSData (SPDataAdditions) - (NSData *)sha1Hash; @@ -46,4 +53,6 @@ - (NSString *)stringRepresentationUsingEncoding:(NSStringEncoding)encoding; - (NSString *)shortStringRepresentationUsingEncoding:(NSStringEncoding)encoding; +- (void)enumerateLinesBreakingAt:(SPLineTerminator)lbChars withBlock:(void (^)(NSRange line,BOOL *stop))block; + @end diff --git a/Source/SPDataAdditions.m b/Source/SPDataAdditions.m index 8b2207c5..65605577 100644 --- a/Source/SPDataAdditions.m +++ b/Source/SPDataAdditions.m @@ -39,6 +39,11 @@ #include #import "SPFunctions.h" +/** Limit an NSUInteger to unsigned 32 bit max. + * @return Whatever is smaller: UINT32_MAX or i + * + * This is pretty much a NOOP on 32 bit platforms. + */ uint32_t LimitUInt32(NSUInteger i); #pragma mark - @@ -433,6 +438,54 @@ uint32_t LimitUInt32(NSUInteger i); return string; } +- (void)enumerateLinesBreakingAt:(SPLineTerminator)lbChars withBlock:(void (^)(NSRange line,BOOL *stop))block +{ + if(lbChars == SPLineTerminatorAny) lbChars = SPLineTerminatorCR|SPLineTerminatorLF|SPLineTerminatorCRLF; + + const uint8_t *bytes = [self bytes]; + NSUInteger length = [self length]; + + NSUInteger curStart = 0; + SPLineTerminator terminatorFound = 0; + NSUInteger i; + for (i = 0; i < length; i++) { + uint8_t chr = bytes[i]; + // if looking for cr and/or crlf we look for cr otherwise for lf + if(((lbChars & SPLineTerminatorCRLF) || (lbChars & SPLineTerminatorCR)) && chr == '\r') { + //if we are looking for CRLF check for the following LF + if((lbChars & SPLineTerminatorCRLF) && ((i+1) < length) && bytes[i+1] == '\n') { + terminatorFound = SPLineTerminatorCRLF; + } + //if we were looking for CR we've found one + else if((lbChars & SPLineTerminatorCR)) { + terminatorFound = SPLineTerminatorCR; + } + } + else if((lbChars & SPLineTerminatorLF) && chr == '\n') { + terminatorFound = SPLineTerminatorLF; + } + // no linebreak yet ? + if(!terminatorFound) continue; + + // found one. call the block. + BOOL stop = NO; + NSRange lineRange = NSMakeRange(curStart, (i-curStart)); + block(lineRange,&stop); + if(stop) return; + + // reset vars for next line + if(terminatorFound == SPLineTerminatorCRLF) i++; //skip the \n in CRLF + curStart = (i+1); + terminatorFound = 0; + } + // there could we one unterminated line left in buffer + if(curStart < i) { + NSRange lineRange = NSMakeRange(curStart, (i-curStart)); + BOOL iDontCare = NO; + block(lineRange,&iDontCare); + } +} + @end #pragma mark - diff --git a/UnitTests/SPDataAdditionsTests.m b/UnitTests/SPDataAdditionsTests.m index 812eb45d..c7a03f7f 100644 --- a/UnitTests/SPDataAdditionsTests.m +++ b/UnitTests/SPDataAdditionsTests.m @@ -40,6 +40,7 @@ - (void)testDataEncryptedWithKeyIV; - (void)testDataDecryptedWithPassword; - (void)testDataDecryptedWithKey; +- (void)testEnumerateLinesBreakingAt_withBlock; @end @@ -281,4 +282,95 @@ } } +- (void)testEnumerateLinesBreakingAt_withBlock +{ + //simple empty data + { + __block NSUInteger invocations = 0; + NSData *data = [NSData data]; + [data enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { + invocations++; + }]; + STAssertTrue(invocations==0, @"Empty data never invokes block"); + } + //simple unix file + { + const char inp[] = "Two\nLines\n"; + __block NSUInteger invocations = 0; + NSData *data = [NSData dataWithBytes:inp length:strlen(inp)]; + [data enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { + switch (invocations) { + case 0: + STAssertEquals(line, NSMakeRange(0, 3), @"range of first line"); + break; + case 1: + STAssertEquals(line, NSMakeRange(4, 5), @"range of second line"); + break; + } + invocations++; + }]; + STAssertTrue(invocations==2, @"File with two lines, terminated with empty line"); + } + //simple windows file without ending empty line + { + const char inp[] = "A\r\nWindows\r\nfile"; + __block NSUInteger invocations = 0; + NSData *data = [NSData dataWithBytes:inp length:strlen(inp)]; + [data enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { + switch (invocations) { + case 0: + STAssertEquals(line, NSMakeRange(0, 1), @"range of first line"); + break; + case 1: + STAssertEquals(line, NSMakeRange(3, 7), @"range of second line"); + break; + case 2: + STAssertEquals(line, NSMakeRange(12, 4), @"range of third line"); + break; + } + invocations++; + }]; + STAssertTrue(invocations==3, @"File with three lines, CRLF, terminated with empty line"); + } + //empty lines with all 3 endings + { + const char inp[] = "\n\r\n\r"; + __block NSUInteger invocations = 0; + NSData *data = [NSData dataWithBytes:inp length:strlen(inp)]; + [data enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { + switch (invocations) { + case 0: + STAssertEquals(line, NSMakeRange(0, 0), @"range of first line"); + break; + case 1: + STAssertEquals(line, NSMakeRange(1, 0), @"range of second line"); + break; + case 2: + STAssertEquals(line, NSMakeRange(3, 0), @"range of third line"); + break; + } + invocations++; + }]; + STAssertTrue(invocations==3, @"LF, CRLF and CR mixed"); + } + //looking for specific line breaks only + { + const char inp[] = "foo\nbar\r\nbaz\r"; + __block NSUInteger invocations = 0; + NSData *data = [NSData dataWithBytes:inp length:strlen(inp)]; + [data enumerateLinesBreakingAt:SPLineTerminatorCRLF withBlock:^(NSRange line, BOOL *stop) { + switch (invocations) { + case 0: + STAssertEquals(line, NSMakeRange(0, 7), @"range of first line"); + break; + case 1: + STAssertEquals(line, NSMakeRange(9, 4), @"range of second line"); + break; + } + invocations++; + }]; + STAssertTrue(invocations==2, @"other line breaks when only CRLF is expected"); + } +} + @end diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index 3f78a15e..e836efe3 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -188,6 +188,8 @@ 503B02D11AE95DD40060CAB1 /* SPTableFilterParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 503B02C91AE82C5E0060CAB1 /* SPTableFilterParser.m */; }; 503B02D21AE95E010060CAB1 /* SPConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 173284E91088FEDE0062E892 /* SPConstants.m */; }; 503CDBB21ACDC204004F8A2F /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 503CDBB11ACDC204004F8A2F /* Quartz.framework */; }; + 505F568F1BCEE485007467DD /* SPFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF1111BBCC57600104523 /* SPFunctions.m */; }; + 505F56901BCEE491007467DD /* SPOSInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 50EAB5B71A8FBB08008F627A /* SPOSInfo.m */; }; 506CE9311A311C6C0039F736 /* SPTableContentFilterController.m in Sources */ = {isa = PBXBuildFile; fileRef = 506CE9301A311C6C0039F736 /* SPTableContentFilterController.m */; }; 507FF1121BBCC57600104523 /* SPFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF1111BBCC57600104523 /* SPFunctions.m */; }; 507FF1621BBF0D5000104523 /* SPTableCopyTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 112730551180788A000737FD /* SPTableCopyTest.m */; }; @@ -3078,6 +3080,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 505F56901BCEE491007467DD /* SPOSInfo.m in Sources */, + 505F568F1BCEE485007467DD /* SPFunctions.m in Sources */, 502D21F81BA50966000D4CE7 /* SPDataAdditions.m in Sources */, 502D21F61BA50710000D4CE7 /* SPDataAdditionsTests.m in Sources */, 503B02D21AE95E010060CAB1 /* SPConstants.m in Sources */, -- cgit v1.2.3 From fa62a27d1fea6f8671a6c021d792433d64fc8d8f Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 15 Oct 2015 00:58:32 +0200 Subject: Add validation of SSL client cert file --- Source/SPConnectionController.h | 2 +- Source/SPConnectionController.m | 106 +++++++++++++++++++++++++++++---------- UnitTests/SPDataAdditionsTests.m | 11 ++++ 3 files changed, 92 insertions(+), 27 deletions(-) diff --git a/Source/SPConnectionController.h b/Source/SPConnectionController.h index ffface82..59a83740 100644 --- a/Source/SPConnectionController.h +++ b/Source/SPConnectionController.h @@ -47,7 +47,7 @@ #endif ; -@interface SPConnectionController : NSViewController +@interface SPConnectionController : NSViewController { id delegate; diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 1df7f9b9..8d2b99fa 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -63,11 +63,24 @@ static NSString *SPRemoveNode = @"RemoveNode"; static NSString *SPExportFavoritesFilename = @"SequelProFavorites.plist"; #endif +#if __MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_6 @interface NSSavePanel (NSSavePanel_unpublishedUntilSnowLeopardAPI) - (void)setShowsHiddenFiles:(BOOL)flag; @end +#endif + +/** + * This is a utility function to validate SSL key/certificate files + * @param fileData The contents of the file + * @param first Buffer with Data that has to occur on a line + * @param first_len Length of first + * @param second Buffer with Data that has to occur on a line after first + * @param second_len Length of second + * @return True if file contains two lines matching first and second and second comes after first + */ +static BOOL FindLinesInFile(NSData *fileData,const void *first,size_t first_len,const void *second,size_t second_len); @interface SPConnectionController () @@ -488,48 +501,64 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, - (BOOL)panel:(id)sender validateURL:(NSURL *)url error:(NSError **)outError { + // mysql limits yaSSL to PEM format files (it would support DER) if([keySelectionPanel accessoryView] == sslKeyFileLocationHelp) { - // mysql limits yaSSL to PEM format files and - // yaSSL only supports RSA type keys, with the exact string below on a single line + // and yaSSL only supports RSA type keys, with the exact string below on a single line NSError *err = nil; NSData *file = [NSData dataWithContentsOfURL:url options:NSDataReadingMappedIfSafe error:&err]; if(err) { *outError = err; return NO; } - __block BOOL rsaStart = NO; - __block BOOL rsaEnd = NO; - [file enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { - if(!rsaStart) { - const char rsaHead[] = "-----BEGIN RSA PRIVATE KEY-----"; - size_t rsaLen = strlen(rsaHead); - if(line.length != rsaLen) return; - if(memcmp(rsaHead, ([file bytes]+line.location), rsaLen) == 0) { - rsaStart = YES; - } - } - else { - const char rsaFoot[] = "-----END RSA PRIVATE KEY-----"; - size_t rsaLen = strlen(rsaFoot); - if(line.length != rsaLen) return; - if(memcmp(rsaFoot, ([file bytes]+line.location), rsaLen) == 0) { - rsaEnd = YES; - *stop = YES; - } - } - }]; + + // see PemToDer() in crypto_wrapper.cpp in yaSSL + const char rsaHead[] = "-----BEGIN RSA PRIVATE KEY-----"; + const char rsaFoot[] = "-----END RSA PRIVATE KEY-----"; - if(rsaStart && rsaEnd) return YES; + if(FindLinesInFile(file, rsaHead, strlen(rsaHead), rsaFoot, strlen(rsaFoot))) + return YES; *outError = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:@{ - NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedString(@"“%@” is not a valid private key file.", @""),[url lastPathComponent]], - NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Make sure the file contains an RSA private key and is using PEM encoding.", @""), + NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedString(@"“%@” is not a valid private key file.", @"connection view : ssl : key file picker : wrong format error title"),[url lastPathComponent]], + NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Make sure the file contains a RSA private key and is using PEM encoding.", @"connection view : ssl : key file picker : wrong format error description"), + NSURLErrorKey: url + }]; + return NO; + } + else if([keySelectionPanel accessoryView] == sslCertificateLocationHelp) { + NSError *err = nil; + NSData *file = [NSData dataWithContentsOfURL:url options:NSDataReadingMappedIfSafe error:&err]; + if(err) { + *outError = err; + return NO; + } + + // see PemToDer() in crypto_wrapper.cpp in yaSSL + const char cerHead[] = "-----BEGIN CERTIFICATE-----"; + const char cerFoot[] = "-----END CERTIFICATE-----"; + + if(FindLinesInFile(file, cerHead, strlen(cerHead), cerFoot, strlen(cerFoot))) + return YES; + + *outError = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:@{ + NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedString(@"“%@” is not a valid client certificate file.", @"connection view : ssl : client cert file picker : wrong format error title"),[url lastPathComponent]], + NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Make sure the file contains a X.509 client certificate and is using PEM encoding.", @"connection view : ssl : client cert picker : wrong format error description"), NSURLErrorKey: url }]; return NO; } //unknown, accept by default return YES; + + /* And now, an intermission from the mysql source code: + + if (!cert_file && key_file) + cert_file= key_file; + + if (!key_file && cert_file) + key_file= cert_file; + + */ } /** @@ -1994,3 +2023,28 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, } @end + +#pragma mark - + +BOOL FindLinesInFile(NSData *fileData,const void *first,size_t first_len,const void *second,size_t second_len) +{ + __block BOOL firstMatch = NO; + __block BOOL secondMatch = NO; + [fileData enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { + if(!firstMatch) { + if(line.length != first_len) return; + if(memcmp(first, ([fileData bytes]+line.location), first_len) == 0) { + firstMatch = YES; + } + } + else { + if(line.length != second_len) return; + if(memcmp(second, ([fileData bytes]+line.location), second_len) == 0) { + secondMatch = YES; + *stop = YES; + } + } + }]; + + return (firstMatch && secondMatch); +} diff --git a/UnitTests/SPDataAdditionsTests.m b/UnitTests/SPDataAdditionsTests.m index c7a03f7f..988267d2 100644 --- a/UnitTests/SPDataAdditionsTests.m +++ b/UnitTests/SPDataAdditionsTests.m @@ -371,6 +371,17 @@ }]; STAssertTrue(invocations==2, @"other line breaks when only CRLF is expected"); } + //stopping early + { + const char inp[] = "Two\nLines\n"; + __block NSUInteger invocations = 0; + NSData *data = [NSData dataWithBytes:inp length:strlen(inp)]; + [data enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { + invocations++; + *stop = YES; + }]; + STAssertTrue(invocations==1, @"File with two lines, stopped after first"); + } } @end -- cgit v1.2.3 From 2aca7fa78d644ce3d084351e66b50252890fbe01 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 15 Oct 2015 01:13:55 +0200 Subject: Guess someone at Apple forgot an #include in the 10.7 SDK --- Source/SPFunctions.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/SPFunctions.m b/Source/SPFunctions.m index 851c2422..d1c72555 100644 --- a/Source/SPFunctions.m +++ b/Source/SPFunctions.m @@ -29,7 +29,7 @@ // More info at #import "SPFunctions.h" -#import +#import #import "SPOSInfo.h" void SPMainQSync(void (^block)(void)) -- cgit v1.2.3 From ce97183958fd81e2f7cd5c1218f9e529009ddddb Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 15 Oct 2015 14:21:05 +0200 Subject: Fix Edit Trigger sheet no working correctly in many localized versions (#2289) --- Interfaces/English.lproj/DBView.xib | 3 ++ Source/SPTableTriggers.m | 76 ++++++++++++++++++++++++++----------- 2 files changed, 57 insertions(+), 22 deletions(-) diff --git a/Interfaces/English.lproj/DBView.xib b/Interfaces/English.lproj/DBView.xib index d4c1e723..b67ac787 100644 --- a/Interfaces/English.lproj/DBView.xib +++ b/Interfaces/English.lproj/DBView.xib @@ -9118,6 +9118,7 @@ IGRvIHlvdSB3YW50IHRvIGFkZCBmb3IgdGhpcyBmaWVsZD8 _popUpItemAction: + 1 @@ -9174,6 +9175,7 @@ IGRvIHlvdSB3YW50IHRvIGFkZCBmb3IgdGhpcyBmaWVsZD8 _popUpItemAction: + 1 @@ -9185,6 +9187,7 @@ IGRvIHlvdSB3YW50IHRvIGFkZCBmb3IgdGhpcyBmaWVsZD8 _popUpItemAction: + 2 diff --git a/Source/SPTableTriggers.m b/Source/SPTableTriggers.m index 179e509e..6f58ce7a 100644 --- a/Source/SPTableTriggers.m +++ b/Source/SPTableTriggers.m @@ -47,6 +47,20 @@ static const NSString *SPTriggerDefiner = @"TriggerDefiner"; static const NSString *SPTriggerCreated = @"TriggerCreated"; static const NSString *SPTriggerSQLMode = @"TriggerSQLMode"; +typedef NS_ENUM(NSInteger, SPTriggerActionTimeTag) { + SPTriggerActionTimeBeforeTag = 0, + SPTriggerActionTimeAfterTag = 1 +}; + +typedef NS_ENUM(NSInteger, SPTriggerEventTag) { + SPTriggerEventInsertTag = 0, + SPTriggerEventUpdateTag = 1, + SPTriggerEventDeleteTag = 2 +}; + +static SPTriggerActionTimeTag TagForActionTime(NSString *mysql); +static SPTriggerEventTag TagForEvent(NSString *mysql); + @interface SPTableTriggers () - (void)_editTriggerAtIndex:(NSInteger)index; @@ -208,18 +222,29 @@ static const NSString *SPTriggerSQLMode = @"TriggerSQLMode"; } NSString *triggerName = [triggerNameTextField stringValue]; - NSString *triggerActionTime = ([triggerActionTimePopUpButton indexOfSelectedItem]) ? @"AFTER" : @"BEFORE"; - NSString *triggerEvent = @""; - switch ([triggerEventPopUpButton indexOfSelectedItem]) + NSString *triggerActionTime = @""; + switch ([triggerActionTimePopUpButton selectedTag]) + { + case SPTriggerActionTimeBeforeTag: + triggerActionTime = @"BEFORE"; + break; + + case SPTriggerActionTimeAfterTag: + triggerActionTime = @"AFTER"; + break; + } + + NSString *triggerEvent = @""; + switch ([triggerEventPopUpButton selectedTag]) { - case 0: + case SPTriggerEventInsertTag: triggerEvent = @"INSERT"; break; - case 1: + case SPTriggerEventUpdateTag: triggerEvent = @"UPDATE"; break; - case 2: + case SPTriggerEventDeleteTag: triggerEvent = @"DELETE"; break; } @@ -531,23 +556,9 @@ static const NSString *SPTriggerSQLMode = @"TriggerSQLMode"; [triggerNameTextField setStringValue:[trigger objectForKey:SPTriggerName]]; [triggerStatementTextView setString:[trigger objectForKey:SPTriggerStatement]]; - // 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:SPTriggerActionTime] uppercaseString]]) { - [triggerActionTimePopUpButton selectItemAtIndex:i]; - break; - } - } + [triggerActionTimePopUpButton selectItemWithTag:TagForActionTime([trigger objectForKey:SPTriggerActionTime])]; - // 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:SPTriggerEvent] uppercaseString]]) { - [triggerEventPopUpButton selectItemAtIndex:i]; - break; - } - } + [triggerEventPopUpButton selectItemWithTag:TagForEvent([trigger objectForKey:SPTriggerEvent])]; // Change button label from Add to Edit [confirmAddTriggerButton setTitle:NSLocalizedString(@"Save", @"Save trigger button label")]; @@ -642,3 +653,24 @@ static const NSString *SPTriggerSQLMode = @"TriggerSQLMode"; } @end + +#pragma mark - + +SPTriggerActionTimeTag TagForActionTime(NSString *mysql) +{ + NSString *uc = [mysql uppercaseString]; + if([uc isEqualToString:@"BEFORE"]) return SPTriggerActionTimeBeforeTag; + if([uc isEqualToString:@"AFTER"]) return SPTriggerActionTimeAfterTag; + SPLog(@"Unknown trigger action time: %@",uc); + return -1; +} + +SPTriggerEventTag TagForEvent(NSString *mysql) +{ + NSString *uc = [mysql uppercaseString]; + if([uc isEqualToString:@"INSERT"]) return SPTriggerEventInsertTag; + if([uc isEqualToString:@"UPDATE"]) return SPTriggerEventUpdateTag; + if([uc isEqualToString:@"DELETE"]) return SPTriggerEventDeleteTag; + SPLog(@"Unknown trigger event: %@",uc); + return -1; +} -- cgit v1.2.3 From 87a37af3999b9cf3f6ae4e06c0d6e4a3939e7dd1 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 16 Oct 2015 15:02:26 +0200 Subject: Split a huuuge method into two --- Source/SPTableData.m | 76 +++++++++++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 33 deletions(-) diff --git a/Source/SPTableData.m b/Source/SPTableData.m index d3af3580..17316fbf 100644 --- a/Source/SPTableData.m +++ b/Source/SPTableData.m @@ -42,6 +42,7 @@ @interface SPTableData (PrivateAPI) - (void)_loopWhileWorking; +- (NSDictionary *)parseCreateStatement:(NSString *)tableDef ofType:(NSString *)tableType; @end @@ -437,21 +438,12 @@ } /** - * Retrieve the CREATE TABLE string for a table and analyse it to extract the field - * details, primary key, unique keys, and table encoding. - * In future this could also be used to retrieve the majority of index information - * assuming information like cardinality isn't needed. - * This function is rather long due to the painful parsing required, but is fast. - * Returns a boolean indicating success. + * Retrieve the CREATE statement for a table/view and return extracted table + * structure information. + * @attention This method will interact with the UI on errors/connection loss! */ - (NSDictionary *) informationForTable:(NSString *)tableName { - SPSQLParser *createTableParser, *fieldsParser, *fieldParser; - NSMutableArray *tableColumns, *fieldStrings; - NSMutableDictionary *tableColumn, *tableData; - NSString *encodingString = nil; - NSUInteger i, stringStart; - unichar quoteCharacter; BOOL changeEncoding = ![[mySQLConnection encoding] isEqualToString:@"utf8"]; // Catch unselected tables and return nil @@ -527,27 +519,46 @@ } tableCreateSyntax = [[NSString alloc] initWithString:[syntaxResult objectAtIndex:1]]; - createTableParser = [[SPSQLParser alloc] initWithString:[syntaxResult objectAtIndex:1]]; + + NSDictionary *tableData = [self parseCreateStatement:tableCreateSyntax ofType:[resultFieldNames objectAtIndex:0]]; + + if (changeEncoding) [mySQLConnection restoreStoredEncoding]; + + return tableData; +} + +/** + * Analyse a CREATE TABLE tring to extract the field details, primary key, unique keys, and table encoding. + * @param tableDef @"CREATE TABLE ..." + * @param tableType Can either be Table or View. Value is copied to the result and not used otherwise + * @return A dict containing info about the table's structure + * + * In future this could also be used to retrieve the majority of index information + * assuming information like cardinality isn't needed. + * This function is rather long due to the painful parsing required, but is fast. + */ +- (NSDictionary *)parseCreateStatement:(NSString *)tableDef ofType:(NSString *)tableType +{ + SPSQLParser *createTableParser = [[SPSQLParser alloc] initWithString:tableDef]; // Extract the fields definition string from the CREATE TABLE syntax - fieldsParser = [[SPSQLParser alloc] initWithString:[createTableParser trimAndReturnStringFromCharacter:'(' toCharacter:')' trimmingInclusively:YES returningInclusively:NO skippingBrackets:YES]]; + SPSQLParser *fieldsParser = [[SPSQLParser alloc] initWithString:[createTableParser trimAndReturnStringFromCharacter:'(' toCharacter:')' trimmingInclusively:YES returningInclusively:NO skippingBrackets:YES]]; // Split the fields and keys string into an array of individual elements - fieldStrings = [[NSMutableArray alloc] initWithArray:[fieldsParser splitStringByCharacter:',' skippingBrackets:YES]]; + NSMutableArray *fieldStrings = [[NSMutableArray alloc] initWithArray:[fieldsParser splitStringByCharacter:',' skippingBrackets:YES]]; // fieldStrings should now hold unparsed field and key strings, while tableProperty string holds unparsed // table information. Proceed further by parsing the field strings. - tableColumns = [[NSMutableArray alloc] init]; - tableColumn = [[NSMutableDictionary alloc] init]; - fieldParser = [[SPSQLParser alloc] init]; + NSMutableArray *tableColumns = [[NSMutableArray alloc] init]; + NSMutableDictionary *tableColumn = [[NSMutableDictionary alloc] init]; NSCharacterSet *whitespaceAndNewlineSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; NSCharacterSet *quoteSet = [NSCharacterSet characterSetWithCharactersInString:@"`'\""]; NSCharacterSet *bracketSet = [NSCharacterSet characterSetWithCharactersInString:@"()"]; - tableData = [NSMutableDictionary dictionary]; + NSMutableDictionary *tableData = [NSMutableDictionary dictionary]; - for (i = 0; i < [fieldStrings count]; i++) { + for (NSUInteger i = 0; i < [fieldStrings count]; i++) { // Take this field/key string, trim whitespace from both ends and remove comments [fieldsParser setString:[NSArrayObjectAtIndex(fieldStrings, i) stringByTrimmingCharactersInSet:whitespaceAndNewlineSet]]; @@ -559,7 +570,7 @@ // If the first character is a quote character, this is a field definition. if ([quoteSet characterIsMember:[fieldsParser characterAtIndex:0]]) { - quoteCharacter = [fieldsParser characterAtIndex:0]; + unichar quoteCharacter = [fieldsParser characterAtIndex:0]; // Capture the area between the two backticks as the name // Set the parser to ignoreCommentStrings since a field name can contain # or /* @@ -734,11 +745,11 @@ [primaryKeyFields addObject:primaryFieldName]; for (NSMutableDictionary *theTableColumn in tableColumns) { if ([[theTableColumn objectForKey:@"name"] isEqualToString:primaryFieldName]) { - [theTableColumn setObject:@1 forKey:@"isprimarykey"]; - break; + [theTableColumn setObject:@1 forKey:@"isprimarykey"]; + break; + } } - } - } + } [tableData setObject:primaryKeyFields forKey:@"primarykeyfield"]; } } @@ -752,9 +763,9 @@ NSString *uniqueFieldName = [[SPSQLParser stringWithString:quotedUniqueKey] unquotedString]; for (NSMutableDictionary *theTableColumn in tableColumns) { if ([[theTableColumn objectForKey:@"name"] isEqualToString:uniqueFieldName]) { - [theTableColumn setObject:@1 forKey:@"unique"]; - break; - } + [theTableColumn setObject:@1 forKey:@"unique"]; + break; + } } } } @@ -769,12 +780,14 @@ [tableColumn release]; // Extract the encoding from the table properties string - other details come from TABLE STATUS. + NSString *encodingString = nil; NSRange charsetDefinitionRange = [createTableParser rangeOfString:@"CHARSET=" options:NSCaseInsensitiveSearch]; if (charsetDefinitionRange.location == NSNotFound) { charsetDefinitionRange = [createTableParser rangeOfString:@"CHARACTER SET=" options:NSCaseInsensitiveSearch]; } if (charsetDefinitionRange.location != NSNotFound) { - stringStart = NSMaxRange(charsetDefinitionRange); + NSUInteger stringStart = NSMaxRange(charsetDefinitionRange); + NSUInteger i; for (i = stringStart; i < [createTableParser length]; i++) { if ([createTableParser characterAtIndex:i] == ' ') break; } @@ -795,10 +808,9 @@ } [createTableParser release]; - [fieldParser release]; // this will be 'Table' or 'View' - [tableData setObject:[resultFieldNames objectAtIndex:0] forKey:@"type"]; + [tableData setObject:tableType forKey:@"type"]; [tableData setObject:[NSString stringWithString:encodingString] forKey:@"encoding"]; [tableData setObject:[NSArray arrayWithArray:tableColumns] forKey:@"columns"]; [tableData setObject:[NSArray arrayWithArray:constraints] forKey:@"constraints"]; @@ -806,8 +818,6 @@ [encodingString release]; [tableColumns release]; - if (changeEncoding) [mySQLConnection restoreStoredEncoding]; - return tableData; } -- cgit v1.2.3 From 3c1850c616018e2ffd19387a94ad4854e99f9aa6 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 17 Oct 2015 02:04:49 +0200 Subject: Very likely a fix for the _scrollViewDidChangeBounds debacle (#2126) --- Source/SPTableView.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/SPTableView.m b/Source/SPTableView.m index 3b1cf507..84e43a26 100644 --- a/Source/SPTableView.m +++ b/Source/SPTableView.m @@ -93,6 +93,8 @@ [notifier addObserver:self selector:@selector(_disableDoubleClickAction:) name:NSWindowWillBeginSheetNotification object:aWindow]; [notifier addObserver:self selector:@selector(_enableDoubleClickAction:) name:NSWindowDidEndSheetNotification object:aWindow]; } + + [super viewWillMoveToWindow:aWindow]; } /** -- cgit v1.2.3 From 73e5d92a927a1411389e22229872a8a2ff4e6a02 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 17 Oct 2015 20:28:45 +0200 Subject: Minor change --- .../Ping & KeepAlive.h | 2 +- .../Ping & KeepAlive.m | 34 ++++++++++++---------- .../SPMySQLFramework/Source/SPMySQLConnection.h | 2 +- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h index cff8d43b..ae3c8156 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h @@ -51,6 +51,6 @@ void _forceThreadExit(int signalNumber); void _pingThreadCleanup(void *pingDetails); // Cancellation -- (void)_cancelKeepAlives; +- (BOOL)_cancelKeepAlives; @end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m index e43b4a2f..0c455242 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m @@ -102,8 +102,7 @@ } // Return as no further ping action required this cycle. - keepAliveThread = nil; - return; + goto end; } // Otherwise, perform a background ping. @@ -113,6 +112,7 @@ } else { keepAlivePingFailures++; } +end: keepAliveThread = nil; } @@ -170,7 +170,7 @@ // If the ping timeout has been exceeded, or the ping thread has been // cancelled, force a timeout; double-check that the thread is still active. - if (([keepAliveThread isCancelled] || pingElapsedTime > pingTimeout) + if (([[NSThread currentThread] isCancelled] || pingElapsedTime > pingTimeout) && keepAlivePingThreadActive && !threadCancelled) { @@ -254,24 +254,28 @@ void _pingThreadCleanup(void *pingDetails) /** * If a keepalive thread is active, cancel it, and wait a short time for it * to exit. + * + * @return YES, if the thread exited within 10 seconds after canceling it */ -- (void)_cancelKeepAlives +- (BOOL)_cancelKeepAlives { // If no keepalive thread is active, return - if (!keepAliveThread) { - return; - } + if (keepAliveThread) { - // Mark the thread as cancelled - [keepAliveThread cancel]; + // Mark the thread as cancelled + [keepAliveThread cancel]; - // Wait inside a time limit of ten seconds for it to exit - uint64_t threadCancelStartTime_t = mach_absolute_time(); - do { - usleep(100000); - if (_elapsedSecondsSinceAbsoluteTime(threadCancelStartTime_t) > 10) break; - } while (keepAliveThread); + // Wait inside a time limit of ten seconds for it to exit + uint64_t threadCancelStartTime_t = mach_absolute_time(); + do { + usleep(100000); + if (_elapsedSecondsSinceAbsoluteTime(threadCancelStartTime_t) > 10) return NO; + } while (keepAliveThread); + + } + + return YES; } @end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h index c65ec2fb..9fae3f98 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h @@ -85,7 +85,7 @@ CGFloat keepAliveInterval; uint64_t lastKeepAliveTime; NSUInteger keepAlivePingFailures; - NSThread *keepAliveThread; + volatile NSThread *keepAliveThread; pthread_t keepAlivePingThread_t; BOOL keepAlivePingThreadActive; BOOL keepAliveLastPingSuccess; -- cgit v1.2.3 From 4d97cbd4df1ebb451d89d6c4e12dd7c622d00b84 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 17 Oct 2015 21:26:30 +0200 Subject: This change will either reduce the amount of crashes or increase the amount of beachballing. In the latter case please file an issue. --- .../Source/SPMySQLConnection Categories/Ping & KeepAlive.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m index 0c455242..c4469f5c 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m @@ -186,6 +186,9 @@ end: keepAliveLastPingBlocked = YES; } } while (keepAlivePingThreadActive); + + //wait for thread to go away, otherwise our free() below might run before _pingThreadCleanup() + pthread_join(keepAlivePingThread_t, NULL); // Clean up keepAlivePingThread_t = NULL; -- cgit v1.2.3 From 747f03cd9fb14f06bffd33adb60b949733296a31 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 18 Oct 2015 08:35:57 +0200 Subject: Avoid a seldom exception when a VIEW is selected and there a connection issues --- Source/SPTableData.m | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/SPTableData.m b/Source/SPTableData.m index 17316fbf..bf1aec20 100644 --- a/Source/SPTableData.m +++ b/Source/SPTableData.m @@ -870,6 +870,12 @@ // Retrieve the table syntax string if (tableCreateSyntax) SPClear(tableCreateSyntax); NSString *syntaxString = [[theResult getRowAsArray] objectAtIndex:1]; + + // Crash reports indicate that this does happen, however I'm not sure why. + if (!syntaxString) { + NSLog(@"%s: query for 'SHOW CREATE TABLE' returned nil but there was no connection error!? queryErrored=%d, userTriggeredDisconnect=%d, isConnected=%d, theResult=%@",__func__,[mySQLConnection queryErrored],[mySQLConnection userTriggeredDisconnect],[mySQLConnection isConnected],theResult); + return nil; + } // A NULL value indicates that the user does not have permission to view the syntax if ([syntaxString isNSNull]) { -- cgit v1.2.3 From e12e3e86f26b47a51c10940bca1102326d47bf82 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 20 Oct 2015 01:04:41 +0200 Subject: Fix one cause a popular crash involving alert dialogs (part of #2297) --- Source/SPTableContent.m | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m index 6b0065dd..618f6c5d 100644 --- a/Source/SPTableContent.m +++ b/Source/SPTableContent.m @@ -985,12 +985,13 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper #ifndef SP_CODA if(activeFilter == 0) { #endif + NSString *errorDetail; if(filterString) - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"The table data couldn't be loaded presumably due to used filter clause. \n\nMySQL said: %@", @"message of panel when loading of table failed and presumably due to used filter argument"), [mySQLConnection lastErrorMessage]]); + errorDetail = [NSString stringWithFormat:NSLocalizedString(@"The table data couldn't be loaded presumably due to used filter clause. \n\nMySQL said: %@", @"message of panel when loading of table failed and presumably due to used filter argument"), [mySQLConnection lastErrorMessage]]; else - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"The table data couldn't be loaded.\n\nMySQL said: %@", @"message of panel when loading of table failed"), [mySQLConnection lastErrorMessage]]); + errorDetail = [NSString stringWithFormat:NSLocalizedString(@"The table data couldn't be loaded.\n\nMySQL said: %@", @"message of panel when loading of table failed"), [mySQLConnection lastErrorMessage]]; + + SPOnewayAlertSheet(NSLocalizedString(@"Error", @"error"), nil, [tableDocumentInstance parentWindow], errorDetail, NSWarningAlertStyle); } #ifndef SP_CODA // Filter task came from filter table -- cgit v1.2.3 From 5357fd1375d19a6c5de96293b3899f430422a3c0 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 20 Oct 2015 15:24:10 +0200 Subject: Replace a lot of SPBeginAlertSheet()s with the more concise SPOnewayAlertSheet() This should also eliminate a few use-after-free crashes --- Source/SPAlertSheets.h | 6 +++ Source/SPAlertSheets.m | 13 +++++- Source/SPAppController.m | 46 ++++++++++++------- Source/SPBundleHTMLOutputController.m | 3 +- Source/SPConnectionController.m | 54 ++++++++++++++++------ Source/SPConnectionDelegate.m | 10 +---- Source/SPConnectionHandler.m | 6 ++- Source/SPCopyTable.m | 21 ++++++--- Source/SPCustomQuery.m | 38 ++++++++++------ Source/SPDataImport.m | 78 ++++++++++++++++---------------- Source/SPDatabaseDocument.m | 85 ++++++++++++++++++++++------------- Source/SPExportController.m | 2 +- Source/SPExtendedTableInfo.m | 32 ++++++++----- Source/SPIndexesController.m | 7 ++- Source/SPKeychain.m | 27 ++++++----- Source/SPProcessListController.m | 14 ++++-- Source/SPTableContent.h | 2 +- Source/SPTableContent.m | 66 ++++++++++++++++++--------- Source/SPTableContentDelegate.m | 7 ++- Source/SPTableData.m | 30 ++++--------- Source/SPTableRelations.m | 9 ++-- Source/SPTableStructure.m | 28 ++++++------ Source/SPTableStructureDelegate.m | 7 ++- Source/SPTableStructureLoading.m | 9 ++-- Source/SPTableTriggers.m | 14 +++--- Source/SPTablesList.m | 54 +++++++++++++--------- Source/SPTextView.m | 7 ++- Source/SPTextViewAdditions.m | 14 ++++-- Source/SPUserManager.m | 16 ++++--- 29 files changed, 432 insertions(+), 273 deletions(-) diff --git a/Source/SPAlertSheets.h b/Source/SPAlertSheets.h index 99e64e59..c92b635d 100644 --- a/Source/SPAlertSheets.h +++ b/Source/SPAlertSheets.h @@ -58,6 +58,12 @@ void SPBeginAlertSheet( ); void SPOnewayAlertSheet( + NSString *title, + NSWindow *docWindow, + NSString *msg +); + +void SPOnewayAlertSheetWithStyle( NSString *title, NSString *defaultButton, NSWindow *docWindow, diff --git a/Source/SPAlertSheets.m b/Source/SPAlertSheets.m index d19da6df..258aac6d 100644 --- a/Source/SPAlertSheets.m +++ b/Source/SPAlertSheets.m @@ -143,6 +143,17 @@ @end +/** + * Shorthand for SPOnewayAlertSheetWithStyle() with defaultButton=nil and alertStyle=NSWarningAlertStyle + */ +void SPOnewayAlertSheet( + NSString *title, + NSWindow *docWindow, + NSString *msg) +{ + SPOnewayAlertSheetWithStyle(title, nil, docWindow, msg, NSWarningAlertStyle); +} + /** * A Send-and-forget variant for displaying alerts. * It will queue the alert on the main thread and *always* immediately return. @@ -151,7 +162,7 @@ * If nil is passed as the button title it will be changed to @"OK". * If nil is passed as the window NSAlert will be modal */ -void SPOnewayAlertSheet( +void SPOnewayAlertSheetWithStyle( NSString *title, NSString *defaultButton, NSWindow *docWindow, diff --git a/Source/SPAppController.m b/Source/SPAppController.m index d733b48a..7ed49859 100644 --- a/Source/SPAppController.m +++ b/Source/SPAppController.m @@ -851,8 +851,7 @@ } if(![status writeToFile:statusFileName atomically:YES encoding:NSUTF8StringEncoding error:nil]) { NSBeep(); - SPBeginAlertSheet(NSLocalizedString(@"BASH Error", @"bash error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self frontDocumentWindow], self, nil, nil, - NSLocalizedString(@"Status file for sequelpro url scheme command couldn't be written!", @"status file for sequelpro url scheme command couldn't be written error message")); + SPOnewayAlertSheet(NSLocalizedString(@"BASH Error", @"bash error"), [self frontDocumentWindow], NSLocalizedString(@"Status file for sequelpro url scheme command couldn't be written!", @"status file for sequelpro url scheme command couldn't be written error message")); } [result writeToFile:resultFileName atomically:YES encoding:NSUTF8StringEncoding error:nil]; return; @@ -897,8 +896,11 @@ BOOL succeed = [status writeToFile:statusFileName atomically:YES encoding:NSUTF8StringEncoding error:nil]; if(!succeed) { NSBeep(); - SPBeginAlertSheet(NSLocalizedString(@"BASH Error", @"bash error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self frontDocumentWindow], self, nil, nil, - NSLocalizedString(@"Status file for sequelpro url scheme command couldn't be written!", @"status file for sequelpro url scheme command couldn't be written error message")); + SPOnewayAlertSheet( + NSLocalizedString(@"BASH Error", @"bash error"), + [self frontDocumentWindow], + NSLocalizedString(@"Status file for sequelpro url scheme command couldn't be written!", @"status file for sequelpro url scheme command couldn't be written error message") + ); } return; } @@ -939,8 +941,11 @@ [cmdDict setObject:(passedProcessID)?:@"" forKey:@"id"]; [processDocument handleSchemeCommand:cmdDict]; } else { - SPBeginAlertSheet(NSLocalizedString(@"sequelpro URL Scheme Error", @"sequelpro url Scheme Error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, - [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), [command description], NSLocalizedString(@"sequelpro URL scheme command not supported.", @"sequelpro URL scheme command not supported.")]); + SPOnewayAlertSheet( + NSLocalizedString(@"sequelpro URL Scheme Error", @"sequelpro url Scheme Error"), + [NSApp mainWindow], + [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), [command description], NSLocalizedString(@"sequelpro URL scheme command not supported.", @"sequelpro URL scheme command not supported.")] + ); // If command failed notify the file handle hand shake mechanism NSString *out = @"1"; @@ -980,9 +985,11 @@ encoding:NSUTF8StringEncoding error:nil]; - SPBeginAlertSheet(NSLocalizedString(@"sequelpro URL Scheme Error", @"sequelpro url Scheme Error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, - [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), [command description], NSLocalizedString(@"An error for sequelpro URL scheme command occurred. Probably no corresponding connection window found.", @"An error for sequelpro URL scheme command occurred. Probably no corresponding connection window found.")]); - + SPOnewayAlertSheet( + NSLocalizedString(@"sequelpro URL Scheme Error", @"sequelpro url Scheme Error"), + [NSApp mainWindow], + [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), [command description], NSLocalizedString(@"An error for sequelpro URL scheme command occurred. Probably no corresponding connection window found.", @"An error for sequelpro URL scheme command occurred. Probably no corresponding connection window found.")] + ); usleep(5000); [fm removeItemAtPath:[NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultStatusPathHeader, passedProcessID] error:nil]; @@ -993,8 +1000,11 @@ } else { - SPBeginAlertSheet(NSLocalizedString(@"sequelpro URL Scheme Error", @"sequelpro url Scheme Error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, - [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), [command description], NSLocalizedString(@"An error occur while executing a scheme command. If the scheme command was invoked by a Bundle command, it could be that the command still runs. You can try to terminate it by pressing ⌘+. or via the Activities pane.", @"an error occur while executing a scheme command. if the scheme command was invoked by a bundle command, it could be that the command still runs. you can try to terminate it by pressing ⌘+. or via the activities pane.")]); + SPOnewayAlertSheet( + NSLocalizedString(@"sequelpro URL Scheme Error", @"sequelpro url Scheme Error"), + [NSApp mainWindow], + [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), [command description], NSLocalizedString(@"An error occur while executing a scheme command. If the scheme command was invoked by a Bundle command, it could be that the command still runs. You can try to terminate it by pressing ⌘+. or via the Activities pane.", @"an error occur while executing a scheme command. if the scheme command was invoked by a bundle command, it could be that the command still runs. you can try to terminate it by pressing ⌘+. or via the activities pane.")] + ); } if(processDocument) @@ -1141,8 +1151,11 @@ if(inputFileError != nil) { NSString *errorMessage = [inputFileError localizedDescription]; - SPBeginAlertSheet(NSLocalizedString(@"Bundle Error", @"bundle error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self frontDocumentWindow], self, nil, nil, - [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), [cmdData objectForKey:@"name"], errorMessage]); + SPOnewayAlertSheet( + NSLocalizedString(@"Bundle Error", @"bundle error"), + [self frontDocumentWindow], + [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), [cmdData objectForKey:@"name"], errorMessage] + ); if (cmdData) [cmdData release]; return; } @@ -1235,8 +1248,11 @@ } } else if([err code] != 9) { // Suppress an error message if command was killed NSString *errorMessage = [err localizedDescription]; - SPBeginAlertSheet(NSLocalizedString(@"BASH Error", @"bash error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, - [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), [cmdData objectForKey:@"name"], errorMessage]); + SPOnewayAlertSheet( + NSLocalizedString(@"BASH Error", @"bash error"), + [NSApp mainWindow], + [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), [cmdData objectForKey:@"name"], errorMessage] + ); } } diff --git a/Source/SPBundleHTMLOutputController.m b/Source/SPBundleHTMLOutputController.m index 16a2f9da..ee4b5286 100644 --- a/Source/SPBundleHTMLOutputController.m +++ b/Source/SPBundleHTMLOutputController.m @@ -191,8 +191,7 @@ static NSString *SPSaveDocumentAction = @"SPSaveDocument"; encoding:NSUTF8StringEncoding error:&err]; if (err != nil) { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil, - [NSString stringWithFormat:@"%@", [err localizedDescription]]); + SPOnewayAlertSheet(NSLocalizedString(@"Error", @"error"), [self window], [NSString stringWithFormat:@"%@", [err localizedDescription]]); } } } diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 8d2b99fa..91629bf3 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -183,13 +183,21 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, // Ensure that host is not empty if this is a TCP/IP or SSH connection if (([self type] == SPTCPIPConnection || [self type] == SPSSHTunnelConnection) && ![[self host] length]) { - SPBeginAlertSheet(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], self, nil, nil, NSLocalizedString(@"Insufficient details provided to establish a connection. Please enter at least the hostname.", @"insufficient details informative message")); + SPOnewayAlertSheet( + NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), + [dbDocument parentWindow], + NSLocalizedString(@"Insufficient details provided to establish a connection. Please enter at least the hostname.", @"insufficient details informative message") + ); return; } // If SSH is enabled, ensure that the SSH host is not nil if ([self type] == SPSSHTunnelConnection && ![[self sshHost] length]) { - SPBeginAlertSheet(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], self, nil, nil, NSLocalizedString(@"Insufficient details provided to establish a connection. Please enter the hostname for the SSH Tunnel, or disable the SSH Tunnel.", @"insufficient SSH tunnel details informative message")); + SPOnewayAlertSheet( + NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), + [dbDocument parentWindow], + NSLocalizedString(@"Insufficient details provided to establish a connection. Please enter the hostname for the SSH Tunnel, or disable the SSH Tunnel.", @"insufficient SSH tunnel details informative message") + ); return; } @@ -197,7 +205,11 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, if ([self type] == SPSSHTunnelConnection && sshKeyLocationEnabled && sshKeyLocation) { if (![[NSFileManager defaultManager] fileExistsAtPath:[sshKeyLocation stringByExpandingTildeInPath]]) { [self setSshKeyLocationEnabled:NSOffState]; - SPBeginAlertSheet(NSLocalizedString(@"SSH Key not found", @"SSH key check error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], self, nil, nil, NSLocalizedString(@"A SSH key location was specified, but no file was found in the specified location. Please re-select the key and try again.", @"SSH key not found message")); + SPOnewayAlertSheet( + NSLocalizedString(@"SSH Key not found", @"SSH key check error"), + [dbDocument parentWindow], + NSLocalizedString(@"A SSH key location was specified, but no file was found in the specified location. Please re-select the key and try again.", @"SSH key not found message") + ); return; } } @@ -214,7 +226,11 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, [self setSslKeyFileLocationEnabled:NSOffState]; [self setSslKeyFileLocation:nil]; - SPBeginAlertSheet(NSLocalizedString(@"SSL Key File not found", @"SSL key file check error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], self, nil, nil, NSLocalizedString(@"A SSL key file location was specified, but no file was found in the specified location. Please re-select the key file and try again.", @"SSL key file not found message")); + SPOnewayAlertSheet( + NSLocalizedString(@"SSL Key File not found", @"SSL key file check error"), + [dbDocument parentWindow], + NSLocalizedString(@"A SSL key file location was specified, but no file was found in the specified location. Please re-select the key file and try again.", @"SSL key file not found message") + ); return; } @@ -225,7 +241,11 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, [self setSslCertificateFileLocationEnabled:NSOffState]; [self setSslCertificateFileLocation:nil]; - SPBeginAlertSheet(NSLocalizedString(@"SSL Certificate File not found", @"SSL certificate file check error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], self, nil, nil, NSLocalizedString(@"A SSL certificate location was specified, but no file was found in the specified location. Please re-select the certificate and try again.", @"SSL certificate file not found message")); + SPOnewayAlertSheet( + NSLocalizedString(@"SSL Certificate File not found", @"SSL certificate file check error"), + [dbDocument parentWindow], + NSLocalizedString(@"A SSL certificate location was specified, but no file was found in the specified location. Please re-select the certificate and try again.", @"SSL certificate file not found message") + ); return; } @@ -236,7 +256,11 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, [self setSslCACertFileLocationEnabled:NSOffState]; [self setSslCACertFileLocation:nil]; - SPBeginAlertSheet(NSLocalizedString(@"SSL Certificate Authority File not found", @"SSL certificate authority file check error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], self, nil, nil, NSLocalizedString(@"A SSL Certificate Authority certificate location was specified, but no file was found in the specified location. Please re-select the Certificate Authority certificate and try again.", @"SSL CA certificate file not found message")); + SPOnewayAlertSheet( + NSLocalizedString(@"SSL Certificate Authority File not found", @"SSL certificate authority file check error"), + [dbDocument parentWindow], + NSLocalizedString(@"A SSL Certificate Authority certificate location was specified, but no file was found in the specified location. Please re-select the Certificate Authority certificate and try again.", @"SSL CA certificate file not found message") + ); return; } @@ -1224,17 +1248,21 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, // Ensure that host is not empty if this is a TCP/IP or SSH connection if (validateDetails && ([self type] == SPTCPIPConnection || [self type] == SPSSHTunnelConnection) && ![[self host] length]) { - SPBeginAlertSheet(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), - NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], nil, nil, nil, - NSLocalizedString(@"Insufficient details provided to establish a connection. Please provide at least a host.", @"insufficient details informative message")); + SPOnewayAlertSheet( + NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), + [dbDocument parentWindow], + NSLocalizedString(@"Insufficient details provided to establish a connection. Please provide at least a host.", @"insufficient details informative message") + ); return; } // If SSH is enabled, ensure that the SSH host is not nil if (validateDetails && [self type] == SPSSHTunnelConnection && ![[self sshHost] length]) { - SPBeginAlertSheet(NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), - NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], nil, nil, nil, - NSLocalizedString(@"Please enter the hostname for the SSH Tunnel, or disable the SSH Tunnel.", @"message of panel when ssh details are incomplete")); + SPOnewayAlertSheet( + NSLocalizedString(@"Insufficient connection details", @"insufficient details message"), + [dbDocument parentWindow], + NSLocalizedString(@"Please enter the hostname for the SSH Tunnel, or disable the SSH Tunnel.", @"message of panel when ssh details are incomplete") + ); return; } @@ -1488,7 +1516,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, [dbDocument parentWindow], // Window to attach to self, // Modal delegate @selector(localhostErrorSheetDidEnd:returnCode:contextInfo:), // Did end selector - nil, // Contextual info for selectors + NULL, // Contextual info for selectors NSLocalizedString(@"To MySQL, 'localhost' is a special host and means that a socket connection should be used.\n\nDid you mean to use a socket connection, or to connect to the local machine via a port? If you meant to connect via a port, '127.0.0.1' should be used instead of 'localhost'.", @"message of error when using 'localhost' for a network connection")); return NO; } diff --git a/Source/SPConnectionDelegate.m b/Source/SPConnectionDelegate.m index caf7926c..9e5cbd25 100644 --- a/Source/SPConnectionDelegate.m +++ b/Source/SPConnectionDelegate.m @@ -123,15 +123,9 @@ */ - (void)noConnectionAvailable:(id)connection { - SPBeginAlertSheet( + SPOnewayAlertSheet( NSLocalizedString(@"No connection available", @"no connection available message"), - NSLocalizedString(@"OK", @"OK button"), - nil, - nil, [self parentWindow], - self, - nil, - nil, NSLocalizedString(@"An error has occured and there doesn't seem to be a connection available.", @"no connection available informatie message") ); } @@ -176,7 +170,7 @@ - (void)showErrorWithTitle:(NSString *)theTitle message:(NSString *)theMessage { if ([[self parentWindow] isVisible]) { - SPBeginAlertSheet(theTitle, NSLocalizedString(@"OK", @"OK button"), nil, nil, [self parentWindow], self, nil, nil, theMessage); + SPOnewayAlertSheet(theTitle, [self parentWindow], theMessage); } } diff --git a/Source/SPConnectionHandler.m b/Source/SPConnectionHandler.m index d27e9a1a..a1c7a532 100644 --- a/Source/SPConnectionHandler.m +++ b/Source/SPConnectionHandler.m @@ -353,7 +353,11 @@ static NSString *SPLocalhostAddress = @"127.0.0.1"; // If SSL was enabled, check it was established correctly if (useSSL && ([self type] == SPTCPIPConnection || [self type] == SPSocketConnection)) { if (![mySQLConnection isConnectedViaSSL]) { - SPBeginAlertSheet(NSLocalizedString(@"SSL connection not established", @"SSL requested but not used title"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], nil, nil, nil, NSLocalizedString(@"You requested that the connection should be established using SSL, but MySQL made the connection without SSL.\n\nThis may be because the server does not support SSL connections, or has SSL disabled; or insufficient details were supplied to establish an SSL connection.\n\nThis connection is not encrypted.", @"SSL connection requested but not established error detail")); + SPOnewayAlertSheet( + NSLocalizedString(@"SSL connection not established", @"SSL requested but not used title"), + [dbDocument parentWindow], + NSLocalizedString(@"You requested that the connection should be established using SSL, but MySQL made the connection without SSL.\n\nThis may be because the server does not support SSL connections, or has SSL disabled; or insufficient details were supplied to establish an SSL connection.\n\nThis connection is not encrypted.", @"SSL connection requested but not established error detail") + ); } else { #ifndef SP_CODA diff --git a/Source/SPCopyTable.m b/Source/SPCopyTable.m index 06d94f0b..8a8c8fa9 100644 --- a/Source/SPCopyTable.m +++ b/Source/SPCopyTable.m @@ -1364,8 +1364,11 @@ static const NSInteger kBlobAsImageFile = 4; if(inputFileError != nil) { NSString *errorMessage = [inputFileError localizedDescription]; - SPBeginAlertSheet(NSLocalizedString(@"Bundle Error", @"bundle error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil, - [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), [cmdData objectForKey:@"name"], errorMessage]); + SPOnewayAlertSheet( + NSLocalizedString(@"Bundle Error", @"bundle error"), + [self window], + [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), [cmdData objectForKey:@"name"], errorMessage] + ); if (cmdData) [cmdData release]; return; } @@ -1424,8 +1427,11 @@ static const NSInteger kBlobAsImageFile = 4; if(inputFileError != nil) { NSString *errorMessage = [inputFileError localizedDescription]; - SPBeginAlertSheet(NSLocalizedString(@"Bundle Error", @"bundle error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil, - [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), [cmdData objectForKey:@"name"], errorMessage]); + SPOnewayAlertSheet( + NSLocalizedString(@"Bundle Error", @"bundle error"), + [self window], + [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), [cmdData objectForKey:@"name"], errorMessage] + ); if (cmdData) [cmdData release]; return; } @@ -1516,8 +1522,11 @@ static const NSInteger kBlobAsImageFile = 4; } } else if([err code] != 9) { // Suppress an error message if command was killed NSString *errorMessage = [err localizedDescription]; - SPBeginAlertSheet(NSLocalizedString(@"BASH Error", @"bash error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil, - [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), [cmdData objectForKey:@"name"], errorMessage]); + SPOnewayAlertSheet( + NSLocalizedString(@"BASH Error", @"bash error"), + [self window], + [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), [cmdData objectForKey:@"name"], errorMessage] + ); } } diff --git a/Source/SPCustomQuery.m b/Source/SPCustomQuery.m index c3d76d54..7b446237 100644 --- a/Source/SPCustomQuery.m +++ b/Source/SPCustomQuery.m @@ -244,8 +244,11 @@ // This should never evaluate to true as we are now performing menu validation, meaning the 'Save Query to Favorites' menu item will // only be enabled if the query text view has at least one character present. if ([[textView string] isEqualToString:@""]) { - SPBeginAlertSheet(NSLocalizedString(@"Empty query", @"empty query message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - NSLocalizedString(@"Cannot save an empty query.", @"empty query informative message")); + SPOnewayAlertSheet( + NSLocalizedString(@"Empty query", @"empty query message"), + [tableDocumentInstance parentWindow], + NSLocalizedString(@"Cannot save an empty query.", @"empty query informative message") + ); return; } @@ -262,8 +265,11 @@ // This should never evaluate to true as we are now performing menu validation, meaning the 'Save Query to Favorites' menu item will // only be enabled if the query text view has at least one character present. if ([[textView string] isEqualToString:@""]) { - SPBeginAlertSheet(NSLocalizedString(@"Empty query", @"empty query message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - NSLocalizedString(@"Cannot save an empty query.", @"empty query informative message")); + SPOnewayAlertSheet( + NSLocalizedString(@"Empty query", @"empty query message"), + [tableDocumentInstance parentWindow], + NSLocalizedString(@"Cannot save an empty query.", @"empty query informative message") + ); return; } @@ -2016,9 +2022,11 @@ // Check for errors while UPDATE if ([mySQLConnection queryErrored]) { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), NSLocalizedString(@"Cancel", @"cancel button"), nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"Couldn't write field.\nMySQL said: %@", @"message of panel when error while updating field to db"), [mySQLConnection lastErrorMessage]]); - + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [tableDocumentInstance parentWindow], + [NSString stringWithFormat:NSLocalizedString(@"Couldn't write field.\nMySQL said: %@", @"message of panel when error while updating field to db"), [mySQLConnection lastErrorMessage]] + ); return; } @@ -2026,8 +2034,11 @@ if ( ![mySQLConnection rowsAffectedByLastQuery] ) { #ifndef SP_CODA if ( [prefs boolForKey:SPShowNoAffectedRowsError] ) { - SPBeginAlertSheet(NSLocalizedString(@"Warning", @"warning"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - NSLocalizedString(@"The row was not written to the MySQL database. You probably haven't changed anything.\nReload the table to be sure that the row exists and use a primary key for your table.\n(This error can be turned off in the preferences.)", @"message of panel when no rows have been affected after writing to the db")); + SPOnewayAlertSheet( + NSLocalizedString(@"Warning", @"warning"), + [tableDocumentInstance parentWindow], + NSLocalizedString(@"The row was not written to the MySQL database. You probably haven't changed anything.\nReload the table to be sure that the row exists and use a primary key for your table.\n(This error can be turned off in the preferences.)", @"message of panel when no rows have been affected after writing to the db") + ); } else { NSBeep(); } @@ -2049,10 +2060,11 @@ } #endif } else { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"Updating field content failed. Couldn't identify field origin unambiguously (%1$ld matches). It's very likely that while editing this field of table `%2$@` was changed.", @"message of panel when error while updating field to db after enabling it"), - (numberOfPossibleUpdateRows<1)?0:numberOfPossibleUpdateRows, [columnDefinition objectForKey:@"org_table"]]); - + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [tableDocumentInstance parentWindow], + [NSString stringWithFormat:NSLocalizedString(@"Updating field content failed. Couldn't identify field origin unambiguously (%1$ld matches). It's very likely that while editing this field of table `%2$@` was changed.", @"message of panel when error while updating field to db after enabling it"), (numberOfPossibleUpdateRows<1)?0:numberOfPossibleUpdateRows, [columnDefinition objectForKey:@"org_table"]] + ); } } diff --git a/Source/SPDataImport.m b/Source/SPDataImport.m index 64f314a9..418b7970 100644 --- a/Source/SPDataImport.m +++ b/Source/SPDataImport.m @@ -381,10 +381,11 @@ // Open a filehandle for the SQL file sqlFileHandle = [SPFileHandle fileHandleForReadingAtPath:filename]; if (!sqlFileHandle) { - SPBeginAlertSheet(NSLocalizedString(@"Import Error", @"Import Error title"), - NSLocalizedString(@"OK", @"OK button"), - nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - NSLocalizedString(@"The SQL file you selected could not be found or read.", @"SQL file open error")); + SPOnewayAlertSheet( + NSLocalizedString(@"Import Error", @"Import Error title"), + [tableDocumentInstance parentWindow], + NSLocalizedString(@"The SQL file you selected could not be found or read.", @"SQL file open error") + ); if([filename hasPrefix:SPImportClipboardTempFileNamePrefix]) [[NSFileManager defaultManager] removeItemAtPath:filename error:nil]; return; @@ -448,10 +449,11 @@ [mySQLConnection queryString:[NSString stringWithFormat:@"SET NAMES '%@'", connectionEncodingToRestore]]; } [self closeAndStopProgressSheet]; - SPBeginAlertSheet(SP_FILE_READ_ERROR_STRING, - NSLocalizedString(@"OK", @"OK button"), - nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occurred when reading the file.\n\nOnly %ld queries were executed.\n\n(%@)", @"SQL read error, including detail from system"), (long)queriesPerformed, [exception reason]]); + SPOnewayAlertSheet( + SP_FILE_READ_ERROR_STRING, + [tableDocumentInstance parentWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occurred when reading the file.\n\nOnly %ld queries were executed.\n\n(%@)", @"SQL read error, including detail from system"), (long)queriesPerformed, [exception reason]] + ); [sqlParser release]; [sqlDataBuffer release]; [importPool drain]; @@ -498,10 +500,11 @@ } else { displayEncoding = [NSString localizedNameOfStringEncoding:sqlEncoding]; } - SPBeginAlertSheet(SP_FILE_READ_ERROR_STRING, - NSLocalizedString(@"OK", @"OK button"), - nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occurred when reading the file, as it could not be read in the encoding you selected (%@).\n\nOnly %ld queries were executed.", @"SQL encoding read error"), displayEncoding, (long)queriesPerformed]); + SPOnewayAlertSheet( + SP_FILE_READ_ERROR_STRING, + [tableDocumentInstance parentWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occurred when reading the file, as it could not be read in the encoding you selected (%@).\n\nOnly %ld queries were executed.", @"SQL encoding read error"), displayEncoding, (long)queriesPerformed] + ); [sqlParser release]; [sqlDataBuffer release]; [importPool drain]; @@ -740,10 +743,11 @@ csvFileHandle = [SPFileHandle fileHandleForReadingAtPath:filename]; if (!csvFileHandle) { - SPBeginAlertSheet(NSLocalizedString(@"Import Error", @"Import Error title"), - NSLocalizedString(@"OK", @"OK button"), - nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - NSLocalizedString(@"The CSV file you selected could not be found or read.", @"CSV file open error")); + SPOnewayAlertSheet( + NSLocalizedString(@"Import Error", @"Import Error title"), + [tableDocumentInstance parentWindow], + NSLocalizedString(@"The CSV file you selected could not be found or read.", @"CSV file open error") + ); if([filename hasPrefix:SPImportClipboardTempFileNamePrefix]) [[NSFileManager defaultManager] removeItemAtPath:filename error:nil]; return; @@ -822,10 +826,11 @@ // Report file read errors, and bail @catch (NSException *exception) { [self closeAndStopProgressSheet]; - SPBeginAlertSheet(SP_FILE_READ_ERROR_STRING, - NSLocalizedString(@"OK", @"OK button"), - nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occurred when reading the file.\n\nOnly %ld rows were imported.\n\n(%@)", @"CSV read error, including detail string from system"), (long)rowsImported, [exception reason]]); + SPOnewayAlertSheet( + SP_FILE_READ_ERROR_STRING, + [tableDocumentInstance parentWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occurred when reading the file.\n\nOnly %ld rows were imported.\n\n(%@)", @"CSV read error, including detail string from system"), (long)rowsImported, [exception reason]] + ); [csvParser release]; [csvDataBuffer release]; [parsedRows release]; @@ -871,10 +876,11 @@ } else { displayEncoding = [NSString localizedNameOfStringEncoding:csvEncoding]; } - SPBeginAlertSheet(SP_FILE_READ_ERROR_STRING, - NSLocalizedString(@"OK", @"OK button"), - nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occurred when reading the file, as it could not be read using the encoding you selected (%@).\n\nOnly %ld rows were imported.", @"CSV encoding read error"), displayEncoding, (long)rowsImported]); + SPOnewayAlertSheet( + SP_FILE_READ_ERROR_STRING, + [tableDocumentInstance parentWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occurred when reading the file, as it could not be read using the encoding you selected (%@).\n\nOnly %ld rows were imported.", @"CSV encoding read error"), displayEncoding, (long)rowsImported] + ); [csvParser release]; [csvDataBuffer release]; [parsedRows release]; @@ -1217,26 +1223,22 @@ // Ensure data was provided, or alert than an import error occurred and return false. if (![importData count]) { [self closeAndStopProgressSheet]; - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), - NSLocalizedString(@"OK", @"OK button"), - nil, nil, - [tableDocumentInstance parentWindow], self, - nil, nil, - NSLocalizedString(@"Could not parse file as CSV", @"Error when we can't parse/split file as CSV") - ); + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [tableDocumentInstance parentWindow], + NSLocalizedString(@"Could not parse file as CSV", @"Error when we can't parse/split file as CSV") + ); return YES; } // Sanity check the first row of the CSV to prevent hang loops caused by wrong line ending entry if ([[importData objectAtIndex:0] count] > 512) { [self closeAndStopProgressSheet]; - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), - NSLocalizedString(@"OK", @"OK button"), - nil, nil, - [tableDocumentInstance parentWindow], self, - nil, nil, - NSLocalizedString(@"The CSV was read as containing more than 512 columns, more than the maximum columns permitted for speed reasons by Sequel Pro.\n\nThis usually happens due to errors reading the CSV; please double-check the CSV to be imported and the line endings and escape characters at the bottom of the CSV selection dialog.", @"Error when CSV appears to have too many columns to import, probably due to line ending mismatch") - ); + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [tableDocumentInstance parentWindow], + NSLocalizedString(@"The CSV was read as containing more than 512 columns, more than the maximum columns permitted for speed reasons by Sequel Pro.\n\nThis usually happens due to errors reading the CSV; please double-check the CSV to be imported and the line endings and escape characters at the bottom of the CSV selection dialog.", @"Error when CSV appears to have too many columns to import, probably due to line ending mismatch") + ); return NO; } fieldMappingImportArrayIsPreview = dataIsPreviewData; diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index 464325d4..bd258662 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -1086,12 +1086,10 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; * Show Error sheet (can be called from inside of a endSheet selector) * via [self performSelector:@selector(showErrorSheetWithTitle:) withObject: afterDelay:] */ --(void)showErrorSheetWith:(id)error +-(void)showErrorSheetWith:(NSArray *)error { // error := first object is the title , second the message, only one button OK - SPBeginAlertSheet([error objectAtIndex:0], NSLocalizedString(@"OK", @"OK button"), - nil, nil, parentWindow, self, nil, nil, - [error objectAtIndex:1]); + SPOnewayAlertSheet([error objectAtIndex:0], parentWindow, [error objectAtIndex:1]); } #endif @@ -2540,10 +2538,18 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; if (![mySQLConnection queryErrored]) { //flushed privileges without errors - SPBeginAlertSheet(NSLocalizedString(@"Flushed Privileges", @"title of panel when successfully flushed privs"), NSLocalizedString(@"OK", @"OK button"), nil, nil, parentWindow, self, nil, nil, NSLocalizedString(@"Successfully flushed privileges.", @"message of panel when successfully flushed privs")); + SPOnewayAlertSheet( + NSLocalizedString(@"Flushed Privileges", @"title of panel when successfully flushed privs"), + parentWindow, + NSLocalizedString(@"Successfully flushed privileges.", @"message of panel when successfully flushed privs") + ); } else { //error while flushing privileges - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, parentWindow, self, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"Couldn't flush privileges.\nMySQL said: %@", @"message of panel when flushing privs failed"), [mySQLConnection lastErrorMessage]]); + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + parentWindow, + [NSString stringWithFormat:NSLocalizedString(@"Couldn't flush privileges.\nMySQL said: %@", @"message of panel when flushing privs failed"), [mySQLConnection lastErrorMessage]] + ); } } @@ -5236,8 +5242,11 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; // Authenticate command if(![docProcessID isEqualToString:[commandDict objectForKey:@"id"]]) { - SPBeginAlertSheet(NSLocalizedString(@"Remote Error", @"remote error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self parentWindow], self, nil, nil, - NSLocalizedString(@"URL scheme command couldn't authenticated", @"URL scheme command couldn't authenticated")); + SPOnewayAlertSheet( + NSLocalizedString(@"Remote Error", @"remote error"), + [self parentWindow], + NSLocalizedString(@"URL scheme command couldn't authenticated", @"URL scheme command couldn't authenticated") + ); return; } @@ -5422,8 +5431,11 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; if ( ![queryResult numberOfRows] ) { //error while getting table structure - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"Couldn't get create syntax.\nMySQL said: %@", @"message of panel when table information cannot be retrieved"), [mySQLConnection lastErrorMessage]]); + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [self parentWindow], + [NSString stringWithFormat:NSLocalizedString(@"Couldn't get create syntax.\nMySQL said: %@", @"message of panel when table information cannot be retrieved"), [mySQLConnection lastErrorMessage]] + ); status = @"1"; @@ -5464,8 +5476,11 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; BOOL succeed = [status writeToFile:statusFileName atomically:YES encoding:NSUTF8StringEncoding error:nil]; if(!succeed) { NSBeep(); - SPBeginAlertSheet(NSLocalizedString(@"BASH Error", @"bash error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self parentWindow], self, nil, nil, - NSLocalizedString(@"Status file for sequelpro url scheme command couldn't be written!", @"status file for sequelpro url scheme command couldn't be written error message")); + SPOnewayAlertSheet( + NSLocalizedString(@"BASH Error", @"bash error"), + [self parentWindow], + NSLocalizedString(@"Status file for sequelpro url scheme command couldn't be written!", @"status file for sequelpro url scheme command couldn't be written error message") + ); } } @@ -5637,16 +5652,20 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; BOOL succeed = [status writeToFile:statusFileName atomically:YES encoding:NSUTF8StringEncoding error:nil]; if(!succeed) { NSBeep(); - SPBeginAlertSheet(NSLocalizedString(@"BASH Error", @"bash error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self parentWindow], self, nil, nil, - NSLocalizedString(@"Status file for sequelpro url scheme command couldn't be written!", @"status file for sequelpro url scheme command couldn't be written error message")); + SPOnewayAlertSheet( + NSLocalizedString(@"BASH Error", @"bash error"), + [self parentWindow], + NSLocalizedString(@"Status file for sequelpro url scheme command couldn't be written!", @"status file for sequelpro url scheme command couldn't be written error message") + ); } return; } - SPBeginAlertSheet(NSLocalizedString(@"Remote Error", @"remote error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"URL scheme command “%@” unsupported", @"URL scheme command “%@” unsupported"), command]); - - + SPOnewayAlertSheet( + NSLocalizedString(@"Remote Error", @"remote error"), + [self parentWindow], + [NSString stringWithFormat:NSLocalizedString(@"URL scheme command “%@” unsupported", @"URL scheme command “%@” unsupported"), command] + ); } - (void)registerActivity:(NSDictionary*)commandDict @@ -5950,7 +5969,7 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; - (void)_copyDatabase { if ([[databaseCopyNameField stringValue] isEqualToString:@""]) { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, parentWindow, self, nil, nil, NSLocalizedString(@"Database must have a name.", @"message of panel when no db name is given")); + SPOnewayAlertSheet(NSLocalizedString(@"Error", @"error"), parentWindow, NSLocalizedString(@"Database must have a name.", @"message of panel when no db name is given")); return; } @@ -5965,9 +5984,11 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; [self selectDatabase:[databaseCopyNameField stringValue] item:nil]; } else { - SPBeginAlertSheet(NSLocalizedString(@"Unable to copy database", @"unable to copy database message"), - NSLocalizedString(@"OK", @"OK button"), nil, nil, parentWindow, self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to copy the database '%@' to '%@'.", @"unable to copy database message informative message"), [self database], [databaseCopyNameField stringValue]]); + SPOnewayAlertSheet( + NSLocalizedString(@"Unable to copy database", @"unable to copy database message"), + parentWindow, + [NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to copy the database '%@' to '%@'.", @"unable to copy database message informative message"), [self database], [databaseCopyNameField stringValue]] + ); } [dbActionCopy release]; @@ -5982,7 +6003,7 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; NSString *newDatabaseName = [databaseRenameNameField stringValue]; if ([newDatabaseName isEqualToString:@""]) { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, parentWindow, self, nil, nil, NSLocalizedString(@"Database must have a name.", @"message of panel when no db name is given")); + SPOnewayAlertSheet(NSLocalizedString(@"Error", @"error"), parentWindow, NSLocalizedString(@"Database must have a name.", @"message of panel when no db name is given")); return; } @@ -5997,9 +6018,11 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; [self selectDatabase:newDatabaseName item:nil]; } else { - SPBeginAlertSheet(NSLocalizedString(@"Unable to rename database", @"unable to rename database message"), - NSLocalizedString(@"OK", @"OK button"), nil, nil, parentWindow, self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to rename the database '%@' to '%@'.", @"unable to rename database message informative message"), [self database], newDatabaseName]); + SPOnewayAlertSheet( + NSLocalizedString(@"Unable to rename database", @"unable to rename database message"), + parentWindow, + [NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to rename the database '%@' to '%@'.", @"unable to rename database message informative message"), [self database], newDatabaseName] + ); } [dbActionRename release]; @@ -6025,7 +6048,7 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; // This check is not necessary anymore as the add database button is now only enabled if the name field // has a length greater than zero. We'll leave it in just in case. if ([[databaseNameField stringValue] isEqualToString:@""]) { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, parentWindow, self, nil, nil, NSLocalizedString(@"Database must have a name.", @"message of panel when no db name is given")); + SPOnewayAlertSheet(NSLocalizedString(@"Error", @"error"), parentWindow, NSLocalizedString(@"Database must have a name.", @"message of panel when no db name is given")); return; } @@ -6041,7 +6064,7 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; if (!res) { // An error occurred - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, parentWindow, self, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"Couldn't create database.\nMySQL said: %@", @"message of panel when creation of db failed"), [mySQLConnection lastErrorMessage]]); + SPOnewayAlertSheet(NSLocalizedString(@"Error", @"error"), parentWindow, [NSString stringWithFormat:NSLocalizedString(@"Couldn't create database.\nMySQL said: %@", @"message of panel when creation of db failed"), [mySQLConnection lastErrorMessage]]); return; } @@ -6079,7 +6102,7 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; if ([mySQLConnection queryErrored]) { // An error occurred - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, parentWindow, self, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"Couldn't alter database.\nMySQL said: %@", @"Alter Database : Query Failed ($1 = mysql error message)"), [mySQLConnection lastErrorMessage]]); + SPOnewayAlertSheet(NSLocalizedString(@"Error", @"error"), parentWindow, [NSString stringWithFormat:NSLocalizedString(@"Couldn't alter database.\nMySQL said: %@", @"Alter Database : Query Failed ($1 = mysql error message)"), [mySQLConnection lastErrorMessage]]); return; } @@ -6178,10 +6201,8 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; SPOnewayAlertSheet( NSLocalizedString(@"Error", @"error"), - nil, 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], - NSWarningAlertStyle + [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] ); } diff --git a/Source/SPExportController.m b/Source/SPExportController.m index bbbec9bd..95fbc5b6 100644 --- a/Source/SPExportController.m +++ b/Source/SPExportController.m @@ -861,7 +861,7 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; SPBeginAlertSheet(NSLocalizedString(@"The list of tables has changed", @"table list change alert message"), NSLocalizedString(@"Continue", @"continue button"), NSLocalizedString(@"Cancel", @"cancel button"), nil, [tableDocumentInstance parentWindow], self, - @selector(tableListChangedAlertDidEnd:returnCode:contextInfo:), nil, + @selector(tableListChangedAlertDidEnd:returnCode:contextInfo:), NULL, [NSString stringWithFormat:NSLocalizedString(@"The number of tables in this database has changed since the export dialog was opened. There are now %d additional table(s), most likely added by an external application.\n\nHow would you like to proceed?", @"table list change alert informative message"), diff]); } else { diff --git a/Source/SPExtendedTableInfo.m b/Source/SPExtendedTableInfo.m index d3b332d0..92b845e9 100644 --- a/Source/SPExtendedTableInfo.m +++ b/Source/SPExtendedTableInfo.m @@ -171,9 +171,11 @@ static NSString *SPMySQLCommentField = @"Comment"; else { [sender selectItemWithTitle:currentEncoding]; - SPBeginAlertSheet(NSLocalizedString(@"Error changing table encoding", @"error changing table encoding message"), - NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table encoding to '%@'.\n\nMySQL said: %@", @"error changing table encoding informative message"), newEncoding, [connection lastErrorMessage]]); + SPOnewayAlertSheet( + NSLocalizedString(@"Error changing table encoding", @"error changing table encoding message"), + [NSApp mainWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table encoding to '%@'.\n\nMySQL said: %@", @"error changing table encoding informative message"), newEncoding, [connection lastErrorMessage]] + ); } } @@ -198,9 +200,11 @@ static NSString *SPMySQLCommentField = @"Comment"; else { [sender selectItemWithTitle:currentCollation]; - SPBeginAlertSheet(NSLocalizedString(@"Error changing table collation", @"error changing table collation message"), - NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table collation to '%@'.\n\nMySQL said: %@", @"error changing table collation informative message"), newCollation, [connection lastErrorMessage]]); + SPOnewayAlertSheet( + NSLocalizedString(@"Error changing table collation", @"error changing table collation message"), + [NSApp mainWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table collation to '%@'.\n\nMySQL said: %@", @"error changing table collation informative message"), newCollation, [connection lastErrorMessage]] + ); } } @@ -546,9 +550,11 @@ static NSString *SPMySQLCommentField = @"Comment"; [self reloadTable:self]; } else { - SPBeginAlertSheet(NSLocalizedString(@"Error changing table comment", @"error changing table comment message"), - NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table's comment to '%@'.\n\nMySQL said: %@", @"error changing table comment informative message"), newComment, [connection lastErrorMessage]]); + SPOnewayAlertSheet( + NSLocalizedString(@"Error changing table comment", @"error changing table comment message"), + [NSApp mainWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table's comment to '%@'.\n\nMySQL said: %@", @"error changing table comment informative message"), newComment, [connection lastErrorMessage]] + ); } } } @@ -650,9 +656,11 @@ static NSString *SPMySQLCommentField = @"Comment"; [tableTypePopUpButton selectItemWithTitle:currentType]; - SPBeginAlertSheet(NSLocalizedString(@"Error changing table type", @"error changing table type message"), - NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table type to '%@'.\n\nMySQL said: %@", @"error changing table type informative message"), newType, [connection lastErrorMessage]]); + SPOnewayAlertSheet( + NSLocalizedString(@"Error changing table type", @"error changing table type message"), + [NSApp mainWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table type to '%@'.\n\nMySQL said: %@", @"error changing table type informative message"), newType, [connection lastErrorMessage]] + ); return; } diff --git a/Source/SPIndexesController.m b/Source/SPIndexesController.m index 0eca239c..a885ff01 100644 --- a/Source/SPIndexesController.m +++ b/Source/SPIndexesController.m @@ -909,8 +909,11 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; // Check for errors, but only if the query wasn't cancelled if ([connection queryErrored] && ![connection lastQueryWasCancelled]) { - SPBeginAlertSheet(NSLocalizedString(@"Unable to add index", @"add index error message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [dbDocument parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to add the index.\n\nMySQL said: %@", @"add index error informative message"), [connection lastErrorMessage]]); + 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]; diff --git a/Source/SPKeychain.m b/Source/SPKeychain.m index 48283359..066f4011 100644 --- a/Source/SPKeychain.m +++ b/Source/SPKeychain.m @@ -115,10 +115,11 @@ if (status != noErr) { NSLog(@"Error (%i) while trying to add password for name: %@ account: %@", (int)status, name, account); - SPBeginAlertSheet(NSLocalizedString(@"Error adding password to Keychain", @"error adding password to keychain message"), - NSLocalizedString(@"OK", @"OK button"), - nil, nil, [NSApp mainWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to add the password to your Keychain. Repairing your Keychain might resolve this, but if it doesn't please report it to the Sequel Pro team, supplying the error code %i.", @"error adding password to keychain informative message"), status]); + SPOnewayAlertSheet( + NSLocalizedString(@"Error adding password to Keychain", @"error adding password to keychain message"), + [NSApp mainWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to add the password to your Keychain. Repairing your Keychain might resolve this, but if it doesn't please report it to the Sequel Pro team, supplying the error code %i.", @"error adding password to keychain informative message"), status] + ); } } } @@ -297,10 +298,11 @@ if (status != noErr) { NSLog(@"Error (%i) while trying to find keychain item to edit for name: %@ account: %@", (int)status, name, account); - SPBeginAlertSheet(NSLocalizedString(@"Error retrieving Keychain item to edit", @"error finding keychain item to edit message"), - NSLocalizedString(@"OK", @"OK button"), - nil, nil, [NSApp mainWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to retrieve the Keychain item you're trying to edit. Repairing your Keychain might resolve this, but if it doesn't please report it to the Sequel Pro team, supplying the error code %i.", @"error finding keychain item to edit informative message"), status]); + SPOnewayAlertSheet( + NSLocalizedString(@"Error retrieving Keychain item to edit", @"error finding keychain item to edit message"), + [NSApp mainWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to retrieve the Keychain item you're trying to edit. Repairing your Keychain might resolve this, but if it doesn't please report it to the Sequel Pro team, supplying the error code %i.", @"error finding keychain item to edit informative message"), status] + ); return; } @@ -329,10 +331,11 @@ NSLog(@"Error (%i) while updating keychain item for name: %@ account: %@", (int)status, name, account); - SPBeginAlertSheet(NSLocalizedString(@"Error updating Keychain item", @"error updating keychain item message"), - NSLocalizedString(@"OK", @"OK button"), - nil, nil, [NSApp mainWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to update the Keychain item. Repairing your Keychain might resolve this, but if it doesn't please report it to the Sequel Pro team, supplying the error code %i.", @"error updating keychain item informative message"), status]); + SPOnewayAlertSheet( + NSLocalizedString(@"Error updating Keychain item", @"error updating keychain item message"), + [NSApp mainWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to update the Keychain item. Repairing your Keychain might resolve this, but if it doesn't please report it to the Sequel Pro team, supplying the error code %i.", @"error updating keychain item informative message"), status] + ); } } diff --git a/Source/SPProcessListController.m b/Source/SPProcessListController.m index e61dfc30..96d8f3a2 100644 --- a/Source/SPProcessListController.m +++ b/Source/SPProcessListController.m @@ -676,8 +676,11 @@ static NSString *SPTableViewIDColumnIdentifier = @"Id"; // Check for errors if ([connection queryErrored]) { - SPBeginAlertSheet(NSLocalizedString(@"Unable to kill query", @"error killing query message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occured while attempting to kill the query associated with connection %lld.\n\nMySQL said: %@", @"error killing query informative message"), processId, [connection lastErrorMessage]]); + SPOnewayAlertSheet( + NSLocalizedString(@"Unable to kill query", @"error killing query message"), + [self window], + [NSString stringWithFormat:NSLocalizedString(@"An error occured while attempting to kill the query associated with connection %lld.\n\nMySQL said: %@", @"error killing query informative message"), processId, [connection lastErrorMessage]] + ); } // Refresh the process list @@ -694,8 +697,11 @@ static NSString *SPTableViewIDColumnIdentifier = @"Id"; // Check for errors if ([connection queryErrored]) { - SPBeginAlertSheet(NSLocalizedString(@"Unable to kill connection", @"error killing connection message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occured while attempting to kill connection %lld.\n\nMySQL said: %@", @"error killing query informative message"), processId, [connection lastErrorMessage]]); + SPOnewayAlertSheet( + NSLocalizedString(@"Unable to kill connection", @"error killing connection message"), + [self window], + [NSString stringWithFormat:NSLocalizedString(@"An error occured while attempting to kill connection %lld.\n\nMySQL said: %@", @"error killing query informative message"), processId, [connection lastErrorMessage]] + ); } // Refresh the process list diff --git a/Source/SPTableContent.h b/Source/SPTableContent.h index 15266d4e..9ce2299a 100644 --- a/Source/SPTableContent.h +++ b/Source/SPTableContent.h @@ -283,7 +283,7 @@ - (void)autosizeColumns; - (BOOL)saveRowOnDeselect; - (void)sortTableTaskWithColumn:(NSTableColumn *)tableColumn; -- (void)showErrorSheetWith:(id)error; +- (void)showErrorSheetWith:(NSArray *)error; - (void)processFieldEditorResult:(id)data contextInfo:(NSDictionary*)contextInfo; - (void)saveViewCellValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSUInteger)rowIndex; diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m index 618f6c5d..2caa80da 100644 --- a/Source/SPTableContent.m +++ b/Source/SPTableContent.m @@ -991,7 +991,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper else errorDetail = [NSString stringWithFormat:NSLocalizedString(@"The table data couldn't be loaded.\n\nMySQL said: %@", @"message of panel when loading of table failed"), [mySQLConnection lastErrorMessage]]; - SPOnewayAlertSheet(NSLocalizedString(@"Error", @"error"), nil, [tableDocumentInstance parentWindow], errorDetail, NSWarningAlertStyle); + SPOnewayAlertSheet(NSLocalizedString(@"Error", @"error"), [tableDocumentInstance parentWindow], errorDetail); } #ifndef SP_CODA // Filter task came from filter table @@ -1124,8 +1124,11 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper if(![filter objectForKey:@"Clause"] || ![(NSString *)[filter objectForKey:@"Clause"] length]) { - SPBeginAlertSheet(NSLocalizedString(@"Warning", @"warning"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - NSLocalizedString(@"Content Filter clause is empty.", @"content filter clause is empty tooltip.")); + SPOnewayAlertSheet( + NSLocalizedString(@"Warning", @"warning"), + [tableDocumentInstance parentWindow], + NSLocalizedString(@"Content Filter clause is empty.", @"content filter clause is empty tooltip.") + ); return nil; } @@ -1575,8 +1578,11 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper [self loadTableValues]; if ([mySQLConnection queryErrored] && ![mySQLConnection lastQueryWasCancelled]) { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"Couldn't sort table. MySQL said: %@", @"message of panel when sorting of table failed"), [mySQLConnection lastErrorMessage]]); + 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]; [sortPool drain]; @@ -1866,7 +1872,11 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper if (![tableContentView numberOfSelectedRows]) return; if ([tableContentView numberOfSelectedRows] > 1) { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, NSLocalizedString(@"You can only copy single rows.", @"message of panel when trying to copy multiple rows")); + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [tableDocumentInstance parentWindow], + NSLocalizedString(@"You can only copy single rows.", @"message of panel when trying to copy multiple rows") + ); return; } @@ -2848,8 +2858,11 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper if ( ![mySQLConnection rowsAffectedByLastQuery] && ![mySQLConnection queryErrored] ) { #ifndef SP_CODA if ( [prefs boolForKey:SPShowNoAffectedRowsError] ) { - SPBeginAlertSheet(NSLocalizedString(@"Warning", @"warning"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - NSLocalizedString(@"The row was not written to the MySQL database. You probably haven't changed anything.\nReload the table to be sure that the row exists and use a primary key for your table.\n(This error can be turned off in the preferences.)", @"message of panel when no rows have been affected after writing to the db")); + SPOnewayAlertSheet( + NSLocalizedString(@"Warning", @"warning"), + [tableDocumentInstance parentWindow], + NSLocalizedString(@"The row was not written to the MySQL database. You probably haven't changed anything.\nReload the table to be sure that the row exists and use a primary key for your table.\n(This error can be turned off in the preferences.)", @"message of panel when no rows have been affected after writing to the db") + ); } else { NSBeep(); } @@ -2930,7 +2943,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper // Report errors which have occurred } else { - SPBeginAlertSheet(NSLocalizedString(@"Unable to write row", @"Unable to write row error"), NSLocalizedString(@"Edit row", @"Edit row button"), NSLocalizedString(@"Discard changes", @"discard changes button"), nil, [tableDocumentInstance parentWindow], self, @selector(addRowErrorSheetDidEnd:returnCode:contextInfo:), nil, + SPBeginAlertSheet(NSLocalizedString(@"Unable to write row", @"Unable to write row error"), NSLocalizedString(@"Edit row", @"Edit row button"), NSLocalizedString(@"Discard changes", @"discard changes button"), nil, [tableDocumentInstance parentWindow], self, @selector(addRowErrorSheetDidEnd:returnCode:contextInfo:), NULL, [NSString stringWithFormat:NSLocalizedString(@"MySQL said:\n\n%@", @"message of panel when error while adding row to db"), [mySQLConnection lastErrorMessage]]); return NO; } @@ -3070,8 +3083,11 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper // the right values to use in the WHERE statement. Throw an error if this is the case. #ifndef SP_CODA if ( [prefs boolForKey:SPLoadBlobsAsNeeded] && [self tableContainsBlobOrTextColumns] ) { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - NSLocalizedString(@"You can't hide blob and text fields when working with tables without index.", @"message of panel when trying to edit tables without index and with hidden blob/text fields")); + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [tableDocumentInstance parentWindow], + NSLocalizedString(@"You can't hide blob and text fields when working with tables without index.", @"message of panel when trying to edit tables without index and with hidden blob/text fields") + ); [keys removeAllObjects]; [tableContentView deselectAll:self]; return @""; @@ -3310,12 +3326,10 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper * Show Error sheet (can be called from inside of a endSheet selector) * via [self performSelector:@selector(showErrorSheetWithTitle:) withObject: afterDelay:] */ -- (void)showErrorSheetWith:(id)error +- (void)showErrorSheetWith:(NSArray *)error { // error := first object is the title , second the message, only one button OK - SPBeginAlertSheet([error objectAtIndex:0], NSLocalizedString(@"OK", @"OK button"), - nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - [error objectAtIndex:1]); + SPOnewayAlertSheet([error objectAtIndex:0], [tableDocumentInstance parentWindow], [error objectAtIndex:1]); } - (void)processFieldEditorResult:(id)data contextInfo:(NSDictionary*)contextInfo @@ -3435,8 +3449,11 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper // Check for errors while UPDATE if ([mySQLConnection queryErrored]) { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), NSLocalizedString(@"Cancel", @"cancel button"), nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"Couldn't write field.\nMySQL said: %@", @"message of panel when error while updating field to db"), [mySQLConnection lastErrorMessage]]); + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [tableDocumentInstance parentWindow], + [NSString stringWithFormat:NSLocalizedString(@"Couldn't write field.\nMySQL said: %@", @"message of panel when error while updating field to db"), [mySQLConnection lastErrorMessage]] + ); [tableDocumentInstance endTask]; [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; @@ -3448,8 +3465,11 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper if ( ![mySQLConnection rowsAffectedByLastQuery] ) { #ifndef SP_CODA if ( [prefs boolForKey:SPShowNoAffectedRowsError] ) { - SPBeginAlertSheet(NSLocalizedString(@"Warning", @"warning"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - NSLocalizedString(@"The row was not written to the MySQL database. You probably haven't changed anything.\nReload the table to be sure that the row exists and use a primary key for your table.\n(This error can be turned off in the preferences.)", @"message of panel when no rows have been affected after writing to the db")); + SPOnewayAlertSheet( + NSLocalizedString(@"Warning", @"warning"), + [tableDocumentInstance parentWindow], + NSLocalizedString(@"The row was not written to the MySQL database. You probably haven't changed anything.\nReload the table to be sure that the row exists and use a primary key for your table.\n(This error can be turned off in the preferences.)", @"message of panel when no rows have been affected after writing to the db") + ); } else { NSBeep(); } @@ -3460,9 +3480,11 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper } } else { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"Updating field content failed. Couldn't identify field origin unambiguously (%1$ld matches). It's very likely that while editing this field the table `%2$@` was changed by an other user.", @"message of panel when error while updating field to db after enabling it"), - (long)numberOfPossibleUpdateRows, tableForColumn]); + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [tableDocumentInstance parentWindow], + [NSString stringWithFormat:NSLocalizedString(@"Updating field content failed. Couldn't identify field origin unambiguously (%1$ld matches). It's very likely that while editing this field the table `%2$@` was changed by an other user.", @"message of panel when error while updating field to db after enabling it"),(long)numberOfPossibleUpdateRows, tableForColumn] + ); [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; [tableDocumentInstance endTask]; diff --git a/Source/SPTableContentDelegate.m b/Source/SPTableContentDelegate.m index 25ea9822..eee0fb0a 100644 --- a/Source/SPTableContentDelegate.m +++ b/Source/SPTableContentDelegate.m @@ -255,8 +255,11 @@ SPMySQLResult *tempResult = [mySQLConnection queryString:query]; if (![tempResult numberOfRows]) { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - NSLocalizedString(@"Couldn't load the row. Reload the table to be sure that the row exists and use a primary key for your table.", @"message of panel when loading of row failed")); + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [tableDocumentInstance parentWindow], + NSLocalizedString(@"Couldn't load the row. Reload the table to be sure that the row exists and use a primary key for your table.", @"message of panel when loading of row failed") + ); return NO; } diff --git a/Source/SPTableData.m b/Source/SPTableData.m index bf1aec20..a40225d0 100644 --- a/Source/SPTableData.m +++ b/Source/SPTableData.m @@ -482,10 +482,8 @@ SPOnewayAlertSheet( NSLocalizedString(@"Error retrieving table information", @"error retrieving table information message"), - nil, [NSApp mainWindow], - errorMessage, - NSWarningAlertStyle + errorMessage ); if (changeEncoding) [mySQLConnection restoreStoredEncoding]; @@ -508,10 +506,8 @@ if ([[syntaxResult objectAtIndex:1] isNSNull]) { SPOnewayAlertSheet( NSLocalizedString(@"Permission Denied", @"Permission Denied"), - nil, [NSApp mainWindow], - NSLocalizedString(@"The creation syntax could not be retrieved due to a permissions error.\n\nPlease check your user permissions with an administrator.", @"Create syntax permission denied detail"), - NSWarningAlertStyle + NSLocalizedString(@"The creation syntax could not be retrieved due to a permissions error.\n\nPlease check your user permissions with an administrator.", @"Create syntax permission denied detail") ); if (changeEncoding) [mySQLConnection restoreStoredEncoding]; @@ -582,7 +578,7 @@ ignoringQuotedStrings: NO]; if(fieldName == nil || [fieldName length] == 0) { NSBeep(); - SPOnewayAlertSheet( + SPOnewayAlertSheetWithStyle( NSLocalizedString(@"Error while parsing CREATE TABLE syntax",@"error while parsing CREATE TABLE syntax"), nil, nil, @@ -857,10 +853,8 @@ if ([mySQLConnection isConnected]) { SPOnewayAlertSheet( NSLocalizedString(@"Error", @"error"), - nil, [NSApp mainWindow], - [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving information.\nMySQL said: %@", @"message of panel when retrieving information failed"),[mySQLConnection lastErrorMessage]], - NSWarningAlertStyle + [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving information.\nMySQL said: %@", @"message of panel when retrieving information failed"),[mySQLConnection lastErrorMessage]] ); if (changeEncoding) [mySQLConnection restoreStoredEncoding]; } @@ -881,10 +875,8 @@ if ([syntaxString isNSNull]) { SPOnewayAlertSheet( NSLocalizedString(@"Permission Denied", @"Permission Denied"), - nil, [NSApp mainWindow], - NSLocalizedString(@"The creation syntax could not be retrieved due to a permissions error.\n\nPlease check your user permissions with an administrator.", @"Create syntax permission denied detail"), - NSWarningAlertStyle + NSLocalizedString(@"The creation syntax could not be retrieved due to a permissions error.\n\nPlease check your user permissions with an administrator.", @"Create syntax permission denied detail") ); if (changeEncoding) [mySQLConnection restoreStoredEncoding]; return nil; @@ -901,10 +893,8 @@ if ([mySQLConnection isConnected]) { SPOnewayAlertSheet( NSLocalizedString(@"Error", @"error"), - nil, [NSApp mainWindow], - [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving information.\nMySQL said: %@", @"message of panel when retrieving information failed"), [mySQLConnection lastErrorMessage]], - NSWarningAlertStyle + [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving information.\nMySQL said: %@", @"message of panel when retrieving information failed"), [mySQLConnection lastErrorMessage]] ); if (changeEncoding) [mySQLConnection restoreStoredEncoding]; } @@ -1013,10 +1003,8 @@ if ([mySQLConnection isConnected]) { SPOnewayAlertSheet( NSLocalizedString(@"Error", @"error"), - nil, [NSApp mainWindow], - [NSString stringWithFormat:NSLocalizedString(@"An error occured while retrieving status data.\nMySQL said: %@", @"message of panel when retrieving view information failed"), [mySQLConnection lastErrorMessage]], - NSWarningAlertStyle + [NSString stringWithFormat:NSLocalizedString(@"An error occured while retrieving status data.\nMySQL said: %@", @"message of panel when retrieving view information failed"), [mySQLConnection lastErrorMessage]] ); if (changeEncoding) [mySQLConnection restoreStoredEncoding]; } @@ -1101,10 +1089,8 @@ if ([mySQLConnection isConnected]) { SPOnewayAlertSheet( NSLocalizedString(@"Error retrieving trigger information", @"error retrieving trigger information message"), - nil, [NSApp mainWindow], - [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving the trigger information for table '%@'. Please try again.\n\nMySQL said: %@", @"error retrieving table information informative message"), [tableListInstance tableName], [mySQLConnection lastErrorMessage]], - NSWarningAlertStyle + [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving the trigger information for table '%@'. Please try again.\n\nMySQL said: %@", @"error retrieving table information informative message"), [tableListInstance tableName], [mySQLConnection lastErrorMessage]] ); if (triggers) SPClear(triggers); if (changeEncoding) [mySQLConnection restoreStoredEncoding]; diff --git a/Source/SPTableRelations.m b/Source/SPTableRelations.m index 134aa2fb..8a2a1568 100644 --- a/Source/SPTableRelations.m +++ b/Source/SPTableRelations.m @@ -526,10 +526,11 @@ static NSString *SPRelationOnDeleteKey = @"on_delete"; if ([connection queryErrored]) { - SPBeginAlertSheet(NSLocalizedString(@"Unable to delete relation", @"error deleting relation message"), - NSLocalizedString(@"OK", @"OK button"), - nil, nil, [NSApp mainWindow], nil, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"The selected relation couldn't be deleted.\n\nMySQL said: %@", @"error deleting relation informative message"), [connection lastErrorMessage]]); + SPOnewayAlertSheet( + NSLocalizedString(@"Unable to delete relation", @"error deleting relation message"), + [NSApp mainWindow], + [NSString stringWithFormat:NSLocalizedString(@"The selected relation couldn't be deleted.\n\nMySQL said: %@", @"error deleting relation informative message"), [connection lastErrorMessage]] + ); // Abort loop break; diff --git a/Source/SPTableStructure.m b/Source/SPTableStructure.m index 1e0c9f5e..35e4c0f1 100644 --- a/Source/SPTableStructure.m +++ b/Source/SPTableStructure.m @@ -630,11 +630,11 @@ static NSString *SPRemoveFieldAndForeignKey = @"SPRemoveFieldAndForeignKey"; [mySQLConnection queryString:[NSString stringWithFormat:@"ALTER TABLE %@ AUTO_INCREMENT = %llu", [selTable backtickQuotedString], [value unsignedLongLongValue]]]; if ([mySQLConnection queryErrored]) { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), - NSLocalizedString(@"OK", @"OK button"), - nil, nil, [NSApp mainWindow], nil, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to reset AUTO_INCREMENT of table '%@'.\n\nMySQL said: %@", @"error resetting auto_increment informative message"), - selTable, [mySQLConnection lastErrorMessage]]); + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [NSApp mainWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to reset AUTO_INCREMENT of table '%@'.\n\nMySQL said: %@", @"error resetting auto_increment informative message"),selTable, [mySQLConnection lastErrorMessage]] + ); } // reload data @@ -976,11 +976,11 @@ static NSString *SPRemoveFieldAndForeignKey = @"SPRemoveFieldAndForeignKey"; else { alertSheetOpened = YES; if([mySQLConnection lastErrorID] == 1146) { // If the current table doesn't exist anymore - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), - NSLocalizedString(@"OK", @"OK button"), - nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to alter table '%@'.\n\nMySQL said: %@", @"error while trying to alter table message"), - selectedTable, [mySQLConnection lastErrorMessage]]); + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [tableDocumentInstance parentWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to alter table '%@'.\n\nMySQL said: %@", @"error while trying to alter table message"),selectedTable, [mySQLConnection lastErrorMessage]] + ); isEditingRow = NO; isEditingNewRow = NO; @@ -1003,14 +1003,14 @@ static NSString *SPRemoveFieldAndForeignKey = @"SPRemoveFieldAndForeignKey"; if (isEditingNewRow) { SPBeginAlertSheet(NSLocalizedString(@"Error adding field", @"error adding field message"), NSLocalizedString(@"Edit row", @"Edit row button"), - NSLocalizedString(@"Discard changes", @"discard changes button"), nil, [tableDocumentInstance parentWindow], self, @selector(addRowErrorSheetDidEnd:returnCode:contextInfo:), nil, + NSLocalizedString(@"Discard changes", @"discard changes button"), nil, [tableDocumentInstance parentWindow], self, @selector(addRowErrorSheetDidEnd:returnCode:contextInfo:), NULL, [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to add the field '%@' via\n\n%@\n\nMySQL said: %@", @"error adding field informative message"), [theRow objectForKey:@"name"], queryString, [mySQLConnection lastErrorMessage]]); } else { SPBeginAlertSheet(NSLocalizedString(@"Error changing field", @"error changing field message"), NSLocalizedString(@"Edit row", @"Edit row button"), - NSLocalizedString(@"Discard changes", @"discard changes button"), nil, [tableDocumentInstance parentWindow], self, @selector(addRowErrorSheetDidEnd:returnCode:contextInfo:), nil, + NSLocalizedString(@"Discard changes", @"discard changes button"), nil, [tableDocumentInstance parentWindow], self, @selector(addRowErrorSheetDidEnd:returnCode:contextInfo:), NULL, [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the field '%@' via\n\n%@\n\nMySQL said: %@", @"error changing field informative message"), [theRow objectForKey:@"name"], queryString, [mySQLConnection lastErrorMessage]]); } @@ -1070,9 +1070,7 @@ static NSString *SPRemoveFieldAndForeignKey = @"SPRemoveFieldAndForeignKey"; } // Display the error sheet - SPBeginAlertSheet([errorDictionary objectForKey:@"title"], NSLocalizedString(@"OK", @"OK button"), - nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - [errorDictionary objectForKey:@"message"]); + SPOnewayAlertSheet([errorDictionary objectForKey:@"title"], [tableDocumentInstance parentWindow], [errorDictionary objectForKey:@"message"]); } /** diff --git a/Source/SPTableStructureDelegate.m b/Source/SPTableStructureDelegate.m index c027d339..3f2c54e6 100644 --- a/Source/SPTableStructureDelegate.m +++ b/Source/SPTableStructureDelegate.m @@ -401,8 +401,11 @@ [mySQLConnection queryString:queryString]; if ([mySQLConnection queryErrored]) { - SPBeginAlertSheet(NSLocalizedString(@"Error moving field", @"error moving field message"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to move the field.\n\nMySQL said: %@", @"error moving field informative message"), [mySQLConnection lastErrorMessage]]); + SPOnewayAlertSheet( + NSLocalizedString(@"Error moving field", @"error moving field message"), + [tableDocumentInstance parentWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to move the field.\n\nMySQL said: %@", @"error moving field informative message"), [mySQLConnection lastErrorMessage]] + ); } else { [tableDataInstance resetAllData]; diff --git a/Source/SPTableStructureLoading.m b/Source/SPTableStructureLoading.m index 5a1e8e10..f6377420 100644 --- a/Source/SPTableStructureLoading.m +++ b/Source/SPTableStructureLoading.m @@ -77,10 +77,11 @@ [[self onMainThread] setTableDetails:nil]; if ([mySQLConnection isConnected]) { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), - nil, nil, [NSApp mainWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving information.\nMySQL said: %@", @"message of panel when retrieving information failed"), - [mySQLConnection lastErrorMessage]]); + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [NSApp mainWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving information.\nMySQL said: %@", @"message of panel when retrieving information failed"), [mySQLConnection lastErrorMessage]] + ); } return; diff --git a/Source/SPTableTriggers.m b/Source/SPTableTriggers.m index 6f58ce7a..96d2fb40 100644 --- a/Source/SPTableTriggers.m +++ b/Source/SPTableTriggers.m @@ -213,7 +213,7 @@ static SPTriggerEventTag TagForEvent(NSString *mysql); if ([connection queryErrored]) { SPBeginAlertSheet(NSLocalizedString(@"Unable to delete trigger", @"error deleting trigger message"), NSLocalizedString(@"OK", @"OK button"), - nil, nil, [NSApp mainWindow], self, @selector(_reopenTriggerSheet:returnCode:contextInfo:), nil, + nil, nil, [NSApp mainWindow], self, @selector(_reopenTriggerSheet:returnCode:contextInfo:), NULL, [NSString stringWithFormat:NSLocalizedString(@"The selected trigger couldn't be deleted.\n\nMySQL said: %@", @"error deleting trigger informative message"), [connection lastErrorMessage]]); @@ -281,7 +281,7 @@ static SPTriggerEventTag TagForEvent(NSString *mysql); SPBeginAlertSheet(NSLocalizedString(@"Error creating trigger", @"error creating trigger message"), NSLocalizedString(@"OK", @"OK button"), - nil, nil, [NSApp mainWindow], self, @selector(_reopenTriggerSheet:returnCode:contextInfo:), nil, + nil, nil, [NSApp mainWindow], self, @selector(_reopenTriggerSheet:returnCode:contextInfo:), NULL, [NSString stringWithFormat:NSLocalizedString(@"The specified trigger was unable to be created.\n\nMySQL said: %@", @"error creating trigger informative message"), createTriggerError]); } @@ -425,11 +425,11 @@ static SPTriggerEventTag TagForEvent(NSString *mysql); if ([connection queryErrored]) { [[alert window] orderOut:self]; - SPBeginAlertSheet(NSLocalizedString(@"Unable to delete trigger", @"error deleting trigger message"), - NSLocalizedString(@"OK", @"OK button"), - 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 lastErrorMessage]]); - + SPOnewayAlertSheet( + NSLocalizedString(@"Unable to delete trigger", @"error deleting trigger message"), + [tableDocumentInstance parentWindow], + [NSString stringWithFormat:NSLocalizedString(@"The selected trigger couldn't be deleted.\n\nMySQL said: %@", @"error deleting trigger informative message"), [connection lastErrorMessage]] + ); // Abort loop break; } diff --git a/Source/SPTablesList.m b/Source/SPTablesList.m index 34a14082..76671530 100644 --- a/Source/SPTablesList.m +++ b/Source/SPTablesList.m @@ -1577,7 +1577,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; // Since we trimmed whitespace and checked for empty string, this means there is already a table with that name SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, - @selector(sheetDidEnd:returnCode:contextInfo:), nil, + @selector(sheetDidEnd:returnCode:contextInfo:), NULL, [NSString stringWithFormat: NSLocalizedString(@"The name '%@' is already used.", @"message when trying to rename a table/view/proc/etc to an already used name"), newTableName]); return; } @@ -1602,7 +1602,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; } } @catch (NSException * myException) { - SPBeginAlertSheet( NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, [myException reason]); + SPOnewayAlertSheet(NSLocalizedString(@"Error", @"error"), [tableDocumentInstance parentWindow], [myException reason]); } #ifndef SP_CODA @@ -2452,7 +2452,11 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; NSString *tableType = @""; if ([[copyTableNameField stringValue] isEqualToString:@""]) { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, NSLocalizedString(@"Table must have a name.", @"message of panel when no name is given for table")); + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [tableDocumentInstance parentWindow], + NSLocalizedString(@"Table must have a name.", @"message of panel when no name is given for table") + ); return; } @@ -2493,8 +2497,11 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; if ( ![queryResult numberOfRows] ) { //error while getting table structure - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"Couldn't get create syntax.\nMySQL said: %@", @"message of panel when table information cannot be retrieved"), [mySQLConnection lastErrorMessage]]); + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [tableDocumentInstance parentWindow], + [NSString stringWithFormat:NSLocalizedString(@"Couldn't get create syntax.\nMySQL said: %@", @"message of panel when table information cannot be retrieved"), [mySQLConnection lastErrorMessage]] + ); return; } @@ -2542,8 +2549,11 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; // Check for errors, only displaying if the connection hasn't been terminated if ([mySQLConnection queryErrored]) { if ([mySQLConnection isConnected]) { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occured while retrieving the create syntax for '%@'.\nMySQL said: %@", @"message of panel when create syntax cannot be retrieved"), selectedTableName, [mySQLConnection lastErrorMessage]]); + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [tableDocumentInstance parentWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occured while retrieving the create syntax for '%@'.\nMySQL said: %@", @"message of panel when create syntax cannot be retrieved"), selectedTableName, [mySQLConnection lastErrorMessage]] + ); } return; } @@ -2555,16 +2565,22 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [mySQLConnection queryString:[tableSyntax stringByReplacingOccurrencesOfRegex:[NSString stringWithFormat:@"(?<=%@ )(`[^`]+?`)", [tableType uppercaseString]] withString:[[copyTableNameField stringValue] backtickQuotedString]]]; if ([mySQLConnection queryErrored]) { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"Couldn't duplicate '%@'.\nMySQL said: %@", @"message of panel when an item cannot be renamed"), [copyTableNameField stringValue], [mySQLConnection lastErrorMessage]]); + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [tableDocumentInstance parentWindow], + [NSString stringWithFormat:NSLocalizedString(@"Couldn't duplicate '%@'.\nMySQL said: %@", @"message of panel when an item cannot be renamed"), [copyTableNameField stringValue], [mySQLConnection lastErrorMessage]] + ); } } if ([mySQLConnection queryErrored]) { //error while creating new table - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"Couldn't create '%@'.\nMySQL said: %@", @"message of panel when table cannot be created"), [copyTableNameField stringValue], [mySQLConnection lastErrorMessage]]); + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [tableDocumentInstance parentWindow], + [NSString stringWithFormat:NSLocalizedString(@"Couldn't create '%@'.\nMySQL said: %@", @"message of panel when table cannot be created"), [copyTableNameField stringValue], [mySQLConnection lastErrorMessage]] + ); return; } @@ -2577,17 +2593,11 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; ]]; if ([mySQLConnection queryErrored]) { - SPBeginAlertSheet( - NSLocalizedString(@"Warning", @"warning"), - NSLocalizedString(@"OK", @"OK button"), - nil, - nil, - [tableDocumentInstance parentWindow], - self, - nil, - nil, - NSLocalizedString(@"There have been errors while copying table content. Please control the new table.", @"message of panel when table content cannot be copied") - ); + SPOnewayAlertSheet( + NSLocalizedString(@"Warning", @"warning"), + [tableDocumentInstance parentWindow], + NSLocalizedString(@"There have been errors while copying table content. Please control the new table.", @"message of panel when table content cannot be copied") + ); } } diff --git a/Source/SPTextView.m b/Source/SPTextView.m index e37b5939..970869c1 100644 --- a/Source/SPTextView.m +++ b/Source/SPTextView.m @@ -1844,8 +1844,11 @@ static inline NSPoint SPPointOnLine(NSPoint a, NSPoint b, CGFloat t) { return NS [theHintString replaceCharactersInRange:tagRange withString:cmdResult]; } else if([err code] != 9) { // Suppress an error message if command was killed NSString *errorMessage = [err localizedDescription]; - SPBeginAlertSheet(NSLocalizedString(@"BASH Error", @"bash error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil, - [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), [theHintString substringWithRange:cmdRange], errorMessage]); + SPOnewayAlertSheet( + NSLocalizedString(@"BASH Error", @"bash error"), + [self window], + [NSString stringWithFormat:NSLocalizedString(@"Error for “%1$@”:\n%2$@", @"error for bash command ($1), $2=message"), [theHintString substringWithRange:cmdRange], errorMessage] + ); } } else { [theHintString replaceCharactersInRange:tagRange withString:@""]; diff --git a/Source/SPTextViewAdditions.m b/Source/SPTextViewAdditions.m index 56103a53..ef5c776e 100644 --- a/Source/SPTextViewAdditions.m +++ b/Source/SPTextViewAdditions.m @@ -653,8 +653,11 @@ if(inputFileError != nil) { NSString *errorMessage = [inputFileError localizedDescription]; - SPBeginAlertSheet(NSLocalizedString(@"Bundle Error", @"bundle error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil, - [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), [cmdData objectForKey:@"name"], errorMessage]); + SPOnewayAlertSheet( + NSLocalizedString(@"Bundle Error", @"bundle error"), + [self window], + [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), [cmdData objectForKey:@"name"], errorMessage] + ); if (cmdData) [cmdData release]; return; } @@ -772,8 +775,11 @@ } } else if([err code] != 9) { // Suppress an error message if command was killed NSString *errorMessage = [err localizedDescription]; - SPBeginAlertSheet(NSLocalizedString(@"BASH Error", @"bash error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil, - [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), [cmdData objectForKey:@"name"], errorMessage]); + SPOnewayAlertSheet( + NSLocalizedString(@"BASH Error", @"bash error"), + [self window], + [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), [cmdData objectForKey:@"name"], errorMessage] + ); } } diff --git a/Source/SPUserManager.m b/Source/SPUserManager.m index e3f611c3..9f68ede8 100644 --- a/Source/SPUserManager.m +++ b/Source/SPUserManager.m @@ -693,9 +693,11 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; if ([[parent valueForKey:@"children"] count] == 0) { - SPBeginAlertSheet(NSLocalizedString(@"Unable to remove host", @"error removing host message"), - NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil, - NSLocalizedString(@"This user doesn't seem to have any associated hosts and will be removed unless a host is added.", @"error removing host informative message")); + SPOnewayAlertSheet( + NSLocalizedString(@"Unable to remove host", @"error removing host message"), + [self window], + NSLocalizedString(@"This user doesn't seem to have any associated hosts and will be removed unless a host is added.", @"error removing host informative message") + ); } } @@ -1386,9 +1388,11 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; [errorsString appendFormat:@"%@\n", [[self connection] lastErrorMessage]]; } else { - SPBeginAlertSheet(NSLocalizedString(@"An error occurred", @"mysql error occurred message"), - NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occurred whilst trying to perform the operation.\n\nMySQL said: %@", @"mysql error occurred informative message"), [[self connection] lastErrorMessage]]); + SPOnewayAlertSheet( + NSLocalizedString(@"An error occurred", @"mysql error occurred message"), + [self window], + [NSString stringWithFormat:NSLocalizedString(@"An error occurred whilst trying to perform the operation.\n\nMySQL said: %@", @"mysql error occurred informative message"), [[self connection] lastErrorMessage]] + ); } return NO; -- cgit v1.2.3 From ffbf7a4a9d4c617afd47a5428a17e0646b9bce97 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 21 Oct 2015 00:39:34 +0200 Subject: Fix bundle commands no longer working in popup field editor (and possibly other cases) (#2299) Replacing some [NSApp mainWindow] with their actually intended calls [NSApp keyWindow] and [view window] --- Source/SPAppController.m | 6 +++--- Source/SPBundleEditorController.m | 4 ++-- Source/SPCustomQuery.m | 9 +++++---- Source/SPDatabaseDocument.m | 14 ++++++++------ Source/SPDatabaseViewController.m | 9 +++++---- Source/SPTableContentDelegate.m | 10 +++++----- Source/SPTableInfo.h | 2 +- Source/SPTableInfo.m | 4 ++-- Source/SPTablesList.m | 2 +- Source/SPTextAndLinkCell.m | 4 ++-- Source/SPTextViewAdditions.m | 2 +- 11 files changed, 35 insertions(+), 31 deletions(-) diff --git a/Source/SPAppController.m b/Source/SPAppController.m index 7ed49859..91ce9737 100644 --- a/Source/SPAppController.m +++ b/Source/SPAppController.m @@ -2024,7 +2024,7 @@ NSEvent *event = [NSApp currentEvent]; BOOL checkForKeyEquivalents = ([event type] == NSKeyDown) ? YES : NO; - id firstResponder = [[NSApp mainWindow] firstResponder]; + id firstResponder = [[NSApp keyWindow] firstResponder]; NSString *scope = [[sender representedObject] objectForKey:@"scope"]; NSString *keyEqKey = nil; @@ -2076,7 +2076,7 @@ NSMenuItem *aMenuItem = [[[NSMenuItem alloc] init] autorelease]; [aMenuItem setTag:0]; [aMenuItem setToolTip:[eq objectForKey:@"path"]]; - [(SPTextView *)[[NSApp mainWindow] firstResponder] executeBundleItemForInputField:aMenuItem]; + [(SPTextView *)firstResponder executeBundleItemForInputField:aMenuItem]; } } } else { @@ -2095,7 +2095,7 @@ NSMenuItem *aMenuItem = [[[NSMenuItem alloc] init] autorelease]; [aMenuItem setTag:0]; [aMenuItem setToolTip:[eq objectForKey:@"path"]]; - [(SPCopyTable *)[[NSApp mainWindow] firstResponder] executeBundleItemForDataTable:aMenuItem]; + [(SPCopyTable *)firstResponder executeBundleItemForDataTable:aMenuItem]; } } } else { diff --git a/Source/SPBundleEditorController.m b/Source/SPBundleEditorController.m index a0c7d7ce..7757c0ba 100644 --- a/Source/SPBundleEditorController.m +++ b/Source/SPBundleEditorController.m @@ -1334,7 +1334,7 @@ //abort editing [control abortEditing]; - [[NSApp mainWindow] makeFirstResponder:commandsOutlineView]; + [[commandsOutlineView window] makeFirstResponder:commandsOutlineView]; return YES; } else{ return NO; @@ -1452,7 +1452,7 @@ (action == @selector(displayBundleMetaInfo:))) { // Allow to record short-cuts used by the Bundle Editor - if([[NSApp mainWindow] firstResponder] == keyEquivalentField) return NO; + if([[NSApp keyWindow] firstResponder] == keyEquivalentField) return NO; return ([[commandBundleTreeController selectedObjects] count] == 1 && ![[[commandBundleTreeController selectedObjects] objectAtIndex:0] objectForKey:kChildrenKey]); } diff --git a/Source/SPCustomQuery.m b/Source/SPCustomQuery.m index 7b446237..4737bea8 100644 --- a/Source/SPCustomQuery.m +++ b/Source/SPCustomQuery.m @@ -2583,16 +2583,17 @@ 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([[[[[NSApp mainWindow] firstResponder] class] description] isEqualToString:@"SPCopyTable"]) - [[[[NSApp mainWindow] firstResponder] onMainThread] executeBundleItemForDataTable:aMenuItem]; + if([[[firstResponder class] description] isEqualToString:@"SPCopyTable"]) + [[firstResponder onMainThread] executeBundleItemForDataTable:aMenuItem]; } else if([[data objectAtIndex:1] isEqualToString:SPBundleScopeInputField]) { - if([[[NSApp mainWindow] firstResponder] isKindOfClass:[NSTextView class]]) - [[[[NSApp mainWindow] firstResponder] onMainThread] executeBundleItemForInputField:aMenuItem]; + if([firstResponder isKindOfClass:[NSTextView class]]) + [[firstResponder onMainThread] executeBundleItemForInputField:aMenuItem]; } } } diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index bd258662..fd4a74c5 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -5291,8 +5291,9 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; } if([command isEqualToString:@"SelectTableRows"]) { - if([params count] > 1 && [[[NSApp mainWindow] firstResponder] respondsToSelector:@selector(selectTableRows:)]) { - [(SPCopyTable *)[[NSApp mainWindow] firstResponder] selectTableRows:[params subarrayWithRange:NSMakeRange(1, [params count]-1)]]; + id firstResponder = [[NSApp keyWindow] firstResponder]; + if([params count] > 1 && [firstResponder respondsToSelector:@selector(selectTableRows:)]) { + [(SPCopyTable *)firstResponder selectTableRows:[params subarrayWithRange:NSMakeRange(1, [params count]-1)]]; } return; } @@ -6307,17 +6308,18 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; 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 ([[[[[NSApp mainWindow] firstResponder] class] description] isEqualToString:@"SPCopyTable"]) { - [[[[NSApp mainWindow] firstResponder] onMainThread] executeBundleItemForDataTable:aMenuItem]; + if ([[[firstResponder class] description] isEqualToString:@"SPCopyTable"]) { + [[firstResponder onMainThread] executeBundleItemForDataTable:aMenuItem]; } } else if([[data objectAtIndex:1] isEqualToString:SPBundleScopeInputField]) { - if ([[[NSApp mainWindow] firstResponder] isKindOfClass:[NSTextView class]]) { - [[[[NSApp mainWindow] firstResponder] onMainThread] executeBundleItemForInputField:aMenuItem]; + if ([firstResponder isKindOfClass:[NSTextView class]]) { + [[firstResponder onMainThread] executeBundleItemForInputField:aMenuItem]; } } } diff --git a/Source/SPDatabaseViewController.m b/Source/SPDatabaseViewController.m index f0ee65d5..194ee3b4 100644 --- a/Source/SPDatabaseViewController.m +++ b/Source/SPDatabaseViewController.m @@ -615,16 +615,17 @@ 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([[[[[NSApp mainWindow] firstResponder] class] description] isEqualToString:@"SPCopyTable"]) - [[[[NSApp mainWindow] firstResponder] onMainThread] executeBundleItemForDataTable:aMenuItem]; + if([[[firstResponder class] description] isEqualToString:@"SPCopyTable"]) + [[firstResponder onMainThread] executeBundleItemForDataTable:aMenuItem]; } else if([[data objectAtIndex:1] isEqualToString:SPBundleScopeInputField]) { - if([[[NSApp mainWindow] firstResponder] isKindOfClass:[NSTextView class]]) - [[[[NSApp mainWindow] firstResponder] onMainThread] executeBundleItemForInputField:aMenuItem]; + if([firstResponder isKindOfClass:[NSTextView class]]) + [[firstResponder onMainThread] executeBundleItemForInputField:aMenuItem]; } } } diff --git a/Source/SPTableContentDelegate.m b/Source/SPTableContentDelegate.m index eee0fb0a..bf7aac5d 100644 --- a/Source/SPTableContentDelegate.m +++ b/Source/SPTableContentDelegate.m @@ -148,18 +148,18 @@ 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 ([[[[[NSApp mainWindow] firstResponder] class] description] isEqualToString:@"SPCopyTable"]) { - [[[[NSApp mainWindow] firstResponder] onMainThread] executeBundleItemForDataTable:aMenuItem]; + if ([[[firstResponder class] description] isEqualToString:@"SPCopyTable"]) { + [[firstResponder onMainThread] executeBundleItemForDataTable:aMenuItem]; } } else if ([[data objectAtIndex:1] isEqualToString:SPBundleScopeInputField]) { - if ([[[NSApp mainWindow] firstResponder] isKindOfClass:[NSTextView class]]) { - [[[[NSApp mainWindow] firstResponder] onMainThread] executeBundleItemForInputField:aMenuItem]; + if ([firstResponder isKindOfClass:[NSTextView class]]) { + [[firstResponder onMainThread] executeBundleItemForInputField:aMenuItem]; } } } diff --git a/Source/SPTableInfo.h b/Source/SPTableInfo.h index 56b22077..13a72976 100644 --- a/Source/SPTableInfo.h +++ b/Source/SPTableInfo.h @@ -30,7 +30,7 @@ @interface SPTableInfo : NSObject { - IBOutlet id infoTable; + IBOutlet NSTableView *infoTable; IBOutlet id tableList; IBOutlet id tableListInstance; IBOutlet id tableDataInstance; diff --git a/Source/SPTableInfo.m b/Source/SPTableInfo.m index e1ce3e4d..8268662b 100644 --- a/Source/SPTableInfo.m +++ b/Source/SPTableInfo.m @@ -390,11 +390,11 @@ if (![tableInfoScrollView isHidden]) { [tableDocumentInstance setActivityPaneHidden:@0]; - [[NSApp mainWindow] makeFirstResponder:activitiesTable]; + [[activitiesTable window] makeFirstResponder:activitiesTable]; } else { [tableDocumentInstance setActivityPaneHidden:@1]; - [[NSApp mainWindow] makeFirstResponder:infoTable]; + [[infoTable window] makeFirstResponder:infoTable]; } [infoTable deselectAll:nil]; diff --git a/Source/SPTablesList.m b/Source/SPTablesList.m index 76671530..4189d618 100644 --- a/Source/SPTablesList.m +++ b/Source/SPTablesList.m @@ -1634,7 +1634,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [textView methodForSelector:command] == [textView methodForSelector:@selector(complete:)] ) { [control abortEditing]; - [[NSApp mainWindow] makeFirstResponder:tablesListView]; + [[tablesListView window] makeFirstResponder:tablesListView]; return YES; } else{ diff --git a/Source/SPTextAndLinkCell.m b/Source/SPTextAndLinkCell.m index 2aefbff5..9759903d 100644 --- a/Source/SPTextAndLinkCell.m +++ b/Source/SPTextAndLinkCell.m @@ -183,7 +183,7 @@ static inline NSRect SPTextLinkRectFromCellRect(NSRect inRect) // Fast case for no link - make entire cell editable click area if (!hasLink || !linkActive) return NSCellHitContentArea | NSCellHitEditableTextArea; - NSPoint p = [[[NSApp mainWindow] contentView] convertPoint:[event locationInWindow] toView:controlView]; + NSPoint p = [[[controlView window] contentView] convertPoint:[event locationInWindow] toView:controlView]; NSRect linkRect = SPTextLinkRectFromCellRect(cellFrame); // Hit the link if it falls within the link rectangle for this cell, set when drawing @@ -233,7 +233,7 @@ static inline NSRect SPTextLinkRectFromCellRect(NSRect inRect) // Capture the clicked row and cell NSTableView *tableView = (NSTableView *)[self controlView]; - p = [[[NSApp mainWindow] contentView] convertPoint:[theEvent locationInWindow] toView:tableView]; + p = [[[tableView window] contentView] convertPoint:[theEvent locationInWindow] toView:tableView]; lastLinkColumn = [tableView columnAtPoint:p]; lastLinkRow = [tableView rowAtPoint:p]; diff --git a/Source/SPTextViewAdditions.m b/Source/SPTextViewAdditions.m index ef5c776e..88096a8f 100644 --- a/Source/SPTextViewAdditions.m +++ b/Source/SPTextViewAdditions.m @@ -849,7 +849,7 @@ if([keyEq length]) [mItem setKeyEquivalentModifierMask:[[[item objectForKey:SPBundleFileKeyEquivalentKey] objectAtIndex:1] intValue]]; - [mItem setTarget:[[NSApp mainWindow] firstResponder]]; + [mItem setTarget:[[NSApp keyWindow] firstResponder]]; if([item objectForKey:SPBundleFileTooltipKey]) [mItem setToolTip:[item objectForKey:SPBundleFileTooltipKey]]; -- cgit v1.2.3 From f55a56f87dbff301c409f66a446a603ad1159242 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 21 Oct 2015 04:12:41 +0200 Subject: That looks like it isn't used anymore --- Source/SPAppController.h | 4 ---- Source/SPAppController.m | 1 - 2 files changed, 5 deletions(-) diff --git a/Source/SPAppController.h b/Source/SPAppController.h index b548c36b..4dd93839 100644 --- a/Source/SPAppController.h +++ b/Source/SPAppController.h @@ -40,10 +40,6 @@ @interface SPAppController : NSObject { - IBOutlet NSWindow* bundleEditorWindow; - - BOOL isNewFavorite; - SPAboutController *aboutController; SPPreferenceController *prefsController; SPBundleEditorController *bundleEditorController; diff --git a/Source/SPAppController.m b/Source/SPAppController.m index 91ce9737..3458634a 100644 --- a/Source/SPAppController.m +++ b/Source/SPAppController.m @@ -131,7 +131,6 @@ // Register for drag start notifications - used to bring all windows to front [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tabDragStarted:) name:PSMTabDragDidBeginNotification object:nil]; - isNewFavorite = NO; } /** -- cgit v1.2.3 From 6f8243138865f4121f3bfd38f18fd8004b66c218 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 21 Oct 2015 13:28:53 +0200 Subject: Increase width of "INDEXES" section heading for localization --- Interfaces/English.lproj/DBView.xib | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Interfaces/English.lproj/DBView.xib b/Interfaces/English.lproj/DBView.xib index b67ac787..b89ada7b 100644 --- a/Interfaces/English.lproj/DBView.xib +++ b/Interfaces/English.lproj/DBView.xib @@ -2112,7 +2112,7 @@ 268 - {{3, 3}, {58, 14}} + {{3, 3}, {155, 14}} YES @@ -30995,7 +30995,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA NSObject NSTableView - id + NSTableView id id NSScrollView -- cgit v1.2.3 From e7cf9cde835a3db7921a96da063b908c0084a43f Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 24 Oct 2015 14:33:44 +0200 Subject: Fix table name token not being used in SQL exports even when only one table is selected --- Source/SPExportFilenameUtilities.m | 2 ++ Source/SPExportInitializer.m | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/SPExportFilenameUtilities.m b/Source/SPExportFilenameUtilities.m index e71ede3b..ac09dd3b 100644 --- a/Source/SPExportFilenameUtilities.m +++ b/Source/SPExportFilenameUtilities.m @@ -149,6 +149,8 @@ } [exportCustomFilenameTokenPool setObjectValue:exportTokens]; + //update preview name as programmatically changing the exportCustomFilenameTokenField does not fire a notification + [self updateDisplayedExportFilename]; } /** diff --git a/Source/SPExportInitializer.m b/Source/SPExportInitializer.m index 03284525..1b9fb5a8 100644 --- a/Source/SPExportInitializer.m +++ b/Source/SPExportInitializer.m @@ -304,7 +304,8 @@ [sqlExporter setSqlExportTables:exportTables]; // Create custom filename if required - [exportFilename setString:(createCustomFilename) ? [self expandCustomFilenameFormatUsingTableName:nil] : [self generateDefaultExportFilename]]; + NSString *selectedTableName = (exportSource == SPTableExport && [exportTables count] == 1)? [[exportTables objectAtIndex:0] objectAtIndex:0] : nil; + [exportFilename setString:(createCustomFilename) ? [self expandCustomFilenameFormatUsingTableName:selectedTableName] : [self generateDefaultExportFilename]]; // Only append the extension if necessary if (![[exportFilename pathExtension] length]) { -- cgit v1.2.3 From 2a8528d88a7ad2c9294d64c6b0effd67e14fbf4b Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 25 Oct 2015 19:34:44 +0100 Subject: Improve the way Sequel Pro inferrs the collation of a column. (#2237) This does not entirely fix the bug of SP sometimes displaying the wrong collation, but should work in >= 99% of cases. --- Source/SPDatabaseData.h | 2 ++ Source/SPDatabaseData.m | 56 ++++++++++++++++++++++++++++++++------- Source/SPTableInfo.m | 13 ++++----- Source/SPTableStructureDelegate.m | 37 ++++++++++++++++++-------- Source/SPTableStructureLoading.m | 24 +++++++++++------ 5 files changed, 98 insertions(+), 34 deletions(-) diff --git a/Source/SPDatabaseData.h b/Source/SPDatabaseData.h index aaf92f3d..f2fe08e1 100644 --- a/Source/SPDatabaseData.h +++ b/Source/SPDatabaseData.h @@ -42,6 +42,7 @@ @interface SPDatabaseData : NSObject { NSString *characterSetEncoding; + NSString *defaultCollationForCharacterSet; NSString *defaultCharacterSetEncoding; NSString *defaultCollation; NSString *serverDefaultCharacterSetEncoding; @@ -72,6 +73,7 @@ - (NSArray *)getDatabaseCollations; - (NSArray *)getDatabaseCollationsForEncoding:(NSString *)encoding; +- (NSString *)getDefaultCollationForEncoding:(NSString *)encoding; - (NSArray *)getDatabaseStorageEngines; - (NSArray *)getDatabaseCharacterSetEncodings; diff --git a/Source/SPDatabaseData.m b/Source/SPDatabaseData.m index 4856505d..2d1d4f14 100644 --- a/Source/SPDatabaseData.m +++ b/Source/SPDatabaseData.m @@ -58,6 +58,7 @@ NSInteger _sortStorageEngineEntry(NSDictionary *itemOne, NSDictionary *itemTwo, { if ((self = [super init])) { characterSetEncoding = nil; + defaultCollationForCharacterSet = nil; defaultCollation = nil; defaultCharacterSetEncoding = nil; serverDefaultCollation = nil; @@ -83,6 +84,7 @@ NSInteger _sortStorageEngineEntry(NSDictionary *itemOne, NSDictionary *itemTwo, - (void)resetAllData { if (characterSetEncoding != nil) SPClear(characterSetEncoding); + if (defaultCollationForCharacterSet != nil) SPClear(defaultCollationForCharacterSet); if (defaultCollation != nil) SPClear(defaultCollation); if (defaultCharacterSetEncoding != nil) SPClear(defaultCharacterSetEncoding); if (serverDefaultCharacterSetEncoding) SPClear(serverDefaultCharacterSetEncoding); @@ -117,9 +119,14 @@ NSInteger _sortStorageEngineEntry(NSDictionary *itemOne, NSDictionary *itemTwo, // If that failed, get the list of collations from the hard-coded list if (![collations count]) { const SPDatabaseCharSets *c = SPGetDatabaseCharacterSets(); - +#warning This probably won't work as intended. See my comment in getDatabaseCollationsForEncoding: do { - [collations addObject:[NSString stringWithCString:c->collation encoding:NSUTF8StringEncoding]]; + [collations addObject:@{ + @"ID" : @(c->nr), + @"CHARACTER_SET_NAME" : [NSString stringWithCString:c->name encoding:NSUTF8StringEncoding], + @"COLLATION_NAME" : [NSString stringWithCString:c->collation encoding:NSUTF8StringEncoding], + // description is not present in information_schema.collations + }]; ++c; } @@ -139,12 +146,16 @@ NSInteger _sortStorageEngineEntry(NSDictionary *itemOne, NSDictionary *itemTwo, if (encoding && ((characterSetEncoding == nil) || (![characterSetEncoding isEqualToString:encoding]) || ([characterSetCollations count] == 0))) { [characterSetEncoding release]; + SPClear(defaultCollationForCharacterSet); //depends on encoding [characterSetCollations removeAllObjects]; characterSetEncoding = [[NSString alloc] initWithString:encoding]; - if([cachedCollationsByEncoding objectForKey:characterSetEncoding] && [[cachedCollationsByEncoding objectForKey:characterSetEncoding] count]) - return [cachedCollationsByEncoding objectForKey:characterSetEncoding]; + NSArray *cachedCollations = [cachedCollationsByEncoding objectForKey:characterSetEncoding]; + if([cachedCollations count]) { + [characterSetCollations addObjectsFromArray:cachedCollations]; + goto copy_return; + } // Try to retrieve the available collations for the supplied encoding from the database if ([serverSupport supportsInformationSchema]) { @@ -162,7 +173,9 @@ NSInteger _sortStorageEngineEntry(NSDictionary *itemOne, NSDictionary *itemTwo, // If that failed, get the list of collations matching the supplied encoding from the hard-coded list if (![characterSetCollations count]) { const SPDatabaseCharSets *c = SPGetDatabaseCharacterSets(); - +#warning I don't think this will work. The hardcoded list is supposed to be used with pre 4.1 mysql servers, \ + which don't have information_schema or SHOW COLLATION. But before 4.1 there were no real collations and \ + even the charsets had different names (e.g. charset "latin1_de" which now is "latin1" + "latin1_german2_ci") do { NSString *charSet = [NSString stringWithCString:c->name encoding:NSUTF8StringEncoding]; @@ -175,13 +188,38 @@ NSInteger _sortStorageEngineEntry(NSDictionary *itemOne, NSDictionary *itemTwo, while (c[0].nr != 0); } - if (characterSetCollations && [characterSetCollations count]) { + if ([characterSetCollations count]) { [cachedCollationsByEncoding setObject:[NSArray arrayWithArray:characterSetCollations] forKey:characterSetEncoding]; } } - - return characterSetCollations; +copy_return: + return [NSArray arrayWithArray:characterSetCollations]; //copy because it is a mutable array and we keep changing it +} + +/** Get the collation that is marked as default for a given encoding by the server + * @param encoding The encoding, e.g. @"latin1" + * @return The default collation (e.g. @"latin1_swedish_ci") or + * nil if either encoding was nil or the server does not provide the neccesary details + */ +- (NSString *)getDefaultCollationForEncoding:(NSString *)encoding +{ + if(!encoding) return nil; + // if ( + // - we have not yet fetched info about the default collation OR + // - encoding is different than the one we currently know about + // ) => we need to load it from server, otherwise just return cached value + if ((defaultCollationForCharacterSet == nil) || (![characterSetEncoding isEqualToString:encoding])) { + NSArray *cols = [self getDatabaseCollationsForEncoding:encoding]; //will clear stored encoding and collation if neccesary + for (NSDictionary *collation in cols) { +#warning This won't work for the hardcoded list (see above) + if([[[collation objectForKey:@"IS_DEFAULT"] lowercaseString] isEqualToString:@"yes"]) { + defaultCollationForCharacterSet = [[NSString alloc] initWithString:[collation objectForKey:@"COLLATION_NAME"]]; + break; + } + } + } + return defaultCollationForCharacterSet; } /** @@ -296,7 +334,7 @@ NSInteger _sortStorageEngineEntry(NSDictionary *itemOne, NSDictionary *itemTwo, // If that failed, get the list of character set encodings from the hard-coded list if (![characterSetEncodings count]) { const SPDatabaseCharSets *c = SPGetDatabaseCharacterSets(); - +#warning This probably won't work as intended. See my comment in getDatabaseCollationsForEncoding: do { [characterSetEncodings addObject:[NSDictionary dictionaryWithObjectsAndKeys: [NSString stringWithCString:c->name encoding:NSUTF8StringEncoding], @"CHARACTER_SET_NAME", diff --git a/Source/SPTableInfo.m b/Source/SPTableInfo.m index 8268662b..66758e3f 100644 --- a/Source/SPTableInfo.m +++ b/Source/SPTableInfo.m @@ -170,7 +170,7 @@ if (![[tableStatus objectForKey:@"Create_time"] isNSNull]) { // Add the creation date to the infoTable - [info addObject:[NSString stringWithFormat:NSLocalizedString(@"created: %@", @"created: %@"), [self _getUserDefinedDateStringFromMySQLDate:[tableStatus objectForKey:@"Create_time"]]]]; + [info addObject:[NSString stringWithFormat:NSLocalizedString(@"created: %@", @"Table Info Section : time+date table was created at"), [self _getUserDefinedDateStringFromMySQLDate:[tableStatus objectForKey:@"Create_time"]]]]; } // Check for 'Update_time' == NULL - InnoDB tables don't have an update time @@ -187,15 +187,16 @@ // Check for 'Rows' == NULL - information_schema database doesn't report row count for it's tables if (![[tableStatus objectForKey:@"Rows"] isNSNull]) { - [info addObject:[NSString stringWithFormat:[[tableStatus objectForKey:@"RowsCountAccurate"] boolValue] ? NSLocalizedString(@"rows: %@", @"rows: %@") : NSLocalizedString(@"rows: ~%@", @"rows: ~%@"), + [info addObject:[NSString stringWithFormat:[[tableStatus objectForKey:@"RowsCountAccurate"] boolValue] ? NSLocalizedString(@"rows: %@", @"Table Info Section : number of rows (exact value)") : NSLocalizedString(@"rows: ~%@", @"Table Info Section : number of rows (estimated value)"), [numberFormatter stringFromNumber:[NSNumber numberWithLongLong:[[tableStatus objectForKey:@"Rows"] longLongValue]]]]]; } - [info addObject:[NSString stringWithFormat:NSLocalizedString(@"size: %@", @"size: %@"), [NSString stringForByteSize:[[tableStatus objectForKey:@"Data_length"] longLongValue]]]]; - [info addObject:[NSString stringWithFormat:NSLocalizedString(@"encoding: %@", @"encoding: %@"), [tableDataInstance tableEncoding]]]; - + [info addObject:[NSString stringWithFormat:NSLocalizedString(@"size: %@", @"Table Info Section : table size on disk"), [NSString stringForByteSize:[[tableStatus objectForKey:@"Data_length"] longLongValue]]]]; + [info addObject:[NSString stringWithFormat:NSLocalizedString(@"encoding: %@", @"Table Info Section : table charset"), [tableDataInstance tableEncoding]]]; + //[info addObject:[NSString stringWithFormat:NSLocalizedString(@"collation: %@", @"Table Info Section : table collation"), [tableStatus objectForKey:@"Collation"]]]; + if (![[tableStatus objectForKey:@"Auto_increment"] isNSNull]) { - [info addObject:[NSString stringWithFormat:NSLocalizedString(@"auto_increment: %@", @"auto_increment: %@"), + [info addObject:[NSString stringWithFormat:NSLocalizedString(@"auto_increment: %@", @"Table Info Section : current value of auto_increment"), [numberFormatter stringFromNumber:[NSNumber numberWithLongLong:[[tableStatus objectForKey:@"Auto_increment"] longLongValue]]]]]; } diff --git a/Source/SPTableStructureDelegate.m b/Source/SPTableStructureDelegate.m index 3f2c54e6..1e470e79 100644 --- a/Source/SPTableStructureDelegate.m +++ b/Source/SPTableStructureDelegate.m @@ -62,6 +62,8 @@ if ((NSUInteger)rowIndex >= [tableFields count]) return @"..."; if ([[tableColumn identifier] isEqualToString:@"collation"]) { + NSString *tableEncoding = [tableDataInstance tableEncoding]; + NSString *columnEncoding = nil; NSInteger idx = 0; if ((idx = [[NSArrayObjectAtIndex(tableFields, rowIndex) objectForKey:@"encoding"] integerValue]) > 0 && idx < [encodingPopupCell numberOfItems]) { @@ -69,28 +71,44 @@ NSUInteger start = [enc rangeOfString:@"("].location + 1; - collations = [databaseDataInstance getDatabaseCollationsForEncoding:[enc substringWithRange:NSMakeRange(start, [enc length] - start - 1)]]; + columnEncoding = [enc substringWithRange:NSMakeRange(start, [enc length] - start - 1)]; + collations = [databaseDataInstance getDatabaseCollationsForEncoding:columnEncoding]; } else { // If the structure has loaded (not still loading!) and the table encoding // is set, use the appropriate collations. - collations = ([tableDocumentInstance structureLoaded] && [tableDataInstance tableEncoding] != nil) ? [databaseDataInstance getDatabaseCollationsForEncoding:[tableDataInstance tableEncoding]] : @[]; + collations = @[]; + if([tableDocumentInstance structureLoaded]) { + columnEncoding = [tableDataInstance tableEncoding]; + if(columnEncoding) collations = [databaseDataInstance getDatabaseCollationsForEncoding:columnEncoding]; + } } - [[tableColumn dataCell] removeAllItems]; + NSPopUpButtonCell *collationCell = [tableColumn dataCell]; + + [collationCell removeAllItems]; if ([collations count] > 0) { - NSString *defaultCollation = [[tableDataInstance statusValues] objectForKey:@"collation"]; + NSString *tableCollation = [[tableDataInstance statusValues] objectForKey:@"Collation"]; - if (!defaultCollation) { - defaultCollation = [databaseDataInstance getDatabaseDefaultCollation]; + if (![tableCollation length]) { + tableCollation = [databaseDataInstance getDefaultCollationForEncoding:tableEncoding]; + } + + NSString *columnCollation = [NSArrayObjectAtIndex(tableFields, rowIndex) objectForKey:@"collationName"]; + + if (![columnCollation length]) { + columnCollation = [databaseDataInstance getDefaultCollationForEncoding:columnEncoding]; } [[tableColumn dataCell] addItemWithTitle:@""]; BOOL useMonospacedFont = [prefs boolForKey:SPUseMonospacedFonts]; CGFloat monospacedFontSize = [prefs floatForKey:SPMonospacedFontSize] > 0 ? [prefs floatForKey:SPMonospacedFontSize] : [NSFont smallSystemFontSize]; + NSMutableDictionary *menuAttributes = [NSMutableDictionary dictionaryWithObject:[NSColor lightGrayColor] forKey:NSForegroundColorAttributeName]; + [menuAttributes setObject:useMonospacedFont ? [NSFont fontWithName:SPDefaultMonospacedFontName size:monospacedFontSize] : [NSFont systemFontOfSize:[NSFont smallSystemFontSize]] forKey:NSFontAttributeName]; + BOOL columnUsesTableDefaultEncoding = ([columnEncoding isEqualToString:tableEncoding]); // Populate collation popup button for (NSDictionary *collation in collations) { @@ -99,12 +117,9 @@ [[tableColumn dataCell] addItemWithTitle:collationName]; // If this matches the table's collation, draw in gray - if ([collationName length] && [collationName isEqualToString:defaultCollation]) { + if (columnUsesTableDefaultEncoding && [collationName isEqualToString:tableCollation]) { NSMenuItem *collationMenuItem = [(NSPopUpButtonCell *)[tableColumn dataCell] itemAtIndex:([[tableColumn dataCell] numberOfItems] - 1)]; - NSMutableDictionary *menuAttributes = [NSMutableDictionary dictionaryWithObject:[NSColor lightGrayColor] forKey:NSForegroundColorAttributeName]; - - [menuAttributes setObject:useMonospacedFont ? [NSFont fontWithName:SPDefaultMonospacedFontName size:monospacedFontSize] : [NSFont systemFontOfSize:[NSFont smallSystemFontSize]] forKey:NSFontAttributeName]; - + NSAttributedString *itemString = [[[NSAttributedString alloc] initWithString:collationName attributes:menuAttributes] autorelease]; [collationMenuItem setAttributedTitle:itemString]; diff --git a/Source/SPTableStructureLoading.m b/Source/SPTableStructureLoading.m index f6377420..a28348dd 100644 --- a/Source/SPTableStructureLoading.m +++ b/Source/SPTableStructureLoading.m @@ -173,14 +173,20 @@ NSString *encoding = nil; if ([fieldValidation isFieldTypeString:type]) { - - collation = [theField objectForKey:@"collation"] ? [theField objectForKey:@"collation"] : [[tableDataInstance statusValues] objectForKey:@"collation"]; - encoding = [theField objectForKey:@"encoding"] ? [theField objectForKey:@"encoding"] : [tableDataInstance tableEncoding]; - // If we still don't have a collation then fallback on the database default (not available on MySQL < 4.1.1). - if (!collation) { - collation = [databaseDataInstance getDatabaseDefaultCollation]; - } + // The MySQL 4.1 manual says: + // + // MySQL chooses the column character set and collation in the following manner: + // 1. If both CHARACTER SET X and COLLATE Y were specified, then character set X and collation Y are used. + // 2. If CHARACTER SET X was specified without COLLATE, then character set X and its default collation are used. + // 3. If COLLATE Y was specified without CHARACTER SET, then the character set associated with Y and collation Y. + // 4. Otherwise, the table character set and collation are used. +#warning This is not correct, see the comment above. \ + However MySQL ususally outputs the CREATE TABLE statement in a way for this to still get the result right. + encoding = [theField objectForKey:@"encoding"] ? [theField objectForKey:@"encoding"] : [tableDataInstance tableEncoding]; + collation = [theField objectForKey:@"collation"] ? [theField objectForKey:@"collation"] : [databaseDataInstance getDefaultCollationForEncoding:encoding]; + + // MySQL < 4.1 does not support collations (they are part of the charset), it will be nil there } if (encoding) { @@ -190,7 +196,8 @@ fieldEncoding = encoding; // Set the selected index as the match index +1 due to the leading @"" in the popup list - [theField setObject:[NSNumber numberWithInteger:(selectedIndex + 1)] forKey:@"encoding"]; + [theField setObject:@(selectedIndex + 1) forKey:@"encoding"]; + [theField setObject:encoding forKey:@"encodingName"]; break; } @@ -210,6 +217,7 @@ // Set the selected index as the match index +1 due to the leading @"" in the popup list [theField setObject:[NSNumber numberWithInteger:(selectedIndex + 1)] forKey:@"collation"]; + [theField setObject:collation forKey:@"collationName"]; // Set BINARY if collation ends with _bin for convenience if ([[col objectForKey:@"COLLATION_NAME"] hasSuffix:@"_bin"]) { -- cgit v1.2.3 From a1916cf9812e7ff0a958dfa0bdc9436a84782e02 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 25 Oct 2015 19:52:18 +0100 Subject: Fix an exception when trying to export CSV to multiple files (caused by my earlier changes to the export filename tokens) --- Source/SPExportInitializer.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/SPExportInitializer.m b/Source/SPExportInitializer.m index 1b9fb5a8..37f15ae9 100644 --- a/Source/SPExportInitializer.m +++ b/Source/SPExportInitializer.m @@ -505,7 +505,7 @@ BOOL tableNameInTokens = NO; NSArray *representedObjects = [exportCustomFilenameTokenField objectValue]; for (id representedObject in representedObjects) { - if ([representedObject isKindOfClass:[SPExportFileNameTokenObject class]] && [[representedObject tokenContent] isEqualToString:NSLocalizedString(@"table", @"table")]) tableNameInTokens = YES; + if ([representedObject isKindOfClass:[SPExportFileNameTokenObject class]] && [[representedObject tokenId] isEqualToString:NSLocalizedString(@"table", @"table")]) tableNameInTokens = YES; } [exportFilename setString:(tableNameInTokens ? exportFilename : [exportFilename stringByAppendingFormat:@"_%@", table])]; } @@ -567,7 +567,7 @@ BOOL tableNameInTokens = NO; NSArray *representedObjects = [exportCustomFilenameTokenField objectValue]; for (id representedObject in representedObjects) { - if ([representedObject isKindOfClass:[SPExportFileNameTokenObject class]] && [[representedObject tokenContent] isEqualToString:NSLocalizedString(@"table", @"table")]) tableNameInTokens = YES; + if ([representedObject isKindOfClass:[SPExportFileNameTokenObject class]] && [[representedObject tokenId] isEqualToString:NSLocalizedString(@"table", @"table")]) tableNameInTokens = YES; } [exportFilename setString:(tableNameInTokens ? exportFilename : [exportFilename stringByAppendingFormat:@"_%@", table])]; } -- cgit v1.2.3 From 80272af56eb0236f4c56e60bf0661f036c6e311b Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 25 Oct 2015 20:07:47 +0100 Subject: Add table collation to the table info area --- Source/SPTableInfo.m | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/SPTableInfo.m b/Source/SPTableInfo.m index 66758e3f..942cf63f 100644 --- a/Source/SPTableInfo.m +++ b/Source/SPTableInfo.m @@ -192,8 +192,7 @@ } [info addObject:[NSString stringWithFormat:NSLocalizedString(@"size: %@", @"Table Info Section : table size on disk"), [NSString stringForByteSize:[[tableStatus objectForKey:@"Data_length"] longLongValue]]]]; - [info addObject:[NSString stringWithFormat:NSLocalizedString(@"encoding: %@", @"Table Info Section : table charset"), [tableDataInstance tableEncoding]]]; - //[info addObject:[NSString stringWithFormat:NSLocalizedString(@"collation: %@", @"Table Info Section : table collation"), [tableStatus objectForKey:@"Collation"]]]; + [info addObject:[NSString stringWithFormat:NSLocalizedString(@"encoding: %1$@ (%2$@)", @"Table Info Section : $1 = table charset, $2 = table collation"), [tableDataInstance tableEncoding], [tableStatus objectForKey:@"Collation"]]]; if (![[tableStatus objectForKey:@"Auto_increment"] isNSNull]) { [info addObject:[NSString stringWithFormat:NSLocalizedString(@"auto_increment: %@", @"Table Info Section : current value of auto_increment"), -- cgit v1.2.3 From 2ed7473afe7bcc9ab7243f3347a5112d917b7385 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 25 Oct 2015 23:42:27 +0100 Subject: Wrap a repeated call in a simpler method --- Source/SPDataImport.m | 4 ++-- Source/SPDatabaseDocument.m | 2 +- Source/SPDatabaseStructure.h | 1 + Source/SPDatabaseStructure.m | 8 ++++++++ Source/SPNavigatorController.m | 2 +- Source/SPTableStructure.m | 2 +- Source/SPTableStructureLoading.m | 5 +---- Source/SPTablesList.m | 18 ++++++------------ 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Source/SPDataImport.m b/Source/SPDataImport.m index 418b7970..1439d7ca 100644 --- a/Source/SPDataImport.m +++ b/Source/SPDataImport.m @@ -670,7 +670,7 @@ [tablesListInstance updateTables:self]; // Re-query the structure of all databases in the background - [NSThread detachNewThreadWithName:SPCtxt(@"SPNavigatorController database structure querier",tableDocumentInstance) target:[tableDocumentInstance databaseStructureRetrieval] selector:@selector(queryDbStructureWithUserInfo:) object:@{@"forceUpdate" : @YES}]; + [[tableDocumentInstance databaseStructureRetrieval] queryDbStructureInBackgroundWithUserInfo:@{@"forceUpdate" : @YES}]; // Import finished Growl notification [[SPGrowlController sharedGrowlController] notifyWithTitle:@"Import Finished" @@ -1193,7 +1193,7 @@ [[tablesListInstance onMainThread] updateTables:self]; // Re-query the structure of all databases in the background - [NSThread detachNewThreadWithName:SPCtxt(@"SPNavigatorController database structure querier",tableDocumentInstance) target:[tableDocumentInstance databaseStructureRetrieval] selector:@selector(queryDbStructureWithUserInfo:) object:@{@"forceUpdate" : @YES}]; + [[tableDocumentInstance databaseStructureRetrieval] queryDbStructureInBackgroundWithUserInfo:@{@"forceUpdate" : @YES}]; // Select the new table [tablesListInstance selectItemWithName:selectedTableTarget]; diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index fd4a74c5..2f7a5235 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -1032,7 +1032,7 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; [self _addDatabase]; // Query the structure of all databases in the background (mainly for completion) - [NSThread detachNewThreadWithName:SPCtxt(@"SPNavigatorController database structure querier",self) target:databaseStructureRetrieval selector:@selector(queryDbStructureWithUserInfo:) object:@{@"forceUpdate" : @YES}]; + [databaseStructureRetrieval queryDbStructureInBackgroundWithUserInfo:@{@"forceUpdate" : @YES}]; } else { // Reset chooseDatabaseButton diff --git a/Source/SPDatabaseStructure.h b/Source/SPDatabaseStructure.h index b40dd449..cfc0b3eb 100644 --- a/Source/SPDatabaseStructure.h +++ b/Source/SPDatabaseStructure.h @@ -57,6 +57,7 @@ - (SPMySQLConnection *)connection; // Structure retrieval from the server +- (void)queryDbStructureInBackgroundWithUserInfo:(NSDictionary *)userInfo; - (void)queryDbStructureWithUserInfo:(NSDictionary*)userInfo; - (BOOL)isQueryingDatabaseStructure; diff --git a/Source/SPDatabaseStructure.m b/Source/SPDatabaseStructure.m index 83ba38bb..6644e922 100644 --- a/Source/SPDatabaseStructure.m +++ b/Source/SPDatabaseStructure.m @@ -147,6 +147,14 @@ #pragma mark - #pragma mark Structure retrieval from the server +- (void)queryDbStructureInBackgroundWithUserInfo:(NSDictionary *)userInfo +{ + [NSThread detachNewThreadWithName:SPCtxt(@"SPNavigatorController database structure querier", delegate) + target:self + selector:@selector(queryDbStructureWithUserInfo:) + object:userInfo]; +} + /** * Updates the dict containing the structure of all available databases (mainly for completion/navigator) * executed on the helper connection. diff --git a/Source/SPNavigatorController.m b/Source/SPNavigatorController.m index 1c7bfbba..47352f06 100644 --- a/Source/SPNavigatorController.m +++ b/Source/SPNavigatorController.m @@ -610,7 +610,7 @@ static NSComparisonResult compareStrings(NSString *s1, NSString *s2, void* conte if (![[doc getConnection] isConnected]) return; - [NSThread detachNewThreadWithName:SPCtxt(@"SPNavigatorController database structure querier", doc) target:[doc databaseStructureRetrieval] selector:@selector(queryDbStructureWithUserInfo:) object:@{@"forceUpdate" : @YES}]; + [[doc databaseStructureRetrieval] queryDbStructureInBackgroundWithUserInfo:@{@"forceUpdate" : @YES}]; } - (IBAction)outlineViewAction:(id)sender diff --git a/Source/SPTableStructure.m b/Source/SPTableStructure.m index 35e4c0f1..9973bf4b 100644 --- a/Source/SPTableStructure.m +++ b/Source/SPTableStructure.m @@ -969,7 +969,7 @@ static NSString *SPRemoveFieldAndForeignKey = @"SPRemoveFieldAndForeignKey"; [tableDocumentInstance setContentRequiresReload:YES]; // Query the structure of all databases in the background - [NSThread detachNewThreadWithName:SPCtxt(@"SPNavigatorController database structure querier", tableDocumentInstance) target:[tableDocumentInstance databaseStructureRetrieval] selector:@selector(queryDbStructureWithUserInfo:) object:[NSDictionary dictionaryWithObjectsAndKeys:@YES, @"forceUpdate", selectedTable, @"affectedItem", [NSNumber numberWithInteger:[tablesListInstance tableType]], @"affectedItemType", nil]]; + [[tableDocumentInstance databaseStructureRetrieval] queryDbStructureInBackgroundWithUserInfo:[NSDictionary dictionaryWithObjectsAndKeys:@YES, @"forceUpdate", selectedTable, @"affectedItem", [NSNumber numberWithInteger:[tablesListInstance tableType]], @"affectedItemType", nil]]; return YES; } diff --git a/Source/SPTableStructureLoading.m b/Source/SPTableStructureLoading.m index a28348dd..2c6d65fe 100644 --- a/Source/SPTableStructureLoading.m +++ b/Source/SPTableStructureLoading.m @@ -296,10 +296,7 @@ [tableDocumentInstance setStatusRequiresReload:YES]; // Query the structure of all databases in the background (mainly for completion) - [NSThread detachNewThreadWithName:SPCtxt(@"SPNavigatorController database structure querier", tableDocumentInstance) - target:[tableDocumentInstance databaseStructureRetrieval] - selector:@selector(queryDbStructureWithUserInfo:) - object:@{@"forceUpdate" : @YES}]; + [[tableDocumentInstance databaseStructureRetrieval] queryDbStructureInBackgroundWithUserInfo:@{@"forceUpdate" : @YES}]; [self loadTable:selectedTable]; } diff --git a/Source/SPTablesList.m b/Source/SPTablesList.m index 4189d618..7449a2cf 100644 --- a/Source/SPTablesList.m +++ b/Source/SPTablesList.m @@ -357,10 +357,10 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; // Query the structure of all databases in the background if (sender == self) // Invoked by SP - [NSThread detachNewThreadWithName:SPCtxt(@"SPNavigatorController database structure querier", tableDocumentInstance) target:[tableDocumentInstance databaseStructureRetrieval] selector:@selector(queryDbStructureWithUserInfo:) object:nil]; + [[tableDocumentInstance databaseStructureRetrieval] queryDbStructureInBackgroundWithUserInfo:nil]; else // User press refresh button ergo force update - [NSThread detachNewThreadWithName:SPCtxt(@"SPNavigatorController database structure querier", tableDocumentInstance) target:[tableDocumentInstance databaseStructureRetrieval] selector:@selector(queryDbStructureWithUserInfo:) object:@{@"forceUpdate" : @YES, @"cancelQuerying" : @YES}]; + [[tableDocumentInstance databaseStructureRetrieval] queryDbStructureInBackgroundWithUserInfo:@{@"forceUpdate" : @YES, @"cancelQuerying" : @YES}]; } /** @@ -1611,7 +1611,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; #endif // Query the structure of all databases in the background (mainly for completion) - [NSThread detachNewThreadWithName:SPCtxt(@"SPNavigatorController database structure querier", tableDocumentInstance) target:[tableDocumentInstance databaseStructureRetrieval] selector:@selector(queryDbStructureWithUserInfo:) object:@{@"forceUpdate" : @YES}]; + [[tableDocumentInstance databaseStructureRetrieval] queryDbStructureInBackgroundWithUserInfo:@{@"forceUpdate" : @YES}]; } #ifndef SP_CODA @@ -2266,10 +2266,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; #endif // Query the structure of all databases in the background (mainly for completion) - [NSThread detachNewThreadWithName:SPCtxt(@"SPNavigatorController database structure querier", tableDocumentInstance) - target:[tableDocumentInstance databaseStructureRetrieval] - selector:@selector(queryDbStructureWithUserInfo:) - object:@{@"forceUpdate" : @YES}]; + [[tableDocumentInstance databaseStructureRetrieval] queryDbStructureInBackgroundWithUserInfo:@{@"forceUpdate" : @YES}]; } #ifndef SP_CODA /* operations performed on whole tables */ @@ -2417,10 +2414,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; #endif // Query the structure of all databases in the background (mainly for completion) - [NSThread detachNewThreadWithName:SPCtxt(@"SPNavigatorController database structure querier", tableDocumentInstance) - target:[tableDocumentInstance databaseStructureRetrieval] - selector:@selector(queryDbStructureWithUserInfo:) - object:@{@"forceUpdate" : @YES}]; + [[tableDocumentInstance databaseStructureRetrieval] queryDbStructureInBackgroundWithUserInfo:@{@"forceUpdate" : @YES}]; } else { // Error while creating new table @@ -2640,7 +2634,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [tableDocumentInstance loadTable:selectedTableName ofType:selectedTableType]; // Query the structure of all databases in the background (mainly for completion) - [NSThread detachNewThreadWithName:SPCtxt(@"SPNavigatorController database structure querier", tableDocumentInstance) target:[tableDocumentInstance databaseStructureRetrieval] selector:@selector(queryDbStructureWithUserInfo:) object:@{@"forceUpdate" : @YES}]; + [[tableDocumentInstance databaseStructureRetrieval] queryDbStructureInBackgroundWithUserInfo:@{@"forceUpdate" : @YES}]; } #endif -- cgit v1.2.3 From 2b2a177e7adceabadd451c0dca300b30f14aebb2 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 26 Oct 2015 00:32:31 +0100 Subject: * Remove some ivars that are only used as local vars * Also I take back what I said about 4d97cbd4df1ebb451d89d6c4e12dd7c622d00b84 and say it will happen now *duckundweg* --- .../Source/SPMySQLConnection Categories/Ping & KeepAlive.h | 4 ++-- .../Source/SPMySQLConnection Categories/Ping & KeepAlive.m | 10 ++++++---- Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h | 4 +--- Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m | 2 -- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h index ae3c8156..a3f34817 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h @@ -32,8 +32,8 @@ typedef struct { MYSQL *mySQLConnection; - BOOL *keepAlivePingActivePointer; - BOOL *keepAliveLastPingSuccessPointer; + volatile BOOL *keepAlivePingThreadActivePointer; + volatile BOOL *keepAliveLastPingSuccessPointer; } SPMySQLConnectionPingDetails; @interface SPMySQLConnection (Ping_and_KeepAlive) diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m index c4469f5c..cfbb7183 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m @@ -140,7 +140,7 @@ end: // Set up a query lock [self _lockConnection]; - keepAliveLastPingSuccess = NO; + volatile BOOL keepAliveLastPingSuccess = NO; keepAliveLastPingBlocked = NO; keepAlivePingThreadActive = YES; @@ -152,12 +152,14 @@ end: SPMySQLConnectionPingDetails *pingDetails = malloc(sizeof(SPMySQLConnectionPingDetails)); pingDetails->mySQLConnection = mySQLConnection; pingDetails->keepAliveLastPingSuccessPointer = &keepAliveLastPingSuccess; - pingDetails->keepAlivePingActivePointer = &keepAlivePingThreadActive; + pingDetails->keepAlivePingThreadActivePointer = &keepAlivePingThreadActive; // Create a pthread for the ping + pthread_t keepAlivePingThread_t; + pthread_attr_t attr; pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); pthread_create(&keepAlivePingThread_t, &attr, (void *)&_backgroundPingTask, pingDetails); // Record the ping start time @@ -245,7 +247,7 @@ void _forceThreadExit(int signalNumber) void _pingThreadCleanup(void *pingDetails) { SPMySQLConnectionPingDetails *pingDetailsStruct = pingDetails; - *(pingDetailsStruct->keepAlivePingActivePointer) = NO; + *(pingDetailsStruct->keepAlivePingThreadActivePointer) = NO; // Clean up MySQL variables and handlers mysql_thread_end(); diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h index 9fae3f98..444e8dff 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h @@ -86,9 +86,7 @@ uint64_t lastKeepAliveTime; NSUInteger keepAlivePingFailures; volatile NSThread *keepAliveThread; - pthread_t keepAlivePingThread_t; - BOOL keepAlivePingThreadActive; - BOOL keepAliveLastPingSuccess; + volatile BOOL keepAlivePingThreadActive; BOOL keepAliveLastPingBlocked; // Encoding details - and also a record of any previous encoding to allow diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m index bcb4e031..eb1816b5 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m @@ -145,9 +145,7 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS keepAlivePingFailures = 0; lastKeepAliveTime = 0; keepAliveThread = nil; - keepAlivePingThread_t = NULL; keepAlivePingThreadActive = NO; - keepAliveLastPingSuccess = NO; keepAliveLastPingBlocked = NO; // Set up default encoding variables -- cgit v1.2.3 From b2d798ba9282d3acf1a2d65de30849e529d4d255 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 30 Oct 2015 01:41:01 +0100 Subject: * Lock connection during disconnect to prevent some race conditions * Always use the server version name provided by mysql_get_server_info() as that should me more reliable * Use mysql_get_server_version() for version comparisons. Less code, official API and closer to what mysql does --- .../Ping & KeepAlive.m | 13 +++-- .../SPMySQLConnection Categories/Server Info.m | 56 +++++----------------- .../SPMySQLFramework/Source/SPMySQLConnection.h | 1 + .../SPMySQLFramework/Source/SPMySQLConnection.m | 30 ++++++++---- Source/SPFieldMapperController.m | 4 +- Source/SPServerSupport.m | 13 +++-- 6 files changed, 48 insertions(+), 69 deletions(-) diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m index cfbb7183..7940b483 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m @@ -94,15 +94,15 @@ // attempt a single reconnection in the background if (_elapsedSecondsSinceAbsoluteTime(lastConnectionUsedTime) < 60 * 15) { [self _reconnectAfterBackgroundConnectionLoss]; - + } // Otherwise set the state to connection lost for automatic reconnect on // next use. - } else { + else { state = SPMySQLConnectionLostInBackground; } // Return as no further ping action required this cycle. - goto end; + goto end_cleanup; } // Otherwise, perform a background ping. @@ -112,7 +112,7 @@ } else { keepAlivePingFailures++; } -end: +end_cleanup: keepAliveThread = nil; } @@ -139,6 +139,11 @@ end: // Set up a query lock [self _lockConnection]; + //we might find ourselves at the losing end of a contest with -[self _disconnect] + if(!mySQLConnection) { + [self _unlockConnection]; + return NO; + } volatile BOOL keepAliveLastPingSuccess = NO; keepAliveLastPingBlocked = NO; diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m index dd684c78..01410eb5 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m @@ -46,54 +46,34 @@ return [NSString stringWithString:serverVariableVersion]; } -#warning FIXME: There is probably a race condition here with -[self _disconnect] - if(mySQLConnection) { - return [self _stringForCString:mysql_get_server_info(mySQLConnection)]; - } - return nil; } /** - * Return the server major version or NSNotFound on failure + * Return the server major version or 0 on failure */ - (NSUInteger)serverMajorVersion { - NSString *ver; - if ((ver = [self serverVersionString]) != nil) { - NSString *s = [[ver componentsSeparatedByString:@"."] objectAtIndex:0]; - return (NSUInteger)[s integerValue]; - } - - return NSNotFound; + // 5.5.33 => 50533 / 10'000 => 5.0533 => 5 + return (serverVersionNumber / 10000); } /** - * Return the server minor version or NSNotFound on failure + * Return the server minor version or 0 on failure */ - (NSUInteger)serverMinorVersion { - NSString *ver; - if ((ver = [self serverVersionString]) != nil) { - NSString *s = [[ver componentsSeparatedByString:@"."] objectAtIndex:1]; - return (NSUInteger)[s integerValue]; - } - - return NSNotFound; + // 5.5.33 => 50533 - (5*10'000) => 533 / 100 => 5.33 => 5 + return ((serverVersionNumber - [self serverMajorVersion]*10000) / 100); } /** - * Return the server release version or NSNotFound on failure + * Return the server release version or 0 on failure */ - (NSUInteger)serverReleaseVersion { - NSString *ver; - if ((ver = [self serverVersionString]) != nil) { - NSString *s = [[ver componentsSeparatedByString:@"."] objectAtIndex:2]; - return (NSUInteger)[[[s componentsSeparatedByString:@"-"] objectAtIndex:0] integerValue]; - } - - return NSNotFound; + // 5.5.33 => 50533 - (5*10'000 + 5*100) => 33 + return (serverVersionNumber - ([self serverMajorVersion]*10000 + [self serverMinorVersion]*100)); } #pragma mark - @@ -105,23 +85,9 @@ */ - (BOOL)serverVersionIsGreaterThanOrEqualTo:(NSUInteger)aMajorVersion minorVersion:(NSUInteger)aMinorVersion releaseVersion:(NSUInteger)aReleaseVersion { - NSString *ver; - if (!(ver = [self serverVersionString])) return NO; - - NSArray *serverVersionParts = [ver componentsSeparatedByString:@"."]; - - NSUInteger serverMajorVersion = (NSUInteger)[[serverVersionParts objectAtIndex:0] integerValue]; - if (serverMajorVersion < aMajorVersion) return NO; - if (serverMajorVersion > aMajorVersion) return YES; - - NSUInteger serverMinorVersion = (NSUInteger)[[serverVersionParts objectAtIndex:1] integerValue]; - if (serverMinorVersion < aMinorVersion) return NO; - if (serverMinorVersion > aMinorVersion) return YES; + unsigned long myver = aMajorVersion * 10000 + aMinorVersion * 100 + aReleaseVersion; - NSString *serverReleasePart = [serverVersionParts objectAtIndex:2]; - NSUInteger serverReleaseVersion = (NSUInteger)[[[serverReleasePart componentsSeparatedByString:@"-"] objectAtIndex:0] integerValue]; - if (serverReleaseVersion < aReleaseVersion) return NO; - return YES; + return (myver >= serverVersionNumber); } #pragma mark - diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h index 444e8dff..6a3a1013 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h @@ -99,6 +99,7 @@ // Server details NSString *serverVariableVersion; + unsigned long serverVersionNumber; // Error state for the last query or connection state NSUInteger queryErrorID; diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m index eb1816b5..1ca2f182 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m @@ -174,6 +174,7 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS // Ensure the server detail records are initialised serverVariableVersion = nil; + serverVersionNumber = 0; // Start with a blank error state queryErrorID = 0; @@ -475,6 +476,19 @@ asm(".desc ___crashreporter_info__, 0x10"); mysqlConnectionThreadId = mySQLConnection->thread_id; lastConnectionUsedTime = initialConnectTime; + // Copy the server version string to the instance variable + if (serverVariableVersion) [serverVariableVersion release], serverVariableVersion = nil; + // the mysql_get_server_info() function + // * returns the version name that is part of the initial connection handshake. + // * Unless the connection failed, it will always return a non-null buffer containing at least a '\0'. + // * It will never affect the error variables (since it only returns a struct member) + // + // At that point (handshake) there is no charset and it's highly unlikely this will ever contain something other than ASCII, + // but to be safe, we'll use the Latin1 encoding which won't bail on invalid chars. + serverVariableVersion = [[NSString alloc] initWithCString:mysql_get_server_info(mySQLConnection) encoding:NSISOLatin1StringEncoding]; + // this one can actually change the error state, but only if the server version string is not set (ie. no connection) + serverVersionNumber = mysql_get_server_version(mySQLConnection); + // Update SSL state connectedWithSSL = NO; if (useSSL) connectedWithSSL = (mysql_get_ssl_cipher(mySQLConnection))?YES:NO; @@ -892,6 +906,7 @@ asm(".desc ___crashreporter_info__, 0x10"); [self _unlockConnection]; [self _cancelKeepAlives]; + [self _lockConnection]; // Close the underlying MySQL connection if it still appears to be active, and not reading // or writing. While this may result in a leak of the MySQL object, it prevents crashes // due to attempts to close a blocked/stuck connection. @@ -899,17 +914,16 @@ asm(".desc ___crashreporter_info__, 0x10"); mysql_close(mySQLConnection); } mySQLConnection = NULL; + if (serverVariableVersion) [serverVariableVersion release], serverVariableVersion = nil; + serverVersionNumber = 0; + if (database) [database release], database = nil; + state = SPMySQLDisconnected; + [self _unlockConnection]; // If using a connection proxy, disconnect that too if (proxy) { [proxy performSelectorOnMainThread:@selector(disconnect) withObject:nil waitUntilDone:YES]; } - - // Clear host-specific information - if (serverVariableVersion) [serverVariableVersion release], serverVariableVersion = nil; - if (database) [database release], database = nil; - - state = SPMySQLDisconnected; } /** @@ -934,10 +948,6 @@ asm(".desc ___crashreporter_info__, 0x10"); [variables setObject:[variableRow objectAtIndex:1] forKey:[variableRow objectAtIndex:0]]; } - // Copy the server version string to the instance variable - if (serverVariableVersion) [serverVariableVersion release], serverVariableVersion = nil; - serverVariableVersion = [[variables objectForKey:@"version"] retain]; - // Get the connection encoding. Although a specific encoding may have been requested on // connection, it may be overridden by init_connect commands or connection state changes. // Default to latin1 for older server versions. diff --git a/Source/SPFieldMapperController.m b/Source/SPFieldMapperController.m index 9bef9d03..1a6cfc83 100644 --- a/Source/SPFieldMapperController.m +++ b/Source/SPFieldMapperController.m @@ -1043,9 +1043,7 @@ static NSUInteger SPSourceColumnTypeInteger = 1; NSArray *encodings = [databaseDataInstance getDatabaseCharacterSetEncodings]; NSString *utf8MenuItemTitle = nil; - if ([encodings count] > 0 - && ([mySQLConnection serverMajorVersion] > 4 - || ([mySQLConnection serverMajorVersion] == 4 && [mySQLConnection serverMinorVersion] >= 1))) + if ([encodings count] > 0 && ([mySQLConnection serverVersionIsGreaterThanOrEqualTo:4 minorVersion:1 releaseVersion:0])) { [[newTableInfoEncodingPopup menu] addItem:[NSMenuItem separatorItem]]; for (NSDictionary *encoding in encodings) { diff --git a/Source/SPServerSupport.m b/Source/SPServerSupport.m index a3b2a1a7..90a47b73 100644 --- a/Source/SPServerSupport.m +++ b/Source/SPServerSupport.m @@ -95,10 +95,9 @@ - (id)initWithMajorVersion:(NSInteger)majorVersion minor:(NSInteger)minorVersion release:(NSInteger)releaseVersion { if ((self = [super init])) { - // Might be NSNotFound if unknown. unknown should also lose against "0". - serverMajorVersion = (majorVersion != NSNotFound)? majorVersion : -1; - serverMinorVersion = (minorVersion != NSNotFound)? minorVersion : -1; - serverReleaseVersion = (releaseVersion != NSNotFound)? releaseVersion : -1; + serverMajorVersion = majorVersion; + serverMinorVersion = minorVersion; + serverReleaseVersion = releaseVersion; // Determine what the server supports [self evaluate]; @@ -363,9 +362,9 @@ - (void)dealloc { // Reset version integers - serverMajorVersion = -1; - serverMinorVersion = -1; - serverReleaseVersion = -1; + serverMajorVersion = 0; + serverMinorVersion = 0; + serverReleaseVersion = 0; // Invalidate all ivars [self _invalidate]; -- cgit v1.2.3 From b5be6b8b785ecd25a7366afd541844ee78f4420d Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 30 Oct 2015 02:50:18 +0100 Subject: * Add a wrapper method for 3 repeatetly used calls --- Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h | 1 + .../SPMySQLConnection Categories/Querying & Preparation.m | 13 ++++++++++++- Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m | 7 ++----- .../SPMySQLFramework/Source/SPMySQLFastStreamingResult.m | 4 +--- .../SPMySQLFramework/Source/SPMySQLStreamingResultStore.m | 4 +--- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h b/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h index ec85f92b..1e2a8c14 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h +++ b/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h @@ -82,6 +82,7 @@ @interface SPMySQLConnection (Querying_and_Preparation_Private_API) - (void)_flushMultipleResultSets; +- (void)_updateLastErrorInfos; - (void)_updateLastErrorMessage:(NSString *)theErrorMessage; - (void)_updateLastErrorID:(NSUInteger)theErrorID; - (void)_updateLastSqlstate:(NSString *)theSqlstate; diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m index 2abb8a35..f001d332 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m @@ -72,11 +72,12 @@ } return nil; } - if (![self _checkConnectionIfNecessary]) return nil; // Ensure per-thread variables are set up [self _validateThreadSetup]; + if (![self _checkConnectionIfNecessary]) return nil; + // Perform a lossy conversion to bytes, using NSData to do the hard work. Preserves // nul characters correctly. NSData *cData = [theString dataUsingEncoding:stringEncoding allowLossyConversion:YES]; @@ -665,6 +666,16 @@ } } +/** + * Update lastErrorID, lastErrorMessage and lastSqlstate from connection + */ +- (void)_updateLastErrorInfos +{ + [self _updateLastErrorID:NSNotFound]; + [self _updateLastErrorMessage:nil]; + [self _updateLastSqlstate:nil]; +} + /** * Update the MySQL error message for this connection. If an error is supplied * it will be stored and returned to anything asking the instance for the last diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m index 1ca2f182..452844ab 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m @@ -503,9 +503,7 @@ asm(".desc ___crashreporter_info__, 0x10"); keepAlivePingFailures = 0; // Clear the connection error record - [self _updateLastErrorID:NSNotFound]; - [self _updateLastErrorMessage:nil]; - [self _updateLastSqlstate:nil]; + [self _updateLastErrorInfos]; // Unlock the connection [self _unlockConnection]; @@ -1028,7 +1026,6 @@ asm(".desc ___crashreporter_info__, 0x10"); */ - (void)_validateThreadSetup { - // Check to see whether the handler has already been installed if (pthread_getspecific(mySQLThreadInitFlagKey)) return; @@ -1039,7 +1036,7 @@ asm(".desc ___crashreporter_info__, 0x10"); 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]]; + [[NSNotificationCenter defaultCenter] addObserver:[self class] selector:@selector(_removeThreadVariables:) name:NSThreadWillExitNotification object:[NSThread currentThread]]; } /** diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.m b/Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.m index 930180da..53ab116f 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.m @@ -392,9 +392,7 @@ typedef struct st_spmysqlstreamingrowdata { } // Update the connection's error statuses to reflect any errors during the content download - [parentConnection _updateLastErrorID:NSNotFound]; - [parentConnection _updateLastErrorMessage:nil]; - [parentConnection _updateLastSqlstate:nil]; + [parentConnection _updateLastErrorInfos]; // Unlock the parent connection now all data has been retrieved [parentConnection _unlockConnection]; diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResultStore.m b/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResultStore.m index 86a6b2b5..29f83e0e 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResultStore.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResultStore.m @@ -809,9 +809,7 @@ static inline void SPMySQLStreamingResultStoreFreeRowData(SPMySQLStreamingResult } // Update the connection's error statuses to reflect any errors during the content download - [parentConnection _updateLastErrorID:NSNotFound]; - [parentConnection _updateLastErrorMessage:nil]; - [parentConnection _updateLastSqlstate:nil]; + [parentConnection _updateLastErrorInfos]; // Unlock the parent connection now all data has been retrieved [parentConnection _unlockConnection]; -- cgit v1.2.3 From d5dd74e8f3b1a73b125fa66db7b08b269933819d Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 31 Oct 2015 01:16:52 +0100 Subject: Add method to get charset from collation --- Source/SPDatabaseData.h | 1 + Source/SPDatabaseData.m | 45 ++++++++++++++++++++++++++++++++------------- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/Source/SPDatabaseData.h b/Source/SPDatabaseData.h index f2fe08e1..cdc44fa3 100644 --- a/Source/SPDatabaseData.h +++ b/Source/SPDatabaseData.h @@ -74,6 +74,7 @@ - (NSArray *)getDatabaseCollations; - (NSArray *)getDatabaseCollationsForEncoding:(NSString *)encoding; - (NSString *)getDefaultCollationForEncoding:(NSString *)encoding; +- (NSString *)getEncodingFromCollation:(NSString *)collation; - (NSArray *)getDatabaseStorageEngines; - (NSArray *)getDatabaseCharacterSetEncodings; diff --git a/Source/SPDatabaseData.m b/Source/SPDatabaseData.m index 2d1d4f14..0cbb756a 100644 --- a/Source/SPDatabaseData.m +++ b/Source/SPDatabaseData.m @@ -130,7 +130,7 @@ NSInteger _sortStorageEngineEntry(NSDictionary *itemOne, NSDictionary *itemTwo, ++c; } - while (c[0].nr != 0); + while (c->nr != 0); } } @@ -180,12 +180,14 @@ NSInteger _sortStorageEngineEntry(NSDictionary *itemOne, NSDictionary *itemTwo, NSString *charSet = [NSString stringWithCString:c->name encoding:NSUTF8StringEncoding]; if ([charSet isEqualToString:characterSetEncoding]) { - [characterSetCollations addObject:@{@"COLLATION_NAME" : [NSString stringWithCString:c->collation encoding:NSUTF8StringEncoding]}]; + [characterSetCollations addObject:@{ + @"COLLATION_NAME" : [NSString stringWithCString:c->collation encoding:NSUTF8StringEncoding] + }]; } ++c; } - while (c[0].nr != 0); + while (c->nr != 0); } if ([characterSetCollations count]) { @@ -222,6 +224,23 @@ copy_return: return defaultCollationForCharacterSet; } +/** Get the name of the mysql charset a given collation belongs to. + * @param collation Name of the collation (e.g. "latin1_swedish_ci") + * @return name of the charset (e.g. "latin1") or nil if unknown + * + * According to the MySQL doc every collation can only ever belong to a single charset. + */ +- (NSString *)getEncodingFromCollation:(NSString *)collation { + if([collation length]) { //shortcut for nil and @"" + for(NSDictionary *coll in [self getDatabaseCollations]) { + if([[coll objectForKey:@"COLLATION_NAME"] isEqualToString:collation]) { + return [coll objectForKey:@"CHARACTER_SET_NAME"]; + } + } + } + return nil; +} + /** * Returns all of the database's available storage engines. */ @@ -321,11 +340,11 @@ copy_return: for (NSDictionary *anEncoding in supportedEncodings) { NSDictionary *convertedEncoding = [NSDictionary dictionaryWithObjectsAndKeys: - [anEncoding objectForKey:@"Charset"], @"CHARACTER_SET_NAME", - [anEncoding objectForKey:@"Description"], @"DESCRIPTION", - [anEncoding objectForKey:@"Default collation"], @"DEFAULT_COLLATE_NAME", - [anEncoding objectForKey:@"Maxlen"], @"MAXLEN", - nil]; + [anEncoding objectForKey:@"Charset"], @"CHARACTER_SET_NAME", + [anEncoding objectForKey:@"Description"], @"DESCRIPTION", + [anEncoding objectForKey:@"Default collation"], @"DEFAULT_COLLATE_NAME", + [anEncoding objectForKey:@"Maxlen"], @"MAXLEN", + nil]; [characterSetEncodings addObject:convertedEncoding]; } @@ -336,14 +355,14 @@ copy_return: const SPDatabaseCharSets *c = SPGetDatabaseCharacterSets(); #warning This probably won't work as intended. See my comment in getDatabaseCollationsForEncoding: do { - [characterSetEncodings addObject:[NSDictionary dictionaryWithObjectsAndKeys: - [NSString stringWithCString:c->name encoding:NSUTF8StringEncoding], @"CHARACTER_SET_NAME", - [NSString stringWithCString:c->description encoding:NSUTF8StringEncoding], @"DESCRIPTION", - nil]]; + [characterSetEncodings addObject:@{ + @"CHARACTER_SET_NAME" : [NSString stringWithCString:c->name encoding:NSUTF8StringEncoding], + @"DESCRIPTION" : [NSString stringWithCString:c->description encoding:NSUTF8StringEncoding] + }]; ++c; } - while (c[0].nr != 0); + while (c->nr != 0); } } -- cgit v1.2.3 From 57955871fb174eefc56dfedcd8222d2e68272ca5 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 31 Oct 2015 01:26:59 +0100 Subject: Minimal refactoring --- Source/SPExportSettingsPersistence.h | 2 +- Source/SPExtendedTableInfo.m | 2 +- Source/SPIndexesController.m | 4 ++-- Source/SPTableContent.m | 22 +++++++++++----------- Source/SPTableStructureDelegate.m | 25 +++++++++++-------------- 5 files changed, 26 insertions(+), 29 deletions(-) diff --git a/Source/SPExportSettingsPersistence.h b/Source/SPExportSettingsPersistence.h index 2f9104c4..90f29cfe 100644 --- a/Source/SPExportSettingsPersistence.h +++ b/Source/SPExportSettingsPersistence.h @@ -39,6 +39,6 @@ /** * @return A serialized form of the "custom filename" field */ -- (NSDictionary *)currentCustomFilenameAsArray; +- (NSArray *)currentCustomFilenameAsArray; @end diff --git a/Source/SPExtendedTableInfo.m b/Source/SPExtendedTableInfo.m index 92b845e9..26574621 100644 --- a/Source/SPExtendedTableInfo.m +++ b/Source/SPExtendedTableInfo.m @@ -119,7 +119,7 @@ static NSString *SPMySQLCommentField = @"Comment"; if ([currentType isEqualToString:newType]) return; // If the table is empty, perform the change directly - if ([[[tableDataInstance statusValues] objectForKey:SPMySQLRowsField] isEqualToString:@"0"]) { + if ([[tableDataInstance statusValueForKey:SPMySQLRowsField] isEqualToString:@"0"]) { [self _changeCurrentTableTypeFrom:currentType to:newType]; return; } diff --git a/Source/SPIndexesController.m b/Source/SPIndexesController.m index a885ff01..5b239ec0 100644 --- a/Source/SPIndexesController.m +++ b/Source/SPIndexesController.m @@ -166,8 +166,8 @@ static const NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize"; // Check whether a save of the current field row is required. if (![tableStructure saveRowOnDeselect]) return; - isMyISAMTable = [[[tableData statusValues] objectForKey:@"Engine"] isEqualToString:@"MyISAM"]; - isInnoDBTable = [[[tableData statusValues] objectForKey:@"Engine"] isEqualToString:@"InnoDB"]; + isMyISAMTable = [[tableData statusValueForKey:@"Engine"] isEqualToString:@"MyISAM"]; + isInnoDBTable = [[tableData statusValueForKey:@"Engine"] isEqualToString:@"InnoDB"]; // Reset visibility of the primary key item [[[indexTypePopUpButton menu] itemWithTag:SPPrimaryKeyMenuTag] setHidden:NO]; diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m index 2caa80da..14bae14a 100644 --- a/Source/SPTableContent.m +++ b/Source/SPTableContent.m @@ -320,7 +320,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper // Attempt to retrieve the table encoding; if that fails (indicating an error occurred // while retrieving table data), or if the Rows variable is null, clear and return - if (![tableDataInstance tableEncoding] || [[[tableDataInstance statusValues] objectForKey:@"Rows"] isNSNull]) { + if (![tableDataInstance tableEncoding] || [[tableDataInstance statusValueForKey:@"Rows"] isNSNull]) { [[self onMainThread] setTableDetails:nil]; return; } @@ -3992,16 +3992,16 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper // If the state is now accurate, use it if ([[tableDataInstance statusValueForKey:@"RowsCountAccurate"] boolValue]) { - maxNumRows = [[tableDataInstance statusValueForKey:@"Rows"] integerValue]; - maxNumRowsIsEstimate = NO; - checkStatusCount = YES; + maxNumRows = [[tableDataInstance statusValueForKey:@"Rows"] integerValue]; + maxNumRowsIsEstimate = NO; + checkStatusCount = YES; - // Otherwise, use the estimate count - } else { - maxNumRows = [[tableDataInstance statusValueForKey:@"Rows"] integerValue]; - maxNumRowsIsEstimate = YES; - checkStatusCount = YES; - } + // Otherwise, use the estimate count + } else { + maxNumRows = [[tableDataInstance statusValueForKey:@"Rows"] integerValue]; + maxNumRowsIsEstimate = YES; + checkStatusCount = YES; + } } // Check whether the estimated count requires updating, ie if the retrieved count exceeds it @@ -4102,7 +4102,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper return; #endif - if ( ![[[tableDataInstance statusValues] objectForKey:@"Rows"] isNSNull] && selectedTable && [selectedTable length] && [tableDataInstance tableEncoding]) { + if ( ![[tableDataInstance statusValueForKey:@"Rows"] isNSNull] && selectedTable && [selectedTable length] && [tableDataInstance tableEncoding]) { [addButton setEnabled:([tablesListInstance tableType] == SPTableTypeTable)]; [self updatePaginationState]; [reloadButton setEnabled:YES]; diff --git a/Source/SPTableStructureDelegate.m b/Source/SPTableStructureDelegate.m index 1e470e79..b8db3bff 100644 --- a/Source/SPTableStructureDelegate.m +++ b/Source/SPTableStructureDelegate.m @@ -146,17 +146,17 @@ - (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { - // Make sure that the drag operation is for the right table view + // Make sure that the operation is for the right table view if (aTableView != tableSourceView) return; + + NSMutableDictionary *currentRow = NSArrayObjectAtIndex(tableFields,rowIndex); if (!isEditingRow) { - [oldRow setDictionary:[tableFields objectAtIndex:rowIndex]]; + [oldRow setDictionary:currentRow]; isEditingRow = YES; currentlyEditingRow = rowIndex; } - - NSMutableDictionary *currentRow = [tableFields objectAtIndex:rowIndex]; - + // Reset collation if encoding was changed if ([[aTableColumn identifier] isEqualToString:@"encoding"]) { if ([[currentRow objectForKey:@"encoding"] integerValue] != [anObject integerValue]) { @@ -286,7 +286,7 @@ - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id )info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)operation { // Make sure that the drag operation is for the right table view - if (tableView!=tableSourceView) return NO; + if (tableView!=tableSourceView) return NSDragOperationNone; NSArray *pboardTypes = [[info draggingPasteboard] types]; NSInteger originalRow; @@ -457,10 +457,8 @@ */ - (void)tableViewSelectionDidChange:(NSNotification *)aNotification { - id object = [aNotification object]; - // Check for which table view the selection changed - if (object == tableSourceView) { + if ([aNotification object] == tableSourceView) { // If we are editing a row, attempt to save that row - if saving failed, reselect the edit row. if (isEditingRow && [tableSourceView selectedRow] != currentlyEditingRow && ![self saveRowOnDeselect]) return; @@ -557,10 +555,9 @@ [self cancelRowEditing]; return YES; - } - else { - return NO; } + + return NO; } /** @@ -577,7 +574,7 @@ } else { // Validate cell against current field type - NSString *rowType = @""; + NSString *rowType; NSDictionary *row = NSArrayObjectAtIndex(tableFields, rowIndex); if ((rowType = [row objectForKey:@"type"])) { @@ -692,7 +689,7 @@ NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF BEGINSWITH[c] %@", [uncompletedString uppercaseString]]; NSArray *result = [typeSuggestions filteredArrayUsingPredicate:predicate]; - if (result && [result count]) return [result objectAtIndex:0]; + if ([result count]) return [result objectAtIndex:0]; return @""; } -- cgit v1.2.3 From 2f2aafb4f5675282a37d16dce96027706096df40 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 31 Oct 2015 02:54:59 +0100 Subject: * Basic math is hard sometimes (fixes an issue introduced by me in b2d798ba9282d3acf1a2d65de30849e529d4d255) * Fix an exception that could occur when trying to view a damaged table * Fix a theoretical use-after-free issue by a wrongly structured retain/release in a setter --- .../Source/SPMySQLConnection Categories/Server Info.m | 2 +- Source/SPDatabaseData.m | 3 +++ Source/SPDatabaseDocument.m | 6 +++--- Source/SPTableContent.m | 14 ++++++++------ Source/SPTableData.m | 14 ++++++++++++-- 5 files changed, 27 insertions(+), 12 deletions(-) diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m index 01410eb5..5925d138 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m @@ -87,7 +87,7 @@ { unsigned long myver = aMajorVersion * 10000 + aMinorVersion * 100 + aReleaseVersion; - return (myver >= serverVersionNumber); + return (serverVersionNumber >= myver); } #pragma mark - diff --git a/Source/SPDatabaseData.m b/Source/SPDatabaseData.m index 0cbb756a..965dfcb5 100644 --- a/Source/SPDatabaseData.m +++ b/Source/SPDatabaseData.m @@ -471,6 +471,9 @@ copy_return: [result setReturnDataAsStrings:YES]; + if([connection queryErrored]) + SPLog(@"server variable lookup failed for '%@': %@ (%lu)",variable,[connection lastErrorMessage],[connection lastErrorID]); + if ([result numberOfRows] != 1) return nil; diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index 2f7a5235..76dda407 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -1729,20 +1729,20 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; { _supportsEncoding = YES; - NSString *mysqlEncoding = [databaseDataInstance getDatabaseDefaultCharacterSet]; + NSString *mysqlEncoding = [[databaseDataInstance getDatabaseDefaultCharacterSet] retain]; SPClear(selectedDatabaseEncoding); // Fallback or older version? -> set encoding to mysql default encoding latin1 if ( !mysqlEncoding ) { - NSLog(@"Error: no character encoding found, mysql version is %@", [self mySQLVersion]); + NSLog(@"Error: no character encoding found for db, mysql version is %@", [self mySQLVersion]); selectedDatabaseEncoding = [[NSString alloc] initWithString:@"latin1"]; _supportsEncoding = NO; } else { - selectedDatabaseEncoding = [mysqlEncoding retain]; + selectedDatabaseEncoding = mysqlEncoding; } } diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m index 14bae14a..095f89ee 100644 --- a/Source/SPTableContent.m +++ b/Source/SPTableContent.m @@ -434,7 +434,8 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper [tableContentView scrollColumnToVisible:0]; // Set the maximum table rows to an estimated count pre-load - maxNumRows = [[tableDataInstance statusValueForKey:@"Rows"] integerValue]; + NSString *rows = [tableDataInstance statusValueForKey:@"Rows"]; + maxNumRows = (rows && ![rows isNSNull])? [rows integerValue] : 0; maxNumRowsIsEstimate = YES; } @@ -3991,14 +3992,15 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper [tableDataInstance updateAccurateNumberOfRowsForCurrentTableForcingUpdate:NO]; // If the state is now accurate, use it + NSString *rows = [tableDataInstance statusValueForKey:@"Rows"]; if ([[tableDataInstance statusValueForKey:@"RowsCountAccurate"] boolValue]) { - maxNumRows = [[tableDataInstance statusValueForKey:@"Rows"] integerValue]; + maxNumRows = [rows integerValue]; maxNumRowsIsEstimate = NO; checkStatusCount = YES; - - // Otherwise, use the estimate count - } else { - maxNumRows = [[tableDataInstance statusValueForKey:@"Rows"] integerValue]; + } + // Otherwise, use the estimate count + else { + maxNumRows = (rows && ![rows isNSNull])? [rows integerValue] : 0; maxNumRowsIsEstimate = YES; checkStatusCount = YES; } diff --git a/Source/SPTableData.m b/Source/SPTableData.m index a40225d0..51c6a274 100644 --- a/Source/SPTableData.m +++ b/Source/SPTableData.m @@ -1004,7 +1004,7 @@ SPOnewayAlertSheet( NSLocalizedString(@"Error", @"error"), [NSApp mainWindow], - [NSString stringWithFormat:NSLocalizedString(@"An error occured while retrieving status data.\nMySQL said: %@", @"message of panel when retrieving view information failed"), [mySQLConnection lastErrorMessage]] + [NSString stringWithFormat:NSLocalizedString(@"An error occured while retrieving status data.\n\nMySQL said: %@", @"message of panel when retrieving view information failed"), [mySQLConnection lastErrorMessage]] ); if (changeEncoding) [mySQLConnection restoreStoredEncoding]; } @@ -1041,9 +1041,19 @@ // this happens e.g. for db "information_schema" if([[status objectForKey:@"Rows"] isNSNull]) { tableStatusResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SELECT COUNT(1) FROM %@", [escapedTableName backtickQuotedString] ]]; - if (![mySQLConnection queryErrored]) + // this query can fail e.g. if a table is damaged + if (tableStatusResult && ![mySQLConnection queryErrored]) { [status setObject:[[tableStatusResult getRowAsArray] objectAtIndex:0] forKey:@"Rows"]; [status setObject:@"y" forKey:@"RowsCountAccurate"]; + } + else { + //FIXME that error should really show only when trying to view the table content, but we don't even try to load that if Rows==NULL + SPOnewayAlertSheet( + NSLocalizedString(@"Querying row count failed", @"table status : row count query failed : error title"), + [NSApp mainWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occured while trying to determine the number of rows for “%@”.\nMySQL said: %@ (%lu)", @"table status : row count query failed : error message"),[tableListInstance tableName],[mySQLConnection lastErrorMessage],[mySQLConnection lastErrorID]] + ); + } } } -- cgit v1.2.3 From 7e74bcb74154eb892ad703b32adbe07fc6fef13b Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 1 Nov 2015 06:18:37 +0100 Subject: Fix an exception on 10.11 when trying to install an SP update while a save dialog is displayed (may have afftected other modal dialogs) --- Source/SPAppController.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/SPAppController.m b/Source/SPAppController.m index 3458634a..35b5ab96 100644 --- a/Source/SPAppController.m +++ b/Source/SPAppController.m @@ -2193,6 +2193,9 @@ */ - (void)updaterWillRelaunchApplication:(SUUpdater *)updater { + // Sparkle might call this on a background thread, but calling endSheet: from a bg thread is unhealthy + if(![NSThread isMainThread]) return [[self onMainThread] updaterWillRelaunchApplication:updater]; + // Get all the currently open windows and their attached sheets if any NSArray *windows = [NSApp windows]; -- cgit v1.2.3 From f02fb787063caabe246a0ee420394f5676c55a9c Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 1 Nov 2015 18:06:45 +0100 Subject: Change the internal handling of charset/collation from offset-based to name-based in structure view This is a major change to the charset/collation code. Please watch out for issues! (part of #2237) --- Source/SPTableStructure.m | 36 ++++----- Source/SPTableStructureDelegate.m | 164 +++++++++++++++++++------------------- Source/SPTableStructureLoading.m | 154 ++++++++++++++--------------------- 3 files changed, 161 insertions(+), 193 deletions(-) diff --git a/Source/SPTableStructure.m b/Source/SPTableStructure.m index 9973bf4b..319e74ec 100644 --- a/Source/SPTableStructure.m +++ b/Source/SPTableStructure.m @@ -46,6 +46,7 @@ #import "SPTableStructureLoading.h" #import "SPThreadAdditions.h" #import "SPServerSupport.h" +#import "SPExtendedTableInfo.h" #import @@ -252,8 +253,8 @@ static NSString *SPRemoveFieldAndForeignKey = @"SPRemoveFieldAndForeignKey"; BOOL allowNull = [[[tableDataInstance statusValueForKey:@"Engine"] uppercaseString] isEqualToString:@"CSV"] ? NO : [prefs boolForKey:SPNewFieldsAllowNulls]; [tableFields insertObject:[NSMutableDictionary - dictionaryWithObjects:[NSArray arrayWithObjects:@"", @"INT", @"", @"0", @"0", @"0", allowNull ? @"1" : @"0", @"", [prefs stringForKey:SPNullValue], @"None", @"", @0, @0, nil] - forKeys:@[@"name", @"type", @"length", @"unsigned", @"zerofill", @"binary", @"null", @"Key", @"default", @"Extra", @"comment", @"encoding", @"collation"]] + dictionaryWithObjects:[NSArray arrayWithObjects:@"", @"INT", @"", @"0", @"0", @"0", allowNull ? @"1" : @"0", @"", [prefs stringForKey:SPNullValue], @"None", @"", nil] + forKeys:@[@"name", @"type", @"length", @"unsigned", @"zerofill", @"binary", @"null", @"Key", @"default", @"Extra", @"comment"]] atIndex:insertIndex]; #else [tableFields insertObject:[NSMutableDictionary @@ -810,30 +811,25 @@ static NSString *SPRemoveFieldAndForeignKey = @"SPRemoveFieldAndForeignKey"; if ([fieldValidation isFieldTypeString:theRowType]) { + BOOL charsetSupport = [[tableDocumentInstance serverSupport] supportsPost41CharacterSetHandling]; + // Add CHARSET - NSString *fieldEncoding = @""; - if([[theRow objectForKey:@"encoding"] integerValue] > 0 && [[tableDocumentInstance serverSupport] supportsPost41CharacterSetHandling]) { - NSString *enc = [[encodingPopupCell itemAtIndex:[[theRow objectForKey:@"encoding"] integerValue]] title]; - NSInteger start = [enc rangeOfString:@"("].location+1; - NSInteger end = [enc length] - start - 1; - fieldEncoding = [enc substringWithRange:NSMakeRange(start, end)]; + NSString *fieldEncoding = [theRow objectForKey:@"encodingName"]; + if(charsetSupport && [fieldEncoding length]) { [queryString appendFormat:@"\n CHARACTER SET %@", fieldEncoding]; } - // Remember CHARSET for COLLATE - if(![fieldEncoding length] && [tableDataInstance tableEncoding]) { - fieldEncoding = [tableDataInstance tableEncoding]; - } - - // ADD COLLATE - if([fieldEncoding length] && [[theRow objectForKey:@"collation"] integerValue] > 0 && ![[theRow objectForKey:@"binary"] integerValue]) { - NSArray *theCollations = [databaseDataInstance getDatabaseCollationsForEncoding:fieldEncoding]; - NSString *col = [[theCollations objectAtIndex:[[theRow objectForKey:@"collation"] integerValue]-1] objectForKey:@"COLLATION_NAME"]; - [queryString appendFormat:@"\n COLLATE %@", col]; - } - if ( [[theRow objectForKey:@"binary"] integerValue] == 1) { + if ([[theRow objectForKey:@"binary"] integerValue] == 1) { [queryString appendString:@"\n BINARY"]; } + else { + // ADD COLLATE + // Note: a collate without charset is valid in MySQL. The charset can be determined from a collation. + NSString *fieldCollation = [theRow objectForKey:@"collationName"]; + if(charsetSupport && [fieldCollation length]) { + [queryString appendFormat:@"\n COLLATE %@", fieldCollation]; + } + } } else if ([fieldValidation isFieldTypeNumeric:theRowType] && (![theRowType isEqualToString:@"BIT"])) { diff --git a/Source/SPTableStructureDelegate.m b/Source/SPTableStructureDelegate.m index b8db3bff..4bfd84c8 100644 --- a/Source/SPTableStructureDelegate.m +++ b/Source/SPTableStructureDelegate.m @@ -37,6 +37,7 @@ #import "SPTableFieldValidation.h" #import "SPTableStructureLoading.h" #import "SPServerSupport.h" +#import "SPTablesList.h" #import @@ -60,72 +61,68 @@ { // Return a placeholder if the table is reloading if ((NSUInteger)rowIndex >= [tableFields count]) return @"..."; + + NSDictionary *rowData = NSArrayObjectAtIndex(tableFields, rowIndex); if ([[tableColumn identifier] isEqualToString:@"collation"]) { NSString *tableEncoding = [tableDataInstance tableEncoding]; - NSString *columnEncoding = nil; - NSInteger idx = 0; - - if ((idx = [[NSArrayObjectAtIndex(tableFields, rowIndex) objectForKey:@"encoding"] integerValue]) > 0 && idx < [encodingPopupCell numberOfItems]) { - NSString *enc = [[encodingPopupCell itemAtIndex:idx] title]; + NSString *columnEncoding = [rowData objectForKey:@"encodingName"]; + NSString *columnCollation = [rowData objectForKey:@"collationName"]; // loadTable: has already inferred it, if not set explicit - NSUInteger start = [enc rangeOfString:@"("].location + 1; - - columnEncoding = [enc substringWithRange:NSMakeRange(start, [enc length] - start - 1)]; - collations = [databaseDataInstance getDatabaseCollationsForEncoding:columnEncoding]; - } - else { - // If the structure has loaded (not still loading!) and the table encoding - // is set, use the appropriate collations. - collations = @[]; - if([tableDocumentInstance structureLoaded]) { - columnEncoding = [tableDataInstance tableEncoding]; - if(columnEncoding) collations = [databaseDataInstance getDatabaseCollationsForEncoding:columnEncoding]; - } - } - NSPopUpButtonCell *collationCell = [tableColumn dataCell]; - [collationCell removeAllItems]; - - if ([collations count] > 0) { - NSString *tableCollation = [[tableDataInstance statusValues] objectForKey:@"Collation"]; - - if (![tableCollation length]) { - tableCollation = [databaseDataInstance getDefaultCollationForEncoding:tableEncoding]; - } - - NSString *columnCollation = [NSArrayObjectAtIndex(tableFields, rowIndex) objectForKey:@"collationName"]; - - if (![columnCollation length]) { - columnCollation = [databaseDataInstance getDefaultCollationForEncoding:columnEncoding]; - } - - [[tableColumn dataCell] addItemWithTitle:@""]; + [collationCell addItemWithTitle:@"dummy"]; + //copy the default style of menu items and add gray color for default item + NSMutableDictionary *menuAttrs = [NSMutableDictionary dictionaryWithDictionary:[[collationCell attributedTitle] attributesAtIndex:0 effectiveRange:NULL]]; + [menuAttrs setObject:[NSColor lightGrayColor] forKey:NSForegroundColorAttributeName]; + [[collationCell lastItem] setTitle:@""]; + + //if this is not set the column either has no encoding (numeric etc.) or retrieval failed. Either way we can't provide collations + if(columnEncoding) { + collations = [databaseDataInstance getDatabaseCollationsForEncoding:columnEncoding]; - BOOL useMonospacedFont = [prefs boolForKey:SPUseMonospacedFonts]; - CGFloat monospacedFontSize = [prefs floatForKey:SPMonospacedFontSize] > 0 ? [prefs floatForKey:SPMonospacedFontSize] : [NSFont smallSystemFontSize]; - NSMutableDictionary *menuAttributes = [NSMutableDictionary dictionaryWithObject:[NSColor lightGrayColor] forKey:NSForegroundColorAttributeName]; - [menuAttributes setObject:useMonospacedFont ? [NSFont fontWithName:SPDefaultMonospacedFontName size:monospacedFontSize] : [NSFont systemFontOfSize:[NSFont smallSystemFontSize]] forKey:NSFontAttributeName]; - - BOOL columnUsesTableDefaultEncoding = ([columnEncoding isEqualToString:tableEncoding]); - // Populate collation popup button - for (NSDictionary *collation in collations) - { - NSString *collationName = [collation objectForKey:@"COLLATION_NAME"]; - - [[tableColumn dataCell] addItemWithTitle:collationName]; + if ([collations count] > 0) { + NSString *tableCollation = [[tableDataInstance statusValues] objectForKey:@"Collation"]; - // If this matches the table's collation, draw in gray - if (columnUsesTableDefaultEncoding && [collationName isEqualToString:tableCollation]) { - NSMenuItem *collationMenuItem = [(NSPopUpButtonCell *)[tableColumn dataCell] itemAtIndex:([[tableColumn dataCell] numberOfItems] - 1)]; + if (![tableCollation length]) { + tableCollation = [databaseDataInstance getDefaultCollationForEncoding:tableEncoding]; + } - NSAttributedString *itemString = [[[NSAttributedString alloc] initWithString:collationName attributes:menuAttributes] autorelease]; - - [collationMenuItem setAttributedTitle:itemString]; + BOOL columnUsesTableDefaultEncoding = ([columnEncoding isEqualToString:tableEncoding]); + // Populate collation popup button + for (NSDictionary *collation in collations) + { + NSString *collationName = [collation objectForKey:@"COLLATION_NAME"]; + + [collationCell addItemWithTitle:collationName]; + NSMenuItem *item = [collationCell lastItem]; + [item setRepresentedObject:collationName]; + + // If this matches the table's collation, draw in gray + if (columnUsesTableDefaultEncoding && [collationName isEqualToString:tableCollation]) { + NSAttributedString *itemString = [[NSAttributedString alloc] initWithString:[item title] attributes:menuAttrs]; + [item setAttributedTitle:[itemString autorelease]]; + } } + + //look up the right item + NSInteger idx = [collationCell indexOfItemWithRepresentedObject:columnCollation]; + if(idx > 0) return @(idx); } } + + return @0; + } + else if ([[tableColumn identifier] isEqualToString:@"encoding"]) { + // the encoding menu was already configured during setTableDetails: + NSString *columnEncoding = [rowData objectForKey:@"encodingName"]; + + if(columnEncoding) { + NSInteger idx = [encodingPopupCell indexOfItemWithRepresentedObject:columnEncoding]; + if(idx > 0) return @(idx); + } + + return @0; } else if ([[tableColumn identifier] isEqualToString:@"Extra"]) { id dataCell = [tableColumn dataCell]; @@ -141,7 +138,7 @@ } } - return [NSArrayObjectAtIndex(tableFields, rowIndex) objectForKey:[tableColumn identifier]]; + return [rowData objectForKey:[tableColumn identifier]]; } - (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex @@ -159,16 +156,32 @@ // Reset collation if encoding was changed if ([[aTableColumn identifier] isEqualToString:@"encoding"]) { - if ([[currentRow objectForKey:@"encoding"] integerValue] != [anObject integerValue]) { - [currentRow setObject:@0 forKey:@"collation"]; + NSString *oldEncoding = [currentRow objectForKey:@"encodingName"]; + NSString *newEncoding = [[encodingPopupCell itemAtIndex:[anObject integerValue]] representedObject]; + if (![oldEncoding isEqualToString:newEncoding]) { + [currentRow removeObjectForKey:@"collationName"]; [tableSourceView reloadData]; } + if(!newEncoding) + [currentRow removeObjectForKey:@"encodingName"]; + else + [currentRow setObject:newEncoding forKey:@"encodingName"]; + return; + } + else if ([[aTableColumn identifier] isEqualToString:@"collation"]) { + NSString *newCollation = [[(NSPopUpButtonCell *)[aTableColumn dataCell] itemAtIndex:[anObject integerValue]] representedObject]; + + if(!newCollation) + [currentRow removeObjectForKey:@"collationName"]; + else + [currentRow setObject:newCollation forKey:@"collationName"]; + return; } // Reset collation if BINARY was set changed, as enabling BINARY sets collation to *_bin else if ([[aTableColumn identifier] isEqualToString:@"binary"]) { if ([[currentRow objectForKey:@"binary"] integerValue] != [anObject integerValue]) { - [currentRow setObject:@0 forKey:@"collation"]; - + [currentRow removeObjectForKey:@"collationName"]; + [tableSourceView reloadData]; } } @@ -220,9 +233,8 @@ [tableSourceView reloadData]; } } - // Store new value but not if user choose "---" for type and reset values if required - if ([[aTableColumn identifier] isEqualToString:@"type"]) { + else if ([[aTableColumn identifier] isEqualToString:@"type"]) { if (anObject && [(NSString*)anObject length] && ![(NSString*)anObject hasPrefix:@"--"]) { [currentRow setObject:[(NSString*)anObject uppercaseString] forKey:@"type"]; @@ -238,10 +250,10 @@ [tableSourceView reloadData]; } + return; } - else { - [currentRow setObject:(anObject) ? anObject : @"" forKey:[aTableColumn identifier]]; - } + + [currentRow setObject:(anObject) ? anObject : @"" forKey:[aTableColumn identifier]]; } /** @@ -270,7 +282,7 @@ if ([rows count] == 1) { [pboard declareTypes:@[SPDefaultPasteboardDragType] owner:nil]; - [pboard setString:[[NSNumber numberWithInteger:[rows firstIndex]] stringValue] forType:SPDefaultPasteboardDragType]; + [pboard setString:[NSString stringWithFormat:@"%lu",[rows firstIndex]] forType:SPDefaultPasteboardDragType]; return YES; } @@ -332,27 +344,19 @@ [queryString appendFormat:@"(%@)", [originalRow objectForKey:@"length"]]; } - NSString *fieldEncoding = @""; - - if ([[originalRow objectForKey:@"encoding"] integerValue] > 0 && [[tableDocumentInstance serverSupport] supportsPost41CharacterSetHandling]) { - NSString *enc = [[encodingPopupCell itemAtIndex:[[originalRow objectForKey:@"encoding"] integerValue]] title]; - - NSInteger start = [enc rangeOfString:@"("].location + 1; - - fieldEncoding = [enc substringWithRange:NSMakeRange(start, [enc length] - start - 1)]; - + NSString *fieldEncoding = [originalRow objectForKey:@"encodingName"]; + + if ([fieldEncoding length] && [[tableDocumentInstance serverSupport] supportsPost41CharacterSetHandling]) { [queryString appendFormat:@" CHARACTER SET %@", fieldEncoding]; } if (![fieldEncoding length] && [tableDataInstance tableEncoding]) { fieldEncoding = [tableDataInstance tableEncoding]; } - - if ([fieldEncoding length] && [[originalRow objectForKey:@"collation"] integerValue] > 0 && ![[originalRow objectForKey:@"binary"] integerValue]) { - NSArray *theCollations = [databaseDataInstance getDatabaseCollationsForEncoding:fieldEncoding]; - NSString *col = [[theCollations objectAtIndex:[[originalRow objectForKey:@"collation"] integerValue] - 1] objectForKey:@"COLLATION_NAME"]; - - [queryString appendFormat:@" COLLATE %@", col]; + + NSString *fieldCollation = [originalRow objectForKey:@"collationName"]; + if ([fieldEncoding length] && [fieldCollation length] && ![[originalRow objectForKey:@"binary"] integerValue]) { + [queryString appendFormat:@" COLLATE %@", fieldCollation]; } // Add unsigned, zerofill, binary, not null if necessary diff --git a/Source/SPTableStructureLoading.m b/Source/SPTableStructureLoading.m index 2c6d65fe..85b74a7c 100644 --- a/Source/SPTableStructureLoading.m +++ b/Source/SPTableStructureLoading.m @@ -36,6 +36,8 @@ #import "SPIndexesController.h" #import "SPTablesList.h" #import "SPThreadAdditions.h" +#import "SPTableView.h" +#import "SPFunctions.h" #import @@ -115,65 +117,47 @@ // Set up the encoding PopUpButtonCell NSArray *encodings = [databaseDataInstance getDatabaseCharacterSetEncodings]; - - if ([encodings count]) { - NSString *defaultEncodingDescription = nil; - - // Populate encoding popup button - NSMutableArray *encodingTitles = [[NSMutableArray alloc] initWithCapacity:[encodings count]+1]; - - [encodingTitles addObject:@""]; - - for (NSDictionary *encoding in encodings) - { - [encodingTitles addObject:(![encoding objectForKey:@"DESCRIPTION"]) ? [encoding objectForKey:@"CHARACTER_SET_NAME"] : [NSString stringWithFormat:@"%@ (%@)", [encoding objectForKey:@"DESCRIPTION"], [encoding objectForKey:@"CHARACTER_SET_NAME"]]]; - if ([[encoding objectForKey:@"CHARACTER_SET_NAME"] isEqualToString:[tableDataInstance tableEncoding]]) { - defaultEncodingDescription = [encodingTitles lastObject]; - } - } - - [[encodingPopupCell onMainThread] removeAllItems]; - [[encodingPopupCell onMainThread] addItemsWithTitles:encodingTitles]; - // Take the encoding that matches the table's encoding and gray it out - if (defaultEncodingDescription) { - NSMenuItem *tableEncodingMenuItem = [[encodingPopupCell menu] itemWithTitle:defaultEncodingDescription]; - NSMutableParagraphStyle *menuStyle = [[[NSMutableParagraphStyle alloc] init] autorelease]; - - [menuStyle setLineBreakMode:NSLineBreakByTruncatingTail]; - - NSMutableDictionary *menuAttributes = [NSMutableDictionary dictionaryWithObject:[NSColor lightGrayColor] forKey:NSForegroundColorAttributeName]; + SPMainQSync(^{ + [encodingPopupCell removeAllItems]; - CGFloat monospacedFontSize = [prefs floatForKey:SPMonospacedFontSize] > 0 ? [prefs floatForKey:SPMonospacedFontSize] : [NSFont smallSystemFontSize]; + if ([encodings count]) { - [menuAttributes setObject:[prefs boolForKey:SPUseMonospacedFonts] ? [NSFont fontWithName:SPDefaultMonospacedFontName size:monospacedFontSize] : [NSFont systemFontOfSize:[NSFont smallSystemFontSize]] forKey:NSFontAttributeName]; - - NSAttributedString *itemString = [[[NSAttributedString alloc] initWithString:defaultEncodingDescription attributes:menuAttributes] autorelease]; - - [[tableEncodingMenuItem onMainThread] setAttributedTitle:itemString]; - } + [encodingPopupCell addItemWithTitle:@"dummy"]; + //copy the default attributes and add gray color + NSMutableDictionary *defaultAttrs = [NSMutableDictionary dictionaryWithDictionary:[[encodingPopupCell attributedTitle] attributesAtIndex:0 effectiveRange:NULL]]; + [defaultAttrs setObject:[NSColor lightGrayColor] forKey:NSForegroundColorAttributeName]; + [[encodingPopupCell lastItem] setTitle:@""]; - [encodingTitles release]; - } - else { - [[encodingPopupCell onMainThread] removeAllItems]; - [[encodingPopupCell onMainThread] addItemWithTitle:NSLocalizedString(@"Not available", @"not available label")]; - } + for (NSDictionary *encoding in encodings) + { + NSString *encodingName = [encoding objectForKey:@"CHARACTER_SET_NAME"]; + NSString *title = (![encoding objectForKey:@"DESCRIPTION"]) ? encodingName : [NSString stringWithFormat:@"%@ (%@)", [encoding objectForKey:@"DESCRIPTION"], encodingName]; + + [encodingPopupCell addItemWithTitle:title]; + NSMenuItem *item = [encodingPopupCell lastItem]; + + [item setRepresentedObject:encodingName]; + + if ([encodingName isEqualToString:[tableDataInstance tableEncoding]]) { + + NSAttributedString *itemString = [[NSAttributedString alloc] initWithString:[item title] attributes:defaultAttrs]; + + [item setAttributedTitle:[itemString autorelease]]; + } + } + } + else { + [encodingPopupCell addItemWithTitle:NSLocalizedString(@"Not available", @"not available label")]; + } + }); // Process all the fields to normalise keys and add additional information for (id theField in theTableFields) { - // Select and re-map encoding and collation since [self dataSource] stores the choice as NSNumbers - NSString *fieldEncoding = @""; - NSInteger selectedIndex = 0; - NSString *type = [[[theField objectForKey:@"type"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString]; - - NSString *collation = nil; - NSString *encoding = nil; - + if ([fieldValidation isFieldTypeString:type]) { - // The MySQL 4.1 manual says: // // MySQL chooses the column character set and collation in the following manner: @@ -181,57 +165,41 @@ // 2. If CHARACTER SET X was specified without COLLATE, then character set X and its default collation are used. // 3. If COLLATE Y was specified without CHARACTER SET, then the character set associated with Y and collation Y. // 4. Otherwise, the table character set and collation are used. -#warning This is not correct, see the comment above. \ - However MySQL ususally outputs the CREATE TABLE statement in a way for this to still get the result right. - encoding = [theField objectForKey:@"encoding"] ? [theField objectForKey:@"encoding"] : [tableDataInstance tableEncoding]; - collation = [theField objectForKey:@"collation"] ? [theField objectForKey:@"collation"] : [databaseDataInstance getDefaultCollationForEncoding:encoding]; - - // MySQL < 4.1 does not support collations (they are part of the charset), it will be nil there - } - - if (encoding) { - for (id enc in encodings) - { - if ([[enc objectForKey:@"CHARACTER_SET_NAME"] isEqualToString:encoding]) { - fieldEncoding = encoding; - - // Set the selected index as the match index +1 due to the leading @"" in the popup list - [theField setObject:@(selectedIndex + 1) forKey:@"encoding"]; - [theField setObject:encoding forKey:@"encodingName"]; - break; + NSString *encoding = [theField objectForKey:@"encoding"]; + NSString *collation = [theField objectForKey:@"collation"]; + if(encoding) { + if(collation) { + // 1 + } + else { + collation = [databaseDataInstance getDefaultCollationForEncoding:encoding]; // 2 } - - selectedIndex++; } - } - - selectedIndex = 0; - - if (encoding && collation) { - - NSArray *theCollations = [databaseDataInstance getDatabaseCollationsForEncoding:fieldEncoding]; - - for (id col in theCollations) - { - if ([[col objectForKey:@"COLLATION_NAME"] isEqualToString:collation]) { - - // Set the selected index as the match index +1 due to the leading @"" in the popup list - [theField setObject:[NSNumber numberWithInteger:(selectedIndex + 1)] forKey:@"collation"]; - [theField setObject:collation forKey:@"collationName"]; - - // Set BINARY if collation ends with _bin for convenience - if ([[col objectForKey:@"COLLATION_NAME"] hasSuffix:@"_bin"]) { - [theField setObject:@1 forKey:@"binary"]; + else { + if(collation) { + encoding = [databaseDataInstance getEncodingFromCollation:collation]; // 3 + } + else { + encoding = [tableDataInstance tableEncoding]; //4 + collation = [tableDataInstance statusValueForKey:@"Collation"]; + if(!collation) { + // should not happen, as the TABLE STATUS output always(?) includes the collation + collation = [databaseDataInstance getDefaultCollationForEncoding:encoding]; } - - break; } - - selectedIndex++; + } + + // MySQL < 4.1 does not support collations (they are part of the charset), it will be nil there + + [theField setObject:encoding forKey:@"encodingName"]; + [theField setObject:collation forKey:@"collationName"]; + + // Set BINARY if collation ends with _bin for convenience + if ([collation hasSuffix:@"_bin"]) { + [theField setObject:@1 forKey:@"binary"]; } } - // Get possible values if the field is an enum or a set if (([type isEqualToString:@"ENUM"] || [type isEqualToString:@"SET"]) && [theField objectForKey:@"values"]) { [theTableEnumLists setObject:[NSArray arrayWithArray:[theField objectForKey:@"values"]] forKey:[theField objectForKey:@"name"]]; -- cgit v1.2.3 From 7c7660b66d4c47a43de8b1a42dba6c787b44c0cc Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 2 Nov 2015 04:18:08 +0100 Subject: Add a visual indication for the defaults in the encoding/collation menus in table structure view (final part of #2237) --- Interfaces/English.lproj/DBView.xib | 20 +++++- Source/SPPillAttachmentCell.h | 53 +++++++++++++++ Source/SPPillAttachmentCell.m | 113 +++++++++++++++++++++++++++++++ Source/SPTableStructureDelegate.m | 126 +++++++++++++++++++++++++++++++++++ sequel-pro.xcodeproj/project.pbxproj | 6 ++ 5 files changed, 316 insertions(+), 2 deletions(-) create mode 100644 Source/SPPillAttachmentCell.h create mode 100644 Source/SPPillAttachmentCell.m diff --git a/Interfaces/English.lproj/DBView.xib b/Interfaces/English.lproj/DBView.xib index b89ada7b..d7f22211 100644 --- a/Interfaces/English.lproj/DBView.xib +++ b/Interfaces/English.lproj/DBView.xib @@ -1675,7 +1675,7 @@ YES - OtherViews + encodingPopupMenu -1 @@ -1717,7 +1717,7 @@ YES - OtherViews + collationPopupMenu -1 @@ -17350,6 +17350,22 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA 7299 + + + delegate + + + + Jbs-NM-Lzb + + + + delegate + + + + Dcc-ay-7AU + delegate diff --git a/Source/SPPillAttachmentCell.h b/Source/SPPillAttachmentCell.h new file mode 100644 index 00000000..78fe80c2 --- /dev/null +++ b/Source/SPPillAttachmentCell.h @@ -0,0 +1,53 @@ +// +// SPPillAttachmentCell.h +// sequel-pro +// +// Created by Max Lohrmann on 01.11.15. +// Copyright (c) 2015 Max Lohrmann. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at + +#import + +/** + * This is a simple TextAttachmentCell which takes the stringValue + * and displays it in a "pill", much like the tokens in an NSTokenTextField. + * + * It is designed to be used with static labels rather than inside user input text fields. + * + * To use it: + * + * NSTextAttachment *attachment = [[NSTextAttachment alloc] init]; + * SPPillAttachmentCell *cell = [[SPPillAttachmentCell alloc] init]; + * [cell setStringValue:@"..."]; + * [attachment setAttachmentCell:[cell autorelease]; + * NSAttributedString *att = [NSAttributedString attributedStringWithAttachment:[attachment autorelease]]; + * [otherAttributedString appendAttributedString:att]; + * + */ +@interface SPPillAttachmentCell : NSTextAttachmentCell { + NSColor *_borderColor; + NSGradient *_gradient; +} +@end diff --git a/Source/SPPillAttachmentCell.m b/Source/SPPillAttachmentCell.m new file mode 100644 index 00000000..77f6c734 --- /dev/null +++ b/Source/SPPillAttachmentCell.m @@ -0,0 +1,113 @@ +// +// SPPillAttachmentCell.m +// sequel-pro +// +// Created by Max Lohrmann on 01.11.15. +// Copyright (c) 2015 Max Lohrmann. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at + +#import "SPPillAttachmentCell.h" + + +@implementation SPPillAttachmentCell + +- (id)init +{ + if(self = [super init]) { + _borderColor = [[NSColor colorWithCalibratedRed:168/255.0 green:184/255.0 blue:249/255.0 alpha: 1] retain]; + _gradient = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithCalibratedRed:199/255.0 green:216/255.0 blue:244/255.0 alpha: 1] + endingColor:[NSColor colorWithCalibratedRed:217/255.0 green:229/255.0 blue:247/255.0 alpha: 1]]; + } + return self; +} + +- (void)dealloc +{ + SPClear(_borderColor); + SPClear(_gradient); + + [super dealloc]; +} + +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + [self drawWithFrame:cellFrame inView:controlView characterIndex:NSNotFound]; +} + +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView characterIndex:(NSUInteger)charIndex +{ + [self drawWithFrame:cellFrame inView:controlView characterIndex:charIndex layoutManager:nil]; +} + +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView characterIndex:(NSUInteger)charIndex layoutManager:(NSLayoutManager *)layoutManager +{ + CGFloat bRadius = cellFrame.size.height/2.0; + NSBezierPath* rectanglePath = [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(cellFrame,1,1) xRadius:bRadius yRadius:bRadius]; + [_gradient drawInBezierPath: rectanglePath angle: -90]; + [_borderColor setStroke]; + [rectanglePath setLineWidth:1.0]; + [rectanglePath stroke]; + + [self drawInteriorWithFrame:cellFrame inView:controlView]; +} + +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + NSString* textContent = [self stringValue]; + NSMutableParagraphStyle* rectangleStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy]; + [rectangleStyle setAlignment:NSCenterTextAlignment]; + + NSMutableDictionary *rectangleFontAttributes = [NSMutableDictionary dictionaryWithDictionary:[self attributes]]; + [rectangleFontAttributes setObject:[rectangleStyle autorelease] forKey:NSParagraphStyleAttributeName]; + + //cellFrame.origin.y += [[self font] descender]; + [textContent drawInRect:cellFrame withAttributes:rectangleFontAttributes]; +} + +- (NSPoint)cellBaselineOffset +{ + //Not used in menu + return NSMakePoint(0, [[self font] descender]-1); +} + +- (NSRect)cellFrameForTextContainer:(NSTextContainer *)textContainer proposedLineFragment:(NSRect)lineFrag glyphPosition:(NSPoint)position characterIndex:(NSUInteger)charIndex +{ + NSSize sz = [self cellSize]; + CGFloat offset = (lineFrag.size.height - sz.height) / 2; + return NSMakeRect(0, [[self font] descender]+offset, sz.width+(2*12), sz.height); +} + +- (NSSize)cellSize +{ + //Not used in menu + return [[self stringValue] sizeWithAttributes:[self attributes]]; +} + +- (NSDictionary *)attributes +{ + return @{NSFontAttributeName: [self font],NSForegroundColorAttributeName: [NSColor controlTextColor]}; +} + +@end diff --git a/Source/SPTableStructureDelegate.m b/Source/SPTableStructureDelegate.m index 4bfd84c8..62485b26 100644 --- a/Source/SPTableStructureDelegate.m +++ b/Source/SPTableStructureDelegate.m @@ -38,9 +38,27 @@ #import "SPTableStructureLoading.h" #import "SPServerSupport.h" #import "SPTablesList.h" +#import "SPPillAttachmentCell.h" #import +struct _cmpMap { + NSString *title; // the title of the "pill" + NSString *tooltipPart; // the tooltip of the menuitem + NSString *cmpWith; // the string to match against +}; + +/** + * This function will compare the representedObject of every item in menu against + * every map->cmpWith. If they match it will append a pill-like (similar to a TokenFieldCell's token) + * element labelled map->title to the menu item's title. If map->tooltipPart is set, + * it will also be added to the menu item's tooltip. + * + * This is used with the encoding/collation popup menus to add visual indicators for the + * table-level and default encoding/collation. + */ +static void _BuildMenuWithPills(NSMenu *menu,struct _cmpMap *map,size_t mapEntries); + @interface SPTableStructure (PrivateAPI) - (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo; @@ -69,6 +87,7 @@ NSString *columnEncoding = [rowData objectForKey:@"encodingName"]; NSString *columnCollation = [rowData objectForKey:@"collationName"]; // loadTable: has already inferred it, if not set explicit +#warning Building the collation menu here is a big performance hog. This should be done in menuNeedsUpdate: below! NSPopUpButtonCell *collationCell = [tableColumn dataCell]; [collationCell removeAllItems]; [collationCell addItemWithTitle:@"dummy"]; @@ -698,4 +717,111 @@ return @""; } +#pragma mark - +#pragma mark Menu delegate methods (encoding/collation dropdown menu) + +- (void)menuNeedsUpdate:(NSMenu *)menu +{ + //NOTE: NSTableView will usually copy the menu and call this method on the copy. Matching with == won't work! + + //walk through the menu and clear the attributedTitle if set. This will remove the gray color from the default items + for(NSMenuItem *item in [menu itemArray]) { + if([item attributedTitle]) { + [item setAttributedTitle:nil]; + } + } + + NSDictionary *rowData = NSArrayObjectAtIndex(tableFields, [tableSourceView selectedRow]); + + if([[menu title] isEqualToString:@"encodingPopupMenu"]) { + NSString *tableEncoding = [tableDataInstance tableEncoding]; + //NSString *databaseEncoding = [databaseDataInstance getDatabaseDefaultCharacterSet]; + //NSString *serverEncoding = [databaseDataInstance getServerDefaultCharacterSet]; + + struct _cmpMap defaultCmp[] = { + { + NSLocalizedString(@"Table",@"Table Structure : Encoding dropdown : 'item is table default' marker"), + [NSString stringWithFormat:NSLocalizedString(@"This is the default encoding of table “%@”.", @"Table Structure : Encoding dropdown : table marker tooltip"),selectedTable], + tableEncoding + }, + /* //we could, but that might confuse users even more plus there is no inheritance between a columns charset and the db/server default + { + NSLocalizedString(@"Database",@"Table Structure : Encoding dropdown : 'item is database default' marker"), + [NSString stringWithFormat:NSLocalizedString(@"This is the default encoding of database “%@”.", @"Table Structure : Encoding dropdown : database marker tooltip"),[tableDocumentInstance database]], + databaseEncoding + }, + { + NSLocalizedString(@"Server",@"Table Structure : Encoding dropdown : 'item is server default' marker"), + NSLocalizedString(@"This is the default encoding of this server.", @"Table Structure : Encoding dropdown : server marker tooltip"), + serverEncoding + } */ + }; + + _BuildMenuWithPills(menu, defaultCmp, COUNT_OF(defaultCmp)); + } + else if([[menu title] isEqualToString:@"collationPopupMenu"]) { + NSString *encoding = [rowData objectForKey:@"encodingName"]; + NSString *encodingDefaultCollation = [databaseDataInstance getDefaultCollationForEncoding:encoding]; + NSString *tableCollation = [tableDataInstance statusValueForKey:@"Collation"]; + //NSString *databaseCollation = [databaseDataInstance getDatabaseDefaultCollation]; + //NSString *serverCollation = [databaseDataInstance getServerDefaultCollation]; + + struct _cmpMap defaultCmp[] = { + { + NSLocalizedString(@"Default",@"Table Structure : Collation dropdown : 'item is the same as the default collation of the row's charset' marker"), + [NSString stringWithFormat:NSLocalizedString(@"This is the default collation of encoding “%@”.", @"Table Structure : Collation dropdown : default marker tooltip"),encoding], + encodingDefaultCollation + }, + { + NSLocalizedString(@"Table",@"Table Structure : Collation dropdown : 'item is the same as the collation of table' marker"), + [NSString stringWithFormat:NSLocalizedString(@"This is the default collation of table “%@”.", @"Table Structure : Collation dropdown : table marker tooltip"),selectedTable], + tableCollation + }, + /* // see the comment for charset above + { + NSLocalizedString(@"Database",@"Table Structure : Collation dropdown : 'item is the same as the collation of database' marker"), + [NSString stringWithFormat:NSLocalizedString(@"This is the default collation of database “%@”.", @"Table Structure : Collation dropdown : database marker tooltip"),[tableDocumentInstance database]], + databaseCollation + }, + { + NSLocalizedString(@"Server",@"Table Structure : Collation dropdown : 'item is the same as the collation of server' marker"), + NSLocalizedString(@"This is the default collation of this server.", @"Table Structure : Collation dropdown : server marker tooltip"), + serverCollation + } */ + }; + + _BuildMenuWithPills(menu, defaultCmp, COUNT_OF(defaultCmp)); + } +} + @end + +void _BuildMenuWithPills(NSMenu *menu,struct _cmpMap *map,size_t mapEntries) +{ + NSDictionary *baseAttrs = @{NSFontAttributeName:[menu font],NSParagraphStyleAttributeName: [NSParagraphStyle defaultParagraphStyle]}; + + for(NSMenuItem *item in [menu itemArray]) { + NSMutableAttributedString *itemStr = [[NSMutableAttributedString alloc] initWithString:[item title] attributes:baseAttrs]; + NSString *value = [item representedObject]; + + NSMutableArray *tooltipParts = [NSMutableArray array]; + for (unsigned int i = 0; i < mapEntries; ++i) { + struct _cmpMap *cmp = &map[i]; + if([cmp->cmpWith isEqualToString:value]) { + SPPillAttachmentCell *cell = [[SPPillAttachmentCell alloc] init]; + [cell setStringValue:cmp->title]; + NSTextAttachment *attachment = [[NSTextAttachment alloc] init]; + [attachment setAttachmentCell:[cell autorelease]]; + NSAttributedString *attachmentString = [NSAttributedString attributedStringWithAttachment:[attachment autorelease]]; + + [[itemStr mutableString] appendString:@" "]; + [itemStr appendAttributedString:attachmentString]; + + if(cmp->tooltipPart) [tooltipParts addObject:cmp->tooltipPart]; + } + } + if([tooltipParts count]) [item setToolTip:[tooltipParts componentsJoinedByString:@" "]]; + + [item setAttributedTitle:[itemStr autorelease]]; + } +} diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index e836efe3..44fe6ecc 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -155,6 +155,7 @@ 17F90E481210B42700274C98 /* SPExportFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 17F90E471210B42700274C98 /* SPExportFile.m */; }; 17F90E4B1210B43A00274C98 /* SPExportFileUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 17F90E4A1210B43A00274C98 /* SPExportFileUtilities.m */; }; 17FDB04C1280778B00DBBBC2 /* SPFontPreviewTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 17FDB04B1280778B00DBBBC2 /* SPFontPreviewTextField.m */; }; + 1A564F74237E2E4958CA593A /* SPPillAttachmentCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A56463D14569A0B56EE8BAC /* SPPillAttachmentCell.m */; }; 296DC89F0F8FD336002A3258 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 296DC89E0F8FD336002A3258 /* WebKit.framework */; }; 296DC8B60F909194002A3258 /* MGTemplateEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 296DC8A70F909194002A3258 /* MGTemplateEngine.m */; }; 296DC8B70F909194002A3258 /* RegexKitLite.m in Sources */ = {isa = PBXBuildFile; fileRef = 296DC8AB0F909194002A3258 /* RegexKitLite.m */; }; @@ -859,6 +860,8 @@ 17F90E4A1210B43A00274C98 /* SPExportFileUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExportFileUtilities.m; sourceTree = ""; }; 17FDB04A1280778B00DBBBC2 /* SPFontPreviewTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPFontPreviewTextField.h; sourceTree = ""; }; 17FDB04B1280778B00DBBBC2 /* SPFontPreviewTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPFontPreviewTextField.m; sourceTree = ""; }; + 1A56463D14569A0B56EE8BAC /* SPPillAttachmentCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPPillAttachmentCell.m; sourceTree = ""; }; + 1A564C0C0FFB444D2E5CA447 /* SPPillAttachmentCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPPillAttachmentCell.h; sourceTree = ""; }; 296DC89E0F8FD336002A3258 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = /System/Library/Frameworks/WebKit.framework; sourceTree = ""; }; 296DC8A50F909194002A3258 /* MGTemplateMarker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGTemplateMarker.h; sourceTree = ""; }; 296DC8A60F909194002A3258 /* MGTemplateFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGTemplateFilter.h; sourceTree = ""; }; @@ -1441,6 +1444,8 @@ B57747DB0F7A89D0003B34F9 /* SPFavoriteTextFieldCell.m */, BC5750D412A6233900911BA2 /* SPActivityTextFieldCell.h */, BC5750D312A6233900911BA2 /* SPActivityTextFieldCell.m */, + 1A56463D14569A0B56EE8BAC /* SPPillAttachmentCell.m */, + 1A564C0C0FFB444D2E5CA447 /* SPPillAttachmentCell.h */, ); name = Cells; sourceTree = ""; @@ -3348,6 +3353,7 @@ 501B1D181728A3DA0017C92E /* SPCharsetCollationHelper.m in Sources */, 50E217B318174246009D3580 /* SPColorSelectorView.m in Sources */, 50E217B618174280009D3580 /* SPFavoriteColorSupport.m in Sources */, + 1A564F74237E2E4958CA593A /* SPPillAttachmentCell.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; -- cgit v1.2.3 From 945f6c56aa6a8cb10be924cc3331789c10e53e57 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 3 Nov 2015 17:46:12 +0100 Subject: Support fractional seconds for CURRENT_TIMESTAMP in the default column (part of #2315) --- Source/SPTableStructure.m | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Source/SPTableStructure.m b/Source/SPTableStructure.m index 319e74ec..5594934b 100644 --- a/Source/SPTableStructure.m +++ b/Source/SPTableStructure.m @@ -852,7 +852,7 @@ static NSString *SPRemoveFieldAndForeignKey = @"SPRemoveFieldAndForeignKey"; // Don't provide any defaults for auto-increment fields if (![theRowExtra isEqualToString:@"AUTO_INCREMENT"]) { - + NSArray *matches; // If a NULL value has been specified, and NULL is allowed, specify DEFAULT NULL if ([[theRow objectForKey:@"default"] isEqualToString:[prefs objectForKey:SPNullValue]]) { @@ -862,10 +862,15 @@ static NSString *SPRemoveFieldAndForeignKey = @"SPRemoveFieldAndForeignKey"; } // Otherwise, if CURRENT_TIMESTAMP was specified for timestamps/datetimes, use that else if (([theRowType isEqualToString:@"TIMESTAMP"] || [theRowType isEqualToString:@"DATETIME"]) && - [[[theRow objectForKey:@"default"] uppercaseString] isEqualToString:@"CURRENT_TIMESTAMP"]) + [(matches = [[[theRow objectForKey:@"default"] uppercaseString] captureComponentsMatchedByRegex:@"^\\s*CURRENT_TIMESTAMP(?:\\s*\\(\\s*(\\d+)\\s*\\))?\\s*$"]) count]) { [queryString appendString:@"\n DEFAULT CURRENT_TIMESTAMP"]; - + NSString *userLen = [matches objectAtIndex:1]; + //mysql 5.6.4+ allows DATETIME(n) for fractional seconds, which in turn requires CURRENT_TIMESTAMP(n). + // Also, if the user explicitly added one we should never ignore that. + if([userLen length] || fieldDefIncludesLen) { + [queryString appendFormat:@"(%@)",([userLen length]? userLen : [theRow objectForKey:@"length"])]; + } } // If the field is of type BIT, permit the use of single qoutes and also don't quote the default value. // For example, use DEFAULT b'1' as opposed to DEFAULT 'b\'1\'' which results in an error. -- cgit v1.2.3 From ddaa4619f1ef9aedf4fbfd156df8b7b71c3e286b Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 3 Nov 2015 19:37:18 +0100 Subject: Remove duplicate code for ALTER statement when reordering columns via drag&drop in table structure view --- Source/SPTableStructure.m | 267 +++++++++++++++++++------------------- Source/SPTableStructureDelegate.m | 84 ++---------- 2 files changed, 141 insertions(+), 210 deletions(-) diff --git a/Source/SPTableStructure.m b/Source/SPTableStructure.m index 5594934b..2fd185e5 100644 --- a/Source/SPTableStructure.m +++ b/Source/SPTableStructure.m @@ -56,6 +56,7 @@ static NSString *SPRemoveFieldAndForeignKey = @"SPRemoveFieldAndForeignKey"; @interface SPTableStructure (PrivateAPI) - (void)_removeFieldAndForeignKey:(NSNumber *)removeForeignKey; +- (NSString *)_buildPartialColumnDefinitionString:(NSDictionary *)theRow; @end @@ -741,6 +742,129 @@ static NSString *SPRemoveFieldAndForeignKey = @"SPRemoveFieldAndForeignKey"; [[tableSourceView window] endEditingFor:nil]; } + NSDictionary *theRow = [tableFields objectAtIndex:currentlyEditingRow]; + + if ([autoIncrementIndex isEqualToString:@"PRIMARY KEY"]) { + // If the field isn't set to be unsigned and we're making it the primary key then make it unsigned + if (![[theRow objectForKey:@"unsigned"] boolValue]) { + NSMutableDictionary *rowCpy = [theRow mutableCopy]; + [rowCpy setObject:@YES forKey:@"unsigned"]; + theRow = [rowCpy autorelease]; + } + } + + NSMutableString *queryString = [NSMutableString stringWithFormat:@"ALTER TABLE %@",[selectedTable backtickQuotedString]]; + [queryString appendString:@" "]; + if (isEditingNewRow) { + [queryString appendString:@"ADD"]; + } + else { + [queryString appendFormat:@"CHANGE %@",[[oldRow objectForKey:@"name"] backtickQuotedString]]; + } + [queryString appendString:@" "]; + [queryString appendString:[self _buildPartialColumnDefinitionString:theRow]]; + + // Process index if given for fields set to AUTO_INCREMENT + if (autoIncrementIndex) { + // User wants to add PRIMARY KEY + if ([autoIncrementIndex isEqualToString:@"PRIMARY KEY"]) { + [queryString appendString:@"\n PRIMARY KEY"]; + + // Add AFTER ... only if the user added a new field + if (isEditingNewRow) { + [queryString appendFormat:@"\n AFTER %@", [[[tableFields objectAtIndex:(currentlyEditingRow -1)] objectForKey:@"name"] backtickQuotedString]]; + } + } + else { + // Add AFTER ... only if the user added a new field + if (isEditingNewRow) { + [queryString appendFormat:@"\n AFTER %@", [[[tableFields objectAtIndex:(currentlyEditingRow -1)] objectForKey:@"name"] backtickQuotedString]]; + } + + [queryString appendFormat:@"\n, ADD %@ (%@)", autoIncrementIndex, [[theRow objectForKey:@"name"] backtickQuotedString]]; + } + } + // Add AFTER ... only if the user added a new field + else if (isEditingNewRow) { + [queryString appendFormat:@"\n AFTER %@", [[[tableFields objectAtIndex:(currentlyEditingRow -1)] objectForKey:@"name"] backtickQuotedString]]; + } + + isCurrentExtraAutoIncrement = NO; + autoIncrementIndex = nil; + + // Execute query + [mySQLConnection queryString:queryString]; + + if (![mySQLConnection queryErrored]) { + isEditingRow = NO; + isEditingNewRow = NO; + currentlyEditingRow = -1; + + [tableDataInstance resetAllData]; + [tableDocumentInstance setStatusRequiresReload:YES]; + [self loadTable:selectedTable]; + + // Mark the content table for refresh + [tableDocumentInstance setContentRequiresReload:YES]; + + // Query the structure of all databases in the background + [[tableDocumentInstance databaseStructureRetrieval] queryDbStructureInBackgroundWithUserInfo:[NSDictionary dictionaryWithObjectsAndKeys:@YES, @"forceUpdate", selectedTable, @"affectedItem", [NSNumber numberWithInteger:[tablesListInstance tableType]], @"affectedItemType", nil]]; + + return YES; + } + else { + alertSheetOpened = YES; + if([mySQLConnection lastErrorID] == 1146) { // If the current table doesn't exist anymore + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [tableDocumentInstance parentWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to alter table '%@'.\n\nMySQL said: %@", @"error while trying to alter table message"),selectedTable, [mySQLConnection lastErrorMessage]] + ); + + isEditingRow = NO; + isEditingNewRow = NO; + currentlyEditingRow = -1; + [tableFields removeAllObjects]; + [tableSourceView reloadData]; + [indexesTableView reloadData]; + [addFieldButton setEnabled:NO]; + [duplicateFieldButton setEnabled:NO]; + [removeFieldButton setEnabled:NO]; +#ifndef SP_CODA + [addIndexButton setEnabled:NO]; + [removeIndexButton setEnabled:NO]; + [editTableButton setEnabled:NO]; +#endif + [tablesListInstance updateTables:self]; + return NO; + } + // Problem: alert sheet doesn't respond to first click + if (isEditingNewRow) { + SPBeginAlertSheet(NSLocalizedString(@"Error adding field", @"error adding field message"), + NSLocalizedString(@"Edit row", @"Edit row button"), + NSLocalizedString(@"Discard changes", @"discard changes button"), nil, [tableDocumentInstance parentWindow], self, @selector(addRowErrorSheetDidEnd:returnCode:contextInfo:), NULL, + [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to add the field '%@' via\n\n%@\n\nMySQL said: %@", @"error adding field informative message"), + [theRow objectForKey:@"name"], queryString, [mySQLConnection lastErrorMessage]]); + } + else { + SPBeginAlertSheet(NSLocalizedString(@"Error changing field", @"error changing field message"), + NSLocalizedString(@"Edit row", @"Edit row button"), + NSLocalizedString(@"Discard changes", @"discard changes button"), nil, [tableDocumentInstance parentWindow], self, @selector(addRowErrorSheetDidEnd:returnCode:contextInfo:), NULL, + [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the field '%@' via\n\n%@\n\nMySQL said: %@", @"error changing field informative message"), + [theRow objectForKey:@"name"], queryString, [mySQLConnection lastErrorMessage]]); + } + + return NO; + } +} + +/** + * Takes the column definition from a dictionary and returns the it to be used + * with an ALTER statement, e.g.: + * `col1` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT + */ +- (NSString *)_buildPartialColumnDefinitionString:(NSDictionary *)theRow +{ NSMutableString *queryString; BOOL fieldDefIncludesLen = NO; @@ -749,27 +873,16 @@ static NSString *SPRemoveFieldAndForeignKey = @"SPRemoveFieldAndForeignKey"; BOOL specialFieldTypes = NO; - NSDictionary *theRow = [tableFields objectAtIndex:currentlyEditingRow]; - if ([theRow objectForKey:@"type"]) theRowType = [[[theRow objectForKey:@"type"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString]; if ([theRow objectForKey:@"Extra"]) theRowExtra = [[[theRow objectForKey:@"Extra"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString]; - if (isEditingNewRow) { - queryString = [NSMutableString stringWithFormat:@"ALTER TABLE %@ ADD %@ %@", - [selectedTable backtickQuotedString], - [[theRow objectForKey:@"name"] backtickQuotedString], - theRowType]; - } - else { - queryString = [NSMutableString stringWithFormat:@"ALTER TABLE %@ CHANGE %@ %@ %@", - [selectedTable backtickQuotedString], - [[oldRow objectForKey:@"name"] backtickQuotedString], - [[theRow objectForKey:@"name"] backtickQuotedString], - theRowType]; - } + queryString = [NSMutableString stringWithString:[[theRow objectForKey:@"name"] backtickQuotedString]]; + + [queryString appendString:@" "]; + [queryString appendString:theRowType]; // Check for pre-defined field type SERIAL if([theRowType isEqualToString:@"SERIAL"]) { @@ -888,7 +1001,7 @@ static NSString *SPRemoveFieldAndForeignKey = @"SPRemoveFieldAndForeignKey"; } } - if (![theRowExtra isEqualToString:@""] && ![theRowExtra isEqualToString:@"NONE"]) { + if ([theRowExtra length] && ![theRowExtra isEqualToString:@"NONE"]) { [queryString appendFormat:@"\n %@", theRowExtra]; } } @@ -898,126 +1011,12 @@ static NSString *SPRemoveFieldAndForeignKey = @"SPRemoveFieldAndForeignKey"; [queryString appendFormat:@"\n COMMENT %@", [mySQLConnection escapeAndQuoteString:[theRow objectForKey:@"comment"]]]; } - if (!isEditingNewRow) { - - // Unparsed details - column formats, storage, reference definitions - if ([(NSString *)[theRow objectForKey:@"unparsed"] length]) { - [queryString appendFormat:@"\n %@", [theRow objectForKey:@"unparsed"]]; - } - } - - // Process index if given for fields set to AUTO_INCREMENT - if (autoIncrementIndex) { - // User wants to add PRIMARY KEY - if ([autoIncrementIndex isEqualToString:@"PRIMARY KEY"]) { - [queryString appendString:@"\n PRIMARY KEY"]; - - // If the field isn't set to be unsigned and we're making it the primary key then make it unsigned - if (![[theRow objectForKey:@"unsigned"] boolValue]) { - - // Find the occurrence of the table name and data type so we know where to insert the - // UNSIGNED keyword. - NSRange range = [queryString rangeOfString:[NSString stringWithFormat:@"%@ %@", [[theRow objectForKey:@"name"] backtickQuotedString], theRowType] options:NSLiteralSearch]; - - NSInteger insertionIndex = NSMaxRange(range); - - // If the field definition's data type includes the length then we must take this into - // account when inserting the UNSIGNED keyword. Add 2 to the index to accommodate the - // parentheses used. - if (fieldDefIncludesLen) { - insertionIndex += ([(NSString *)[theRow objectForKey:@"length"] length] + 2); - } - - [queryString insertString:@" UNSIGNED" atIndex:insertionIndex]; - } - - // Add AFTER ... only if the user added a new field - if (isEditingNewRow) { - [queryString appendFormat:@"\n AFTER %@", [[[tableFields objectAtIndex:(currentlyEditingRow -1)] objectForKey:@"name"] backtickQuotedString]]; - } - } - else { - // Add AFTER ... only if the user added a new field - if (isEditingNewRow) { - [queryString appendFormat:@"\n AFTER %@", [[[tableFields objectAtIndex:(currentlyEditingRow -1)] objectForKey:@"name"] backtickQuotedString]]; - } - - [queryString appendFormat:@"\n, ADD %@ (%@)", autoIncrementIndex, [[theRow objectForKey:@"name"] backtickQuotedString]]; - } - } - - // Add AFTER ... only if the user added a new field - else if (isEditingNewRow) { - [queryString appendFormat:@"\n AFTER %@", [[[tableFields objectAtIndex:(currentlyEditingRow -1)] objectForKey:@"name"] backtickQuotedString]]; - } - - isCurrentExtraAutoIncrement = NO; - autoIncrementIndex = nil; - - // Execute query - [mySQLConnection queryString:queryString]; - - if (![mySQLConnection queryErrored]) { - isEditingRow = NO; - isEditingNewRow = NO; - currentlyEditingRow = -1; - - [tableDataInstance resetAllData]; - [tableDocumentInstance setStatusRequiresReload:YES]; - [self loadTable:selectedTable]; - - // Mark the content table for refresh - [tableDocumentInstance setContentRequiresReload:YES]; - - // Query the structure of all databases in the background - [[tableDocumentInstance databaseStructureRetrieval] queryDbStructureInBackgroundWithUserInfo:[NSDictionary dictionaryWithObjectsAndKeys:@YES, @"forceUpdate", selectedTable, @"affectedItem", [NSNumber numberWithInteger:[tablesListInstance tableType]], @"affectedItemType", nil]]; - - return YES; + // Unparsed details - column formats, storage, reference definitions + if ([(NSString *)[theRow objectForKey:@"unparsed"] length]) { + [queryString appendFormat:@"\n %@", [theRow objectForKey:@"unparsed"]]; } - else { - alertSheetOpened = YES; - if([mySQLConnection lastErrorID] == 1146) { // If the current table doesn't exist anymore - SPOnewayAlertSheet( - NSLocalizedString(@"Error", @"error"), - [tableDocumentInstance parentWindow], - [NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to alter table '%@'.\n\nMySQL said: %@", @"error while trying to alter table message"),selectedTable, [mySQLConnection lastErrorMessage]] - ); - - isEditingRow = NO; - isEditingNewRow = NO; - currentlyEditingRow = -1; - [tableFields removeAllObjects]; - [tableSourceView reloadData]; - [indexesTableView reloadData]; - [addFieldButton setEnabled:NO]; - [duplicateFieldButton setEnabled:NO]; - [removeFieldButton setEnabled:NO]; -#ifndef SP_CODA - [addIndexButton setEnabled:NO]; - [removeIndexButton setEnabled:NO]; - [editTableButton setEnabled:NO]; -#endif - [tablesListInstance updateTables:self]; - return NO; - } - // Problem: alert sheet doesn't respond to first click - if (isEditingNewRow) { - SPBeginAlertSheet(NSLocalizedString(@"Error adding field", @"error adding field message"), - NSLocalizedString(@"Edit row", @"Edit row button"), - NSLocalizedString(@"Discard changes", @"discard changes button"), nil, [tableDocumentInstance parentWindow], self, @selector(addRowErrorSheetDidEnd:returnCode:contextInfo:), NULL, - [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to add the field '%@' via\n\n%@\n\nMySQL said: %@", @"error adding field informative message"), - [theRow objectForKey:@"name"], queryString, [mySQLConnection lastErrorMessage]]); - } - else { - SPBeginAlertSheet(NSLocalizedString(@"Error changing field", @"error changing field message"), - NSLocalizedString(@"Edit row", @"Edit row button"), - NSLocalizedString(@"Discard changes", @"discard changes button"), nil, [tableDocumentInstance parentWindow], self, @selector(addRowErrorSheetDidEnd:returnCode:contextInfo:), NULL, - [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the field '%@' via\n\n%@\n\nMySQL said: %@", @"error changing field informative message"), - [theRow objectForKey:@"name"], queryString, [mySQLConnection lastErrorMessage]]); - } - return NO; - } + return queryString; } #ifdef SP_CODA /* glue */ diff --git a/Source/SPTableStructureDelegate.m b/Source/SPTableStructureDelegate.m index 62485b26..3ff4d6e4 100644 --- a/Source/SPTableStructureDelegate.m +++ b/Source/SPTableStructureDelegate.m @@ -62,6 +62,7 @@ static void _BuildMenuWithPills(NSMenu *menu,struct _cmpMap *map,size_t mapEntri @interface SPTableStructure (PrivateAPI) - (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo; +- (NSString *)_buildPartialColumnDefinitionString:(NSDictionary *)theRow; @end @@ -351,88 +352,19 @@ static void _BuildMenuWithPills(NSMenu *menu,struct _cmpMap *map,size_t mapEntri NSDictionary *originalRow = [[NSDictionary alloc] initWithDictionary:[tableFields objectAtIndex:originalRowIndex]]; [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance]; - - NSString *fieldType = [[originalRow objectForKey:@"type"] uppercaseString]; - - // Begin construction of the reordering query - NSMutableString *queryString = [NSMutableString stringWithFormat:@"ALTER TABLE %@ MODIFY COLUMN %@ %@", [selectedTable backtickQuotedString], - [[originalRow objectForKey:@"name"] backtickQuotedString], fieldType]; - - // Add the length parameter if necessary - if ([originalRow objectForKey:@"length"] && ![[originalRow objectForKey:@"length"] isEqualToString:@""]) { - [queryString appendFormat:@"(%@)", [originalRow objectForKey:@"length"]]; - } - - NSString *fieldEncoding = [originalRow objectForKey:@"encodingName"]; - if ([fieldEncoding length] && [[tableDocumentInstance serverSupport] supportsPost41CharacterSetHandling]) { - [queryString appendFormat:@" CHARACTER SET %@", fieldEncoding]; - } - - if (![fieldEncoding length] && [tableDataInstance tableEncoding]) { - fieldEncoding = [tableDataInstance tableEncoding]; - } - - NSString *fieldCollation = [originalRow objectForKey:@"collationName"]; - if ([fieldEncoding length] && [fieldCollation length] && ![[originalRow objectForKey:@"binary"] integerValue]) { - [queryString appendFormat:@" COLLATE %@", fieldCollation]; - } + // Begin construction of the reordering query + NSMutableString *queryString = [NSMutableString stringWithFormat:@"ALTER TABLE %@ MODIFY COLUMN %@", + [selectedTable backtickQuotedString], + [self _buildPartialColumnDefinitionString:originalRow]]; - // Add unsigned, zerofill, binary, not null if necessary - if ([[originalRow objectForKey:@"unsigned"] integerValue]) { - [queryString appendString:@" UNSIGNED"]; - } - - if ([[originalRow objectForKey:@"zerofill"] integerValue]) { - [queryString appendString:@" ZEROFILL"]; - } - - if ([[originalRow objectForKey:@"binary"] integerValue]) { - [queryString appendString:@" BINARY"]; - } - - if (![[originalRow objectForKey:@"null"] integerValue]) { - [queryString appendString:@" NOT NULL"]; - } - - if (![[originalRow objectForKey:@"Extra"] isEqualToString:@"None"] ) { - [queryString appendString:@" "]; - [queryString appendString:[[originalRow objectForKey:@"Extra"] uppercaseString]]; - } - - BOOL isTimestampType = [fieldType isEqualToString:@"TIMESTAMP"]; - - // Add the default value, skip it for auto_increment - if ([originalRow objectForKey:@"Extra"] && ![[originalRow objectForKey:@"Extra"] isEqualToString:@"auto_increment"]) { - if ([[originalRow objectForKey:@"default"] isEqualToString:[prefs objectForKey:SPNullValue]]) { - if ([[originalRow objectForKey:@"null"] integerValue] == 1) { - [queryString appendString:(isTimestampType) ? @" NULL DEFAULT NULL" : @" DEFAULT NULL"]; - } - } - else if (isTimestampType && ([[[originalRow objectForKey:@"default"] uppercaseString] isEqualToString:@"CURRENT_TIMESTAMP"]) ) { - [queryString appendString:@" DEFAULT CURRENT_TIMESTAMP"]; - } - else if ([(NSString *)[originalRow objectForKey:@"default"] length]) { - [queryString appendFormat:@" DEFAULT %@", [mySQLConnection escapeAndQuoteString:[originalRow objectForKey:@"default"]]]; - } - } - - // Any column comments - if ([(NSString *)[originalRow objectForKey:@"comment"] length]) { - [queryString appendFormat:@" COMMENT %@", [mySQLConnection escapeAndQuoteString:[originalRow objectForKey:@"comment"]]]; - } - - // Unparsed details - column formats, storage, reference definitions - if ([originalRow objectForKey:@"unparsed"]) { - [queryString appendString:[originalRow objectForKey:@"unparsed"]]; - } - + [queryString appendString:@" "]; // Add the new location if (destinationRowIndex == 0) { - [queryString appendString:@" FIRST"]; + [queryString appendString:@"FIRST"]; } else { - [queryString appendFormat:@" AFTER %@", [[[tableFields objectAtIndex:destinationRowIndex - 1] objectForKey:@"name"] backtickQuotedString]]; + [queryString appendFormat:@"AFTER %@", [[[tableFields objectAtIndex:destinationRowIndex - 1] objectForKey:@"name"] backtickQuotedString]]; } // Run the query; report any errors, or reload the table on success -- cgit v1.2.3 From edd4ae31b6ad14bcf3bc4deca6e68af79ad24b2a Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 3 Nov 2015 22:42:43 +0100 Subject: Changed my mind about something, to make it safer for localization --- Interfaces/English.lproj/DBView.xib | 44 ++++++++++++++++++++-- Source/SPIdMenu.h | 47 +++++++++++++++++++++++ Source/SPIdMenu.m | 73 ++++++++++++++++++++++++++++++++++++ Source/SPTableStructureDelegate.m | 6 ++- sequel-pro.xcodeproj/project.pbxproj | 6 +++ 5 files changed, 171 insertions(+), 5 deletions(-) create mode 100644 Source/SPIdMenu.h create mode 100644 Source/SPIdMenu.m diff --git a/Interfaces/English.lproj/DBView.xib b/Interfaces/English.lproj/DBView.xib index d7f22211..4288acde 100644 --- a/Interfaces/English.lproj/DBView.xib +++ b/Interfaces/English.lproj/DBView.xib @@ -1675,7 +1675,7 @@ YES - encodingPopupMenu + OtherViews -1 @@ -1717,7 +1717,7 @@ YES - collationPopupMenu + OtherViews -1 @@ -26385,10 +26385,40 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA com.apple.InterfaceBuilder.CocoaPlugin + SPIdMenu + + IBUserDefinedRuntimeAttributesPlaceholderName + + IBUserDefinedRuntimeAttributesPlaceholderName + + + + com.apple.InterfaceBuilder.userDefinedRuntimeAttributeType.string + menuId + encodingPopupMenu + + + + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + SPIdMenu + + IBUserDefinedRuntimeAttributesPlaceholderName + + IBUserDefinedRuntimeAttributesPlaceholderName + + + + com.apple.InterfaceBuilder.userDefinedRuntimeAttributeType.string + menuId + collationPopupMenu + + + + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -29806,6 +29836,14 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA ../Source/SPHistoryController.m + + SPIdMenu + NSMenu + + IBProjectSource + ../Source/SPIdMenu.h + + SPIndexesController NSWindowController @@ -31025,7 +31063,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA infoTable - id + NSTableView tableDataInstance diff --git a/Source/SPIdMenu.h b/Source/SPIdMenu.h new file mode 100644 index 00000000..dbda8952 --- /dev/null +++ b/Source/SPIdMenu.h @@ -0,0 +1,47 @@ +// +// SPIdMenu.h +// sequel-pro +// +// Created by Max Lohrmann on 02.11.15. +// Copyright (c) 2015 Max Lohrmann. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at + +#import + +/** + * This class aims to solve the problem, that the only strong connection between + * a menu in IB and code can be made via an outlet. Since comparing breaks once + * the menu is copied (which NSTableView does for its cells) and the menu title + * is localizable, ie. not constant either, we need to add another field. + * + * Note that menuId can be set via IB's "Custom Runtime Attribute" section. + */ +@interface SPIdMenu : NSMenu { + NSString *_menuId; +} + +@property (copy) NSString *menuId; + +@end diff --git a/Source/SPIdMenu.m b/Source/SPIdMenu.m new file mode 100644 index 00000000..7cb7d97e --- /dev/null +++ b/Source/SPIdMenu.m @@ -0,0 +1,73 @@ +// +// SPIdMenu.m +// sequel-pro +// +// Created by Max Lohrmann on 02.11.15. +// Copyright (c) 2015 Max Lohrmann. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at + +#import "SPIdMenu.h" + +@implementation SPIdMenu + +@synthesize menuId = _menuId; + +-(id)copyWithZone:(NSZone *)zone +{ + SPIdMenu *copy = [super copyWithZone:zone]; + copy->_menuId = [[self menuId] copyWithZone:zone]; + return copy; +} + +-(void)dealloc +{ + [self setMenuId:nil]; + [super dealloc]; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder +{ + [super encodeWithCoder:aCoder]; + if([aCoder allowsKeyedCoding]) { + [aCoder encodeObject:[self menuId] forKey:@"SPMenuId"]; + } +} + +- (id)initWithCoder:(NSCoder *)aDecoder +{ + if(self = [super initWithCoder:aDecoder]) { + if([aDecoder allowsKeyedCoding]) { + [self setMenuId:[aDecoder decodeObjectForKey:@"SPMenuId"]]; + } + } + return self; +} + +- (NSString *)description +{ + return [[super description] stringByAppendingFormat:@" with menuId=%@",[self menuId]]; +} + +@end diff --git a/Source/SPTableStructureDelegate.m b/Source/SPTableStructureDelegate.m index 3ff4d6e4..6b2249dc 100644 --- a/Source/SPTableStructureDelegate.m +++ b/Source/SPTableStructureDelegate.m @@ -39,6 +39,7 @@ #import "SPServerSupport.h" #import "SPTablesList.h" #import "SPPillAttachmentCell.h" +#import "SPIdMenu.h" #import @@ -654,6 +655,7 @@ static void _BuildMenuWithPills(NSMenu *menu,struct _cmpMap *map,size_t mapEntri - (void)menuNeedsUpdate:(NSMenu *)menu { + if(![menu isKindOfClass:[SPIdMenu class]]) return; //NOTE: NSTableView will usually copy the menu and call this method on the copy. Matching with == won't work! //walk through the menu and clear the attributedTitle if set. This will remove the gray color from the default items @@ -665,7 +667,7 @@ static void _BuildMenuWithPills(NSMenu *menu,struct _cmpMap *map,size_t mapEntri NSDictionary *rowData = NSArrayObjectAtIndex(tableFields, [tableSourceView selectedRow]); - if([[menu title] isEqualToString:@"encodingPopupMenu"]) { + if([[menu menuId] isEqualToString:@"encodingPopupMenu"]) { NSString *tableEncoding = [tableDataInstance tableEncoding]; //NSString *databaseEncoding = [databaseDataInstance getDatabaseDefaultCharacterSet]; //NSString *serverEncoding = [databaseDataInstance getServerDefaultCharacterSet]; @@ -691,7 +693,7 @@ static void _BuildMenuWithPills(NSMenu *menu,struct _cmpMap *map,size_t mapEntri _BuildMenuWithPills(menu, defaultCmp, COUNT_OF(defaultCmp)); } - else if([[menu title] isEqualToString:@"collationPopupMenu"]) { + else if([[menu menuId] isEqualToString:@"collationPopupMenu"]) { NSString *encoding = [rowData objectForKey:@"encodingName"]; NSString *encodingDefaultCollation = [databaseDataInstance getDefaultCollationForEncoding:encoding]; NSString *tableCollation = [tableDataInstance statusValueForKey:@"Collation"]; diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index 44fe6ecc..7abb7567 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -198,6 +198,7 @@ 507FF26A1BC8450100104523 /* SPExportSettingsPersistence.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF2691BC8450100104523 /* SPExportSettingsPersistence.m */; }; 507FF2A11BCD27A700104523 /* SPFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF1111BBCC57600104523 /* SPFunctions.m */; }; 507FF2A21BCD27AE00104523 /* SPOSInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 50EAB5B71A8FBB08008F627A /* SPOSInfo.m */; }; + 5089B0271BE714E300E226CD /* SPIdMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 5089B0261BE714E300E226CD /* SPIdMenu.m */; }; 50A9F8B119EAD4B90053E571 /* SPGotoDatabaseController.m in Sources */ = {isa = PBXBuildFile; fileRef = 50A9F8B019EAD4B90053E571 /* SPGotoDatabaseController.m */; }; 50D3C3491A75B8A800B5429C /* GotoDatabaseDialog.xib in Resources */ = {isa = PBXBuildFile; fileRef = 50D3C34B1A75B8A800B5429C /* GotoDatabaseDialog.xib */; }; 50D3C3521A77135F00B5429C /* SPParserUtils.c in Sources */ = {isa = PBXBuildFile; fileRef = 50D3C3501A77135F00B5429C /* SPParserUtils.c */; }; @@ -916,6 +917,8 @@ 507FF1111BBCC57600104523 /* SPFunctions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPFunctions.m; sourceTree = ""; }; 507FF2681BC8450100104523 /* SPExportSettingsPersistence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPExportSettingsPersistence.h; sourceTree = ""; }; 507FF2691BC8450100104523 /* SPExportSettingsPersistence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExportSettingsPersistence.m; sourceTree = ""; }; + 5089B0251BE714E300E226CD /* SPIdMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPIdMenu.h; sourceTree = ""; }; + 5089B0261BE714E300E226CD /* SPIdMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPIdMenu.m; sourceTree = ""; }; 50A9F8AF19EAD4B90053E571 /* SPGotoDatabaseController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPGotoDatabaseController.h; sourceTree = ""; }; 50A9F8B019EAD4B90053E571 /* SPGotoDatabaseController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPGotoDatabaseController.m; sourceTree = ""; }; 50D3C34A1A75B8A800B5429C /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/GotoDatabaseDialog.xib; sourceTree = ""; }; @@ -2394,6 +2397,8 @@ children = ( 507FF1101BBCC4C400104523 /* SPFunctions.h */, 507FF1111BBCC57600104523 /* SPFunctions.m */, + 5089B0251BE714E300E226CD /* SPIdMenu.h */, + 5089B0261BE714E300E226CD /* SPIdMenu.m */, ); name = Utility; sourceTree = ""; @@ -3248,6 +3253,7 @@ 171312CE109D23C700FB465F /* SPTableTextFieldCell.m in Sources */, 174CE14210AB9281008F892B /* SPProcessListController.m in Sources */, 1792C13710AD75C800ABE758 /* SPServerVariablesController.m in Sources */, + 5089B0271BE714E300E226CD /* SPIdMenu.m in Sources */, 1792C26110AE1A2D00ABE758 /* SPConnectionDelegate.m in Sources */, 17CC97F310B4ABE90034CD7A /* SPAboutController.m in Sources */, 5870868410FA3E9C00D58E1C /* SPDataStorage.m in Sources */, -- cgit v1.2.3 From 47d9bf6d6c6dcb0810a03b268d73f3f10c513460 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 3 Nov 2015 23:18:53 +0100 Subject: Fix the charset/collation selection when creating a new db/table (minor leftover from #2237) --- Interfaces/English.lproj/DBView.xib | 18 +++++++++--------- Source/SPCharsetCollationHelper.h | 22 ++++++++++++++++++++++ Source/SPCharsetCollationHelper.m | 27 ++++++++++++++++----------- Source/SPDatabaseDocument.m | 2 ++ Source/SPTablesList.m | 1 + 5 files changed, 50 insertions(+), 20 deletions(-) diff --git a/Interfaces/English.lproj/DBView.xib b/Interfaces/English.lproj/DBView.xib index 4288acde..e42e67e3 100644 --- a/Interfaces/English.lproj/DBView.xib +++ b/Interfaces/English.lproj/DBView.xib @@ -7474,14 +7474,14 @@ 9 2 - {{343, 433}, {384, 162}} + {{343, 433}, {425, 162}} 1886912512 New Table NSWindow {600, 162} - {384, 142} + {425, 162} 256 @@ -7527,7 +7527,7 @@ 258 - {{138, 124}, {226, 18}} + {{138, 124}, {267, 18}} YES @@ -7547,7 +7547,7 @@ 257 - {{291, 13}, {78, 28}} + {{332, 13}, {78, 28}} {250, 750} 1 @@ -7572,7 +7572,7 @@ 258 - {{135, 95}, {232, 22}} + {{135, 95}, {273, 22}} YES @@ -7624,7 +7624,7 @@ 258 - {{135, 70}, {232, 22}} + {{135, 70}, {273, 22}} YES @@ -7676,7 +7676,7 @@ 258 - {{135, 45}, {232, 22}} + {{135, 45}, {273, 22}} YES @@ -7709,7 +7709,7 @@ 257 - {{209, 13}, {84, 28}} + {{250, 13}, {84, 28}} {250, 750} @@ -7731,7 +7731,7 @@ NO - {384, 162} + {425, 162} {{0, 0}, {1920, 1178}} diff --git a/Source/SPCharsetCollationHelper.h b/Source/SPCharsetCollationHelper.h index 79aefcef..7a442d17 100644 --- a/Source/SPCharsetCollationHelper.h +++ b/Source/SPCharsetCollationHelper.h @@ -51,6 +51,7 @@ NSString *selectedCharset; NSString *selectedCollation; NSString *defaultCharsetFormatString; + NSString *defaultCollationFormatString; NSString *_oldCharset; BOOL _enabled; @@ -105,6 +106,27 @@ */ @property(readwrite,retain) NSString *defaultCharsetFormatString; +/** + * This is the format string that will be used for formatting the defaultCollation. + * It must contain one %@ variable (the collation name). + * + * Note that this is only used as long as the user keeps the _implicit_ defaultCharset. + * E.g. if the charset menu has those items: + * @code + * Inherited from db (latin1) + * ------------------------------- + * ... + * cp1521 Western Europe (latin1) + * ... + * @endcode + * This item will only be used for the FIRST appearance of latin1. + * If the user picks ANY item below the line (thus making the charset explicit) both the defaultCollation + * and the defaultCollationFormatString no longer apply and the item at the top of + * the collation list will be the global default for the given charset (not the inherited one) + * and named "Default (...)", because that is how MySQL applies the settings. + */ +@property(readwrite,retain) NSString *defaultCollationFormatString; + /** * Set this to YES before showing the UI and NO after dismissing it. * This will cause the charsets to be re-read and the selection to be reset. diff --git a/Source/SPCharsetCollationHelper.m b/Source/SPCharsetCollationHelper.m index 378fb7cf..72387597 100644 --- a/Source/SPCharsetCollationHelper.m +++ b/Source/SPCharsetCollationHelper.m @@ -53,6 +53,7 @@ @synthesize selectedCharset; @synthesize selectedCollation; @synthesize defaultCharsetFormatString; +@synthesize defaultCollationFormatString; @synthesize _oldCharset; - (id)initWithCharsetButton:(NSPopUpButton *)aCharsetButton CollationButton:(NSPopUpButton *)aCollationButton @@ -63,7 +64,8 @@ self = [super init]; if (self != nil) { [self setPromoteUTF8:YES]; - [self setDefaultCharsetFormatString:NSLocalizedString(@"Default (%@)",@"Charset Dropdown : Default item ($1 = name)")]; + [self setDefaultCharsetFormatString:NSLocalizedString(@"Default (%@)",@"Charset Dropdown : Default item ($1 = charset name)")]; + [self setDefaultCollationFormatString:NSLocalizedString(@"Default (%@)",@"Collation Dropdown : Default collation for given charset ($1 = collation name)")]; charsetButton = aCharsetButton; collationButton = aCollationButton; //connect the charset button with ourselves @@ -210,6 +212,7 @@ //get the charset id NSString *charsetId = [[charsetButton selectedItem] representedObject]; + BOOL charsetIsInherited = ([self selectedCharset] == nil); //now let's get the list of collations for the selected charset id NSArray *applicableCollations = [databaseData getDatabaseCollationsForEncoding:charsetId]; @@ -220,16 +223,18 @@ //add a separator [[collationButton menu] addItem:[NSMenuItem separatorItem]]; - - //if this is the defaultCharset and we have a defaultCollation use that instead - BOOL useGivenDefaultCollation = (defaultCharset && defaultCollation && [charsetId isEqualToString:defaultCharset]); - - if(useGivenDefaultCollation) { - NSString *userDefaultCollateTitle = [NSString stringWithFormat:fmtStrDefaultId,defaultCollation]; + + // there are two kinds of default collations: + // - the inherited default (which is only used if NEITHER charset NOR collation is explicitly set), and + // - the charset default (which is used if charset is explicitly set, but collation is not) + // - that even applies if the selectedCharset is the same as the defaultCharset! + if(charsetIsInherited) { + // implies [charsetId isEqualToString:defaultCharset] + NSString *userInheritedCollateTitle = [NSString stringWithFormat:defaultCollationFormatString,defaultCollation]; //remove the dummy default item. [collationButton removeItemAtIndex:0]; //add it to the top of the list - [collationButton insertItemWithTitle:userDefaultCollateTitle atIndex:0]; + [collationButton insertItemWithTitle:userInheritedCollateTitle atIndex:0]; } //add the real items @@ -238,8 +243,8 @@ NSString *collationName = [collation objectForKey:@"COLLATION_NAME"]; [collationButton addItemWithTitle:collationName]; - //is this the default collation for this charset (and we didn't override it)? - if(!useGivenDefaultCollation && [[collation objectForKey:@"IS_DEFAULT"] isEqualToString:@"Yes"]) { + //is this the default collation for this charset and charset was given explicitly (ie. breaking inheritance)? + if(!charsetIsInherited && [[collation objectForKey:@"IS_DEFAULT"] isEqualToString:@"Yes"]) { NSString *defaultCollateTitle = [NSString stringWithFormat:fmtStrDefaultId,collationName]; //remove the dummy default item. [collationButton removeItemAtIndex:0]; @@ -247,7 +252,7 @@ [collationButton insertItemWithTitle:defaultCollateTitle atIndex:0]; } } - //reset selection to first item (it may moved when adding the default item) + //reset selection to first item (it may have moved when adding the default item) [collationButton selectItemAtIndex:0]; //honor selectedCollation diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index 76dda407..d4c568b2 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -809,6 +809,8 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; // Setup the charset and collation dropdowns [addDatabaseCharsetHelper setDatabaseData:databaseDataInstance]; + [addDatabaseCharsetHelper setDefaultCharsetFormatString:NSLocalizedString(@"Server Default (%@)", @"Add Database : Charset dropdown : default item ($1 = charset name)")]; + [addDatabaseCharsetHelper setDefaultCollationFormatString:NSLocalizedString(@"Server Default (%@)", @"Add Database : Collation dropdown : default item ($1 = collation name)")]; [addDatabaseCharsetHelper setServerSupport:serverSupport]; [addDatabaseCharsetHelper setPromoteUTF8:YES]; [addDatabaseCharsetHelper setSelectedCharset:nil]; diff --git a/Source/SPTablesList.m b/Source/SPTablesList.m index 7449a2cf..92f7cae5 100644 --- a/Source/SPTablesList.m +++ b/Source/SPTablesList.m @@ -391,6 +391,7 @@ static NSString *SPDuplicateTable = @"SPDuplicateTable"; [addTableCharsetHelper setServerSupport:[tableDocumentInstance serverSupport]]; [addTableCharsetHelper setPromoteUTF8:YES]; [addTableCharsetHelper setDefaultCharsetFormatString:NSLocalizedString(@"Inherit from database (%@)", @"New Table Sheet : Table Encoding Dropdown : Default inherited from database")]; + [addTableCharsetHelper setDefaultCollationFormatString:NSLocalizedString(@"Inherit from database (%@)", @"New Table Sheet : Table Collation Dropdown : Default inherited from database")]; [addTableCharsetHelper setDefaultCharset:[databaseDataInstance getDatabaseDefaultCharacterSet]]; [addTableCharsetHelper setDefaultCollation:[databaseDataInstance getDatabaseDefaultCollation]]; [addTableCharsetHelper setSelectedCharset:nil]; //reset to not carry over state from last time sheet was shown -- cgit v1.2.3 From 3e69d23923c0e3fe7b1312ce2c0a90acfaadbb1d Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 4 Nov 2015 02:01:06 +0100 Subject: Add support for CURRENT_TIMESTAMP(n) in default/on update column of DATETIME/TIMESTAMP fields (part of #2315) --- Source/SPConstants.h | 2 ++ Source/SPConstants.m | 5 +++++ Source/SPCustomQuery.m | 13 +++++++------ Source/SPObjectAdditions.h | 5 +++++ Source/SPObjectAdditions.m | 5 +++++ Source/SPSQLExporter.m | 4 ++-- Source/SPTableContent.m | 23 ++++++++++++----------- Source/SPTableData.m | 3 ++- Source/SPTableStructure.m | 10 +++++++--- Source/SPTableStructureLoading.m | 12 +++++++++--- 10 files changed, 56 insertions(+), 26 deletions(-) diff --git a/Source/SPConstants.h b/Source/SPConstants.h index e53d09b4..4246906f 100644 --- a/Source/SPConstants.h +++ b/Source/SPConstants.h @@ -620,6 +620,8 @@ extern NSString *SPBundleShellVariableAllFunctions; extern NSString *SPBundleShellVariableAllViews; extern NSString *SPBundleShellVariableAllTables; +extern NSString *SPCurrentTimestampPattern; + typedef NS_ENUM(NSInteger, SPBundleRedirectAction) { SPBundleRedirectActionNone = 200, SPBundleRedirectActionReplaceSection = 201, diff --git a/Source/SPConstants.m b/Source/SPConstants.m index 893c725f..e5a407f4 100644 --- a/Source/SPConstants.m +++ b/Source/SPConstants.m @@ -431,6 +431,11 @@ NSString *SPBundleShellVariableSelectedText = @"SP_SELECTED_TEXT NSString *SPBundleShellVariableSelectedTextRange = @"SP_SELECTED_TEXT_RANGE"; NSString *SPBundleShellVariableUsedQueryForTable = @"SP_USED_QUERY_FOR_TABLE"; +#define OWS @"\\s*" /* optional whitespace */ +// CURRENT_TIMESTAMP [ ( [n] ) ] +NSString *SPCurrentTimestampPattern = (@"^" OWS @"CURRENT_TIMESTAMP" @"(?:" OWS @"\\(" OWS @"(\\d*)" OWS @"\\)" @")?" OWS @"$"); +#undef OWS + // URL scheme NSString *SPURLSchemeQueryInputPathHeader = @"/tmp/SP_QUERY_"; NSString *SPURLSchemeQueryResultPathHeader = @"/tmp/SP_QUERY_RESULT_"; diff --git a/Source/SPCustomQuery.m b/Source/SPCustomQuery.m index 4737bea8..3a04d1d5 100644 --- a/Source/SPCustomQuery.m +++ b/Source/SPCustomQuery.m @@ -1996,22 +1996,23 @@ } else if ( [anObject isKindOfClass:[NSData class]] ) { newObject = [mySQLConnection escapeAndQuoteData:anObject]; } else { - if ( [[anObject description] isEqualToString:@"CURRENT_TIMESTAMP"] ) { - newObject = @"CURRENT_TIMESTAMP"; + NSString *desc = [anObject description]; + if ( [desc isMatchedByRegex:SPCurrentTimestampPattern] ) { + newObject = desc; } else if ([anObject isEqualToString:[prefs stringForKey:SPNullValue]] || (([columnTypeGroup isEqualToString:@"float"] || [columnTypeGroup isEqualToString:@"integer"] || [columnTypeGroup isEqualToString:@"date"]) - && [[anObject description] isEqualToString:@""])) + && [desc isEqualToString:@""])) { newObject = @"NULL"; } else if ([columnTypeGroup isEqualToString:@"geometry"]) { newObject = [(NSString*)anObject getGeomFromTextString]; } else if ([columnTypeGroup isEqualToString:@"bit"]) { - newObject = [NSString stringWithFormat:@"b'%@'", ((![[anObject description] length] || [[anObject description] isEqualToString:@"0"]) ? @"0" : [anObject description])]; + newObject = [NSString stringWithFormat:@"b'%@'", ((![desc length] || [desc isEqualToString:@"0"]) ? @"0" : desc)]; } else if ([columnTypeGroup isEqualToString:@"date"] - && [[anObject description] isEqualToString:@"NOW()"]) { + && [desc isEqualToString:@"NOW()"]) { newObject = @"NOW()"; } else { - newObject = [mySQLConnection escapeAndQuoteString:[anObject description]]; + newObject = [mySQLConnection escapeAndQuoteString:desc]; } } diff --git a/Source/SPObjectAdditions.h b/Source/SPObjectAdditions.h index 03c04d85..bfe620d9 100644 --- a/Source/SPObjectAdditions.h +++ b/Source/SPObjectAdditions.h @@ -35,4 +35,9 @@ */ - (BOOL)isNSNull; +/** + * easier to read version of [array containsObject:x] + */ +- (BOOL)isInArray:(NSArray *)list; + @end diff --git a/Source/SPObjectAdditions.m b/Source/SPObjectAdditions.m index 5f7c73ab..ba53fc2d 100644 --- a/Source/SPObjectAdditions.m +++ b/Source/SPObjectAdditions.m @@ -46,6 +46,11 @@ static NSMutableDictionary *gScrollViewDealloc; return (self == null); } +- (BOOL)isInArray:(NSArray *)list +{ + return [list containsObject:self]; +} + - (void)_scrollViewDidChangeBounds:(id)obj { NSMutableString *msg = [NSMutableString string]; diff --git a/Source/SPSQLExporter.m b/Source/SPSQLExporter.m index 94506728..9dffe7c1 100644 --- a/Source/SPSQLExporter.m +++ b/Source/SPSQLExporter.m @@ -884,8 +884,8 @@ [fieldString appendString:@" DEFAULT NULL"]; } } - else if (([[column objectForKey:@"type"] isEqualToString:@"TIMESTAMP"] || [[column objectForKey:@"type"] isEqualToString:@"DATETIME"]) && [column objectForKey:@"default"] != [NSNull null] && [[[column objectForKey:@"default"] uppercaseString] isEqualToString:@"CURRENT_TIMESTAMP"]) { - [fieldString appendString:@" DEFAULT CURRENT_TIMESTAMP"]; + else if (([[column objectForKey:@"type"] isInArray:@[@"TIMESTAMP",@"DATETIME"]]) && [[column objectForKey:@"default"] isMatchedByRegex:SPCurrentTimestampPattern]) { + [fieldString appendFormat:@" DEFAULT %@",[column objectForKey:@"default"]]; } else { [fieldString appendFormat:@" DEFAULT %@", [connection escapeAndQuoteString:[column objectForKey:@"default"]]]; diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m index 095f89ee..ed01703f 100644 --- a/Source/SPTableContent.m +++ b/Source/SPTableContent.m @@ -2801,14 +2801,15 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper fieldValue = [mySQLConnection escapeAndQuoteData:rowObject]; } else { - if ([[rowObject description] isEqualToString:@"CURRENT_TIMESTAMP"]) { - fieldValue = @"CURRENT_TIMESTAMP"; + NSString *desc = [rowObject description]; + if ([desc isMatchedByRegex:SPCurrentTimestampPattern]) { + fieldValue = desc; } else if ([fieldTypeGroup isEqualToString:@"bit"]) { - fieldValue = [NSString stringWithFormat:@"b'%@'", ((![[rowObject description] length] || [[rowObject description] isEqualToString:@"0"]) ? @"0" : [rowObject description])]; - } else if ([fieldTypeGroup isEqualToString:@"date"] && [[rowObject description] isEqualToString:@"NOW()"]) { + fieldValue = [NSString stringWithFormat:@"b'%@'", ((![desc length] || [desc isEqualToString:@"0"]) ? @"0" : desc)]; + } else if ([fieldTypeGroup isEqualToString:@"date"] && [desc isEqualToString:@"NOW()"]) { fieldValue = @"NOW()"; } else { - fieldValue = [mySQLConnection escapeAndQuoteString:[rowObject description]]; + fieldValue = [mySQLConnection escapeAndQuoteString:desc]; } } } @@ -3426,19 +3427,19 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper } else if ( [anObject isKindOfClass:[NSData class]] ) { newObject = [mySQLConnection escapeAndQuoteData:anObject]; } else { - if ( [[anObject description] isEqualToString:@"CURRENT_TIMESTAMP"] ) { - newObject = @"CURRENT_TIMESTAMP"; + NSString *desc = [anObject description]; + if ( [desc isMatchedByRegex:SPCurrentTimestampPattern] ) { + newObject = desc; } else if([anObject isEqualToString:[prefs stringForKey:SPNullValue]]) { newObject = @"NULL"; } else if ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"geometry"]) { newObject = [(NSString*)anObject getGeomFromTextString]; } else if ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"bit"]) { - newObject = [NSString stringWithFormat:@"b'%@'", ((![[anObject description] length] || [[anObject description] isEqualToString:@"0"]) ? @"0" : [anObject description])]; - } else if ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"date"] - && [[anObject description] isEqualToString:@"NOW()"]) { + newObject = [NSString stringWithFormat:@"b'%@'", ((![desc length] || [desc isEqualToString:@"0"]) ? @"0" : desc)]; + } else if ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"date"] && [desc isEqualToString:@"NOW()"]) { newObject = @"NOW()"; } else { - newObject = [mySQLConnection escapeAndQuoteString:[anObject description]]; + newObject = [mySQLConnection escapeAndQuoteString:desc]; } } diff --git a/Source/SPTableData.m b/Source/SPTableData.m index 51c6a274..62732b51 100644 --- a/Source/SPTableData.m +++ b/Source/SPTableData.m @@ -1359,7 +1359,8 @@ // Special timestamp/datetime case - Whether fields are set to update the current timestamp } else if ([detailString isEqualToString:@"ON"] && (definitionPartsIndex + 2 < partsArrayLength) && [[NSArrayObjectAtIndex(definitionParts, definitionPartsIndex+1) uppercaseString] isEqualToString:@"UPDATE"] - && [[NSArrayObjectAtIndex(definitionParts, definitionPartsIndex+2) uppercaseString] isEqualToString:@"CURRENT_TIMESTAMP"]) { + && [NSArrayObjectAtIndex(definitionParts, definitionPartsIndex+2) isMatchedByRegex:SPCurrentTimestampPattern]) { + // mysql requires the CURRENT_TIMESTAMP(n) to be exactly the same as the column types length, so we don't need to keep it, we can just restore it later [fieldDetails setValue:@YES forKey:@"onupdatetimestamp"]; definitionPartsIndex += 2; diff --git a/Source/SPTableStructure.m b/Source/SPTableStructure.m index 2fd185e5..d242ae88 100644 --- a/Source/SPTableStructure.m +++ b/Source/SPTableStructure.m @@ -974,12 +974,12 @@ static NSString *SPRemoveFieldAndForeignKey = @"SPRemoveFieldAndForeignKey"; } } // Otherwise, if CURRENT_TIMESTAMP was specified for timestamps/datetimes, use that - else if (([theRowType isEqualToString:@"TIMESTAMP"] || [theRowType isEqualToString:@"DATETIME"]) && - [(matches = [[[theRow objectForKey:@"default"] uppercaseString] captureComponentsMatchedByRegex:@"^\\s*CURRENT_TIMESTAMP(?:\\s*\\(\\s*(\\d+)\\s*\\))?\\s*$"]) count]) + else if ([theRowType isInArray:@[@"TIMESTAMP",@"DATETIME"]] && + [(matches = [[[theRow objectForKey:@"default"] uppercaseString] captureComponentsMatchedByRegex:SPCurrentTimestampPattern]) count]) { [queryString appendString:@"\n DEFAULT CURRENT_TIMESTAMP"]; NSString *userLen = [matches objectAtIndex:1]; - //mysql 5.6.4+ allows DATETIME(n) for fractional seconds, which in turn requires CURRENT_TIMESTAMP(n). + // mysql 5.6.4+ allows DATETIME(n) for fractional seconds, which in turn requires CURRENT_TIMESTAMP(n) with the same n! // Also, if the user explicitly added one we should never ignore that. if([userLen length] || fieldDefIncludesLen) { [queryString appendFormat:@"(%@)",([userLen length]? userLen : [theRow objectForKey:@"length"])]; @@ -1003,6 +1003,10 @@ static NSString *SPRemoveFieldAndForeignKey = @"SPRemoveFieldAndForeignKey"; if ([theRowExtra length] && ![theRowExtra isEqualToString:@"NONE"]) { [queryString appendFormat:@"\n %@", theRowExtra]; + //fix our own default item if needed + if([theRowExtra isEqualToString:@"ON UPDATE CURRENT_TIMESTAMP"] && fieldDefIncludesLen) { + [queryString appendFormat:@"(%@)",[theRow objectForKey:@"length"]]; + } } } diff --git a/Source/SPTableStructureLoading.m b/Source/SPTableStructureLoading.m index 85b74a7c..57191b4b 100644 --- a/Source/SPTableStructureLoading.m +++ b/Source/SPTableStructureLoading.m @@ -227,9 +227,15 @@ } // For timestamps/datetime check to see whether "on update CURRENT_TIMESTAMP" and set Extra accordingly - else if (([type isEqualToString:@"TIMESTAMP"] || [type isEqualToString:@"DATETIME"]) && - [[theField objectForKey:@"onupdatetimestamp"] integerValue]) { - [theField setObject:@"on update CURRENT_TIMESTAMP" forKey:@"Extra"]; + else if ([type isInArray:@[@"TIMESTAMP",@"DATETIME"]] && [[theField objectForKey:@"onupdatetimestamp"] boolValue]) { + NSString *ouct = @"on update CURRENT_TIMESTAMP"; + // restore a length parameter if the field has fractional seconds. + // the parameter of current_timestamp MUST match the field's length in that case, so we can just 'guess' it. + NSString *fieldLen = [theField objectForKey:@"length"]; + if([fieldLen length] && ![fieldLen isEqualToString:@"0"]) { + ouct = [ouct stringByAppendingFormat:@"(%@)",fieldLen]; + } + [theField setObject:ouct forKey:@"Extra"]; } } -- cgit v1.2.3 From ea1d72868aabba227aa608a2a89079aa12ac70a1 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 5 Nov 2015 03:32:12 +0100 Subject: * Change window creation to use a create-and-get model instead of create-then-get as that did cause problems on 10.11 in some cases (#2294) * Split a huuuuge method into many smaller methods * Updated some code to use methods that are already there --- Source/SPAppController.m | 857 +++++++++++++++++++++----------------------- Source/SPGrowlController.m | 17 +- Source/SPWindowController.h | 2 + Source/SPWindowController.m | 9 +- Source/SPWindowManagement.h | 6 + Source/SPWindowManagement.m | 56 ++- 6 files changed, 478 insertions(+), 469 deletions(-) diff --git a/Source/SPAppController.m b/Source/SPAppController.m index 35b5ab96..a0f4865f 100644 --- a/Source/SPAppController.m +++ b/Source/SPAppController.m @@ -58,6 +58,12 @@ - (void)_copyDefaultThemes; +- (void)openConnectionFileAtPath:(NSString *)filePath; +- (void)openSQLFileAtPath:(NSString *)filePath; +- (void)openSessionBundleAtPath:(NSString *)filePath; +- (void)openColorThemeFileAtPath:(NSString *)filePath; +- (void)openUserBundleAtPath:(NSString *)filePath; + @end @implementation SPAppController @@ -164,15 +170,15 @@ // If no documents are open, open one if (![self frontDocument]) { - [self newWindow:self]; + SPDatabaseDocument *newConnection = [self makeNewConnectionTabOrWindow]; if (spfDict) { - [[self frontDocument] setState:spfDict]; + [newConnection setState:spfDict]; } // Set autoconnection if appropriate if ([[NSUserDefaults standardUserDefaults] boolForKey:SPAutoConnectToDefault]) { - [[self frontDocument] connect]; + [newConnection connect]; } } } @@ -180,30 +186,13 @@ - (void)externalApplicationWantsToOpenADatabaseConnection:(NSNotification *)notification { - SPWindowController *frontController = nil; - - for (NSWindow *aWindow in [NSApp orderedWindows]) { - if ([[aWindow windowController] isMemberOfClass:[SPWindowController class]]) { - frontController = [aWindow windowController]; - break; - } - } - - // If no window was found or the front most window has no tabs, create a new one - if (!frontController || [[frontController valueForKeyPath:@"tabView"] numberOfTabViewItems] == 1) { - [self newWindow:self]; - // Open the spf file in a new tab if the tab bar is visible - } else if ([[frontController valueForKeyPath:@"tabView"] numberOfTabViewItems] != 1) { - if ([[frontController window] isMiniaturized]) [[frontController window] deminiaturize:self]; - [frontController addNewConnection:self]; - } - NSDictionary *userInfo = [notification userInfo]; NSString *MAMP_SPFVersion = [userInfo objectForKey:@"dataVersion"]; if ([MAMP_SPFVersion isEqualToString:@"1"]) { NSDictionary *spfStructure = [userInfo objectForKey:@"spfData"]; if (spfStructure) { - [[self frontDocument] setState:spfStructure]; + SPDatabaseDocument *frontDoc = [self makeNewConnectionTabOrWindow]; + [frontDoc setState:spfStructure]; } } } @@ -327,426 +316,426 @@ { for (NSString *filePath in filenames) { - //NSString *filePath = [file path]; - + NSString *fileExt = [[filePath pathExtension] lowercaseString]; // Opens a sql file and insert its content into the Custom Query editor - if ([[[filePath pathExtension] lowercaseString] isEqualToString:[SPFileExtensionSQL lowercaseString]]) { - - // Check size and NSFileType - NSDictionary *attr = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil]; - - if (attr) - { - NSNumber *filesize = [attr objectForKey:NSFileSize]; - NSString *filetype = [attr objectForKey:NSFileType]; - if(filetype == NSFileTypeRegular && filesize) - { - // Ask for confirmation if file content is larger than 1MB - if ([filesize unsignedLongValue] > 1000000) - { - NSAlert *alert = [[NSAlert alloc] init]; - [alert addButtonWithTitle:NSLocalizedString(@"OK", @"OK button")]; - [alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"cancel button")]; - - // Show 'Import' button only if there's a connection available - if ([self frontDocument]) - [alert addButtonWithTitle:NSLocalizedString(@"Import", @"import button")]; - - - [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"Do you really want to load a SQL file with %@ of data into the Query Editor?", @"message of panel asking for confirmation for loading large text into the query editor"), - [NSString stringForByteSize:[filesize longLongValue]]]]; - - [alert setHelpAnchor:filePath]; - [alert setMessageText:NSLocalizedString(@"Warning",@"warning")]; - [alert setAlertStyle:NSWarningAlertStyle]; - - NSUInteger returnCode = [alert runModal]; - - [alert release]; - - if(returnCode == NSAlertSecondButtonReturn) return; // Cancel - else if(returnCode == NSAlertThirdButtonReturn) { // Import - // begin import process - [[[self frontDocument] valueForKeyPath:@"tableDumpInstance"] startSQLImportProcessWithFile:filePath]; - return; - } - } - } - } - - // Attempt to open the file into a string. - NSStringEncoding sqlEncoding; - NSString *sqlString = nil; - - // If the user came from an openPanel use the chosen encoding - if (encodingPopUp) { - sqlEncoding = [[encodingPopUp selectedItem] tag]; - - // Otherwise, attempt to autodetect the encoding - } - else { - sqlEncoding = [[NSFileManager defaultManager] detectEncodingforFileAtPath:filePath]; - } - - NSError *error = nil; - - sqlString = [NSString stringWithContentsOfFile:filePath encoding:sqlEncoding error:&error]; - - if (error != nil) { - NSAlert *errorAlert = [NSAlert alertWithError:error]; - [errorAlert runModal]; - - return; - } - - // if encodingPopUp is defined the filename comes from an openPanel and - // the encodingPopUp contains the chosen encoding; otherwise autodetect encoding - if (encodingPopUp) { - [[NSUserDefaults standardUserDefaults] setInteger:[[encodingPopUp selectedItem] tag] forKey:SPLastSQLFileEncoding]; - } - - // Check if at least one document exists. If not, open one. - if (![self frontDocument]) { - [self newWindow:self]; - [[self frontDocument] initQueryEditorWithString:sqlString]; - } - else { - // Pass query to the Query editor of the current document - [[self frontDocument] doPerformLoadQueryService:sqlString]; - } - - [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:[NSURL fileURLWithPath:filePath]]; - - [[self frontDocument] setSqlFileURL:[NSURL fileURLWithPath:filePath]]; - [[self frontDocument] setSqlFileEncoding:sqlEncoding]; - + if ([fileExt isEqualToString:[SPFileExtensionSQL lowercaseString]]) { + [self openSQLFileAtPath:filePath]; break; // open only the first SQL file - } - else if ([[[filePath pathExtension] lowercaseString] isEqualToString:[SPFileExtensionDefault lowercaseString]]) { + else if ([fileExt isEqualToString:[SPFileExtensionDefault lowercaseString]]) { + [self openConnectionFileAtPath:filePath]; + } + else if ([fileExt isEqualToString:[SPBundleFileExtension lowercaseString]]) { + [self openSessionBundleAtPath:filePath]; + } + else if ([fileExt isEqualToString:[SPColorThemeFileExtension lowercaseString]]) { + [self openColorThemeFileAtPath:filePath]; + } + else if ([fileExt isEqualToString:[SPUserBundleFileExtension lowercaseString]]) { + [self openUserBundleAtPath:filePath]; + } + else { + NSBeep(); + NSLog(@"Only files with the extensions ‘%@’, ‘%@’, ‘%@’ or ‘%@’ are allowed.", SPFileExtensionDefault, SPBundleFileExtension, SPColorThemeFileExtension, SPFileExtensionSQL); + } + } +} - SPWindowController *frontController = nil; +- (void)openConnectionFileAtPath:(NSString *)filePath +{ + SPDatabaseDocument *frontDocument = [self makeNewConnectionTabOrWindow]; + + [frontDocument setStateFromConnectionFile:filePath]; + + [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:[NSURL fileURLWithPath:filePath]]; +} - for (NSWindow *aWindow in [NSApp orderedWindows]) +- (void)openSQLFileAtPath:(NSString *)filePath +{ + // Check size and NSFileType + NSDictionary *attr = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil]; + + SPDatabaseDocument *frontDocument = [self frontDocument]; + + if (attr) + { + NSNumber *filesize = [attr objectForKey:NSFileSize]; + NSString *filetype = [attr objectForKey:NSFileType]; + if(filetype == NSFileTypeRegular && filesize) + { + // Ask for confirmation if file content is larger than 1MB + if ([filesize unsignedLongValue] > 1000000) { - if ([[aWindow windowController] isMemberOfClass:[SPWindowController class]]) { - frontController = [aWindow windowController]; - break; + NSAlert *alert = [[NSAlert alloc] init]; + [alert addButtonWithTitle:NSLocalizedString(@"OK", @"OK button")]; + [alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"cancel button")]; + + // Show 'Import' button only if there's a connection available + if ([self frontDocument]) + [alert addButtonWithTitle:NSLocalizedString(@"Import", @"import button")]; + + + [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"Do you really want to load a SQL file with %@ of data into the Query Editor?", @"message of panel asking for confirmation for loading large text into the query editor"), + [NSString stringForByteSize:[filesize longLongValue]]]]; + + [alert setHelpAnchor:filePath]; + [alert setMessageText:NSLocalizedString(@"Warning",@"warning")]; + [alert setAlertStyle:NSWarningAlertStyle]; + + NSUInteger returnCode = [alert runModal]; + + [alert release]; + + if(returnCode == NSAlertSecondButtonReturn) return; // Cancel + else if(returnCode == NSAlertThirdButtonReturn) { // Import + // begin import process + [[frontDocument valueForKeyPath:@"tableDumpInstance"] startSQLImportProcessWithFile:filePath]; + return; } } - - // If no window was found or the front most window has no tabs, create a new one - if (!frontController || [[frontController valueForKeyPath:@"tabView"] numberOfTabViewItems] == 1) { - [self newWindow:self]; - // Open the spf file in a new tab if the tab bar is visible - } - else if ([[frontController valueForKeyPath:@"tabView"] numberOfTabViewItems] != 1) { - if ([[frontController window] isMiniaturized]) [[frontController window] deminiaturize:self]; - [frontController addNewConnection:self]; - } - - [[self frontDocument] setStateFromConnectionFile:filePath]; - - [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:[NSURL fileURLWithPath:filePath]]; } - else if ([[[filePath pathExtension] lowercaseString] isEqualToString:[SPBundleFileExtension lowercaseString]]) { - - NSError *readError = nil; - NSString *convError = nil; - NSPropertyListFormat format; - NSDictionary *spfs = nil; - NSData *pData = [NSData dataWithContentsOfFile:[NSString stringWithFormat:@"%@/info.plist", filePath] options:NSUncachedRead error:&readError]; - - spfs = [[NSPropertyListSerialization propertyListFromData:pData - mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&convError] retain]; - - if(!spfs || readError != nil || [convError length] || !(format == NSPropertyListXMLFormat_v1_0 || format == NSPropertyListBinaryFormat_v1_0)) { - NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while reading connection data file", @"error while reading connection data file")] - defaultButton:NSLocalizedString(@"OK", @"OK button") - alternateButton:nil - otherButton:nil - informativeTextWithFormat:NSLocalizedString(@"Connection data file couldn't be read.", @"error while reading connection data file")]; - - [alert setAlertStyle:NSCriticalAlertStyle]; - [alert runModal]; - - if (spfs) [spfs release]; + } + + // Attempt to open the file into a string. + NSStringEncoding sqlEncoding; + NSString *sqlString = nil; + + // If the user came from an openPanel use the chosen encoding + if (encodingPopUp) { + sqlEncoding = [[encodingPopUp selectedItem] tag]; + + // Otherwise, attempt to autodetect the encoding + } + else { + sqlEncoding = [[NSFileManager defaultManager] detectEncodingforFileAtPath:filePath]; + } + + NSError *error = nil; + + sqlString = [NSString stringWithContentsOfFile:filePath encoding:sqlEncoding error:&error]; + + if (error != nil) { + NSAlert *errorAlert = [NSAlert alertWithError:error]; + [errorAlert runModal]; + + return; + } + + // if encodingPopUp is defined the filename comes from an openPanel and + // the encodingPopUp contains the chosen encoding; otherwise autodetect encoding + if (encodingPopUp) { + [[NSUserDefaults standardUserDefaults] setInteger:[[encodingPopUp selectedItem] tag] forKey:SPLastSQLFileEncoding]; + } + + // Check if at least one document exists. If not, open one. + if (!frontDocument) { + frontDocument = [self makeNewConnectionTabOrWindow]; + [frontDocument initQueryEditorWithString:sqlString]; + } + else { + // Pass query to the Query editor of the current document + [frontDocument doPerformLoadQueryService:sqlString]; + } + + [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:[NSURL fileURLWithPath:filePath]]; + + [frontDocument setSqlFileURL:[NSURL fileURLWithPath:filePath]]; + [frontDocument setSqlFileEncoding:sqlEncoding]; +} - return; +- (void)openSessionBundleAtPath:(NSString *)filePath +{ + NSError *readError = nil; + NSString *convError = nil; + NSPropertyListFormat format; + NSDictionary *spfs = nil; + NSData *pData = [NSData dataWithContentsOfFile:[NSString stringWithFormat:@"%@/info.plist", filePath] options:NSUncachedRead error:&readError]; + + spfs = [[NSPropertyListSerialization propertyListFromData:pData + mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&convError] retain]; + + if(!spfs || readError != nil || [convError length] || !(format == NSPropertyListXMLFormat_v1_0 || format == NSPropertyListBinaryFormat_v1_0)) { + NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while reading connection data file", @"error while reading connection data file")] + defaultButton:NSLocalizedString(@"OK", @"OK button") + alternateButton:nil + otherButton:nil + informativeTextWithFormat:NSLocalizedString(@"Connection data file couldn't be read.", @"error while reading connection data file")]; + + [alert setAlertStyle:NSCriticalAlertStyle]; + [alert runModal]; + + if (spfs) [spfs release]; + + return; + } + + + if([spfs objectForKey:@"windows"] && [[spfs objectForKey:@"windows"] isKindOfClass:[NSArray class]]) { + + NSFileManager *fileManager = [NSFileManager defaultManager]; + + // Retrieve Save Panel accessory view data for remembering them globally + NSMutableDictionary *spfsDocData = [NSMutableDictionary dictionary]; + [spfsDocData setObject:[NSNumber numberWithBool:[[spfs objectForKey:@"encrypted"] boolValue]] forKey:@"encrypted"]; + [spfsDocData setObject:[NSNumber numberWithBool:[[spfs objectForKey:@"auto_connect"] boolValue]] forKey:@"auto_connect"]; + [spfsDocData setObject:[NSNumber numberWithBool:[[spfs objectForKey:@"save_password"] boolValue]] forKey:@"save_password"]; + [spfsDocData setObject:[NSNumber numberWithBool:[[spfs objectForKey:@"include_session"] boolValue]] forKey:@"include_session"]; + [spfsDocData setObject:[NSNumber numberWithBool:[[spfs objectForKey:@"save_editor_content"] boolValue]] forKey:@"save_editor_content"]; + + // Set global session properties + [SPAppDelegate setSpfSessionDocData:spfsDocData]; + [SPAppDelegate setSessionURL:filePath]; + + // Loop through each defined window in reversed order to reconstruct the last active window + for (NSDictionary *window in [[[spfs objectForKey:@"windows"] reverseObjectEnumerator] allObjects]) + { + // Create a new window controller, and set up a new connection view within it. + SPWindowController *newWindowController = [[SPWindowController alloc] initWithWindowNibName:@"MainWindow"]; + NSWindow *newWindow = [newWindowController window]; + + // If window has more than 1 tab then set setHideForSingleTab to NO + // in order to avoid animation problems while opening tabs + if([[window objectForKey:@"tabs"] count] > 1) + [newWindowController setHideForSingleTab:NO]; + + // The first window should use autosaving; subsequent windows should cascade. + // So attempt to set the frame autosave name; this will succeed for the very + // first window, and fail for others. + BOOL usedAutosave = [newWindow setFrameAutosaveName:@"DBView"]; + + if (!usedAutosave) { + [newWindow setFrameUsingName:@"DBView"]; } - - - if([spfs objectForKey:@"windows"] && [[spfs objectForKey:@"windows"] isKindOfClass:[NSArray class]]) { - - NSFileManager *fileManager = [NSFileManager defaultManager]; - - // Retrieve Save Panel accessory view data for remembering them globally - NSMutableDictionary *spfsDocData = [NSMutableDictionary dictionary]; - [spfsDocData setObject:[NSNumber numberWithBool:[[spfs objectForKey:@"encrypted"] boolValue]] forKey:@"encrypted"]; - [spfsDocData setObject:[NSNumber numberWithBool:[[spfs objectForKey:@"auto_connect"] boolValue]] forKey:@"auto_connect"]; - [spfsDocData setObject:[NSNumber numberWithBool:[[spfs objectForKey:@"save_password"] boolValue]] forKey:@"save_password"]; - [spfsDocData setObject:[NSNumber numberWithBool:[[spfs objectForKey:@"include_session"] boolValue]] forKey:@"include_session"]; - [spfsDocData setObject:[NSNumber numberWithBool:[[spfs objectForKey:@"save_editor_content"] boolValue]] forKey:@"save_editor_content"]; - - // Set global session properties - [SPAppDelegate setSpfSessionDocData:spfsDocData]; - [SPAppDelegate setSessionURL:filePath]; - - // Loop through each defined window in reversed order to reconstruct the last active window - for (NSDictionary *window in [[[spfs objectForKey:@"windows"] reverseObjectEnumerator] allObjects]) - { - // Create a new window controller, and set up a new connection view within it. - SPWindowController *newWindowController = [[SPWindowController alloc] initWithWindowNibName:@"MainWindow"]; - NSWindow *newWindow = [newWindowController window]; - - // If window has more than 1 tab then set setHideForSingleTab to NO - // in order to avoid animation problems while opening tabs - if([[window objectForKey:@"tabs"] count] > 1) - [newWindowController setHideForSingleTab:NO]; - - // The first window should use autosaving; subsequent windows should cascade. - // So attempt to set the frame autosave name; this will succeed for the very - // first window, and fail for others. - BOOL usedAutosave = [newWindow setFrameAutosaveName:@"DBView"]; - - if (!usedAutosave) { - [newWindow setFrameUsingName:@"DBView"]; - } - - if ([window objectForKey:@"frame"]) - { - [newWindow setFrame:NSRectFromString([window objectForKey:@"frame"]) display:NO]; - } - - // Set the window controller as the window's delegate - [newWindow setDelegate:newWindowController]; - - usleep(1000); - - // Show the window - [newWindowController showWindow:self]; - - // Loop through all defined tabs for each window - for (NSDictionary *tab in [window objectForKey:@"tabs"]) - { - NSString *fileName = nil; - BOOL isBundleFile = NO; - - // If isAbsolutePath then take this path directly - // otherwise construct the releative path for the passed spfs file - if ([[tab objectForKey:@"isAbsolutePath"] boolValue]) { - fileName = [tab objectForKey:@"path"]; - } - else { - fileName = [NSString stringWithFormat:@"%@/Contents/%@", filePath, [tab objectForKey:@"path"]]; - isBundleFile = YES; - } - - // Security check if file really exists - if ([fileManager fileExistsAtPath:fileName]) { - - // Add new the tab - if(newWindowController) { - - if ([[newWindowController window] isMiniaturized]) [[newWindowController window] deminiaturize:self]; - [newWindowController addNewConnection:self]; - - [[self frontDocument] setIsSavedInBundle:isBundleFile]; - if (![[self frontDocument] setStateFromConnectionFile:fileName]) { - break; - } - } - - } - else { - NSLog(@"Bundle file “%@” does not exists", fileName); - NSBeep(); + + if ([window objectForKey:@"frame"]) + { + [newWindow setFrame:NSRectFromString([window objectForKey:@"frame"]) display:NO]; + } + + // Set the window controller as the window's delegate + [newWindow setDelegate:newWindowController]; + + usleep(1000); + + // Show the window + [newWindowController showWindow:self]; + + // Loop through all defined tabs for each window + for (NSDictionary *tab in [window objectForKey:@"tabs"]) + { + NSString *fileName = nil; + BOOL isBundleFile = NO; + + // If isAbsolutePath then take this path directly + // otherwise construct the releative path for the passed spfs file + if ([[tab objectForKey:@"isAbsolutePath"] boolValue]) { + fileName = [tab objectForKey:@"path"]; + } + else { + fileName = [NSString stringWithFormat:@"%@/Contents/%@", filePath, [tab objectForKey:@"path"]]; + isBundleFile = YES; + } + + // Security check if file really exists + if ([fileManager fileExistsAtPath:fileName]) { + + // Add new the tab + if(newWindowController) { + + if ([[newWindowController window] isMiniaturized]) [[newWindowController window] deminiaturize:self]; + SPDatabaseDocument *newConnection = [newWindowController addNewConnection]; + + [newConnection setIsSavedInBundle:isBundleFile]; + if (![newConnection setStateFromConnectionFile:fileName]) { + break; } } - - // Select active tab - [newWindowController selectTabAtIndex:[[window objectForKey:@"selectedTabIndex"] intValue]]; - - // Reset setHideForSingleTab - if ([[NSUserDefaults standardUserDefaults] objectForKey:SPAlwaysShowWindowTabBar]) { - [newWindowController setHideForSingleTab:[[NSUserDefaults standardUserDefaults] boolForKey:SPAlwaysShowWindowTabBar]]; - } - else { - [newWindowController setHideForSingleTab:YES]; - } + } - } - - [spfs release]; - - [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:[NSURL fileURLWithPath:filePath]]; - } - else if ([[[filePath pathExtension] lowercaseString] isEqualToString:[SPColorThemeFileExtension lowercaseString]]) { - - NSFileManager *fm = [NSFileManager defaultManager]; - - NSString *themePath = [[NSFileManager defaultManager] applicationSupportDirectoryForSubDirectory:SPThemesSupportFolder error:nil]; - - if (!themePath) return; - - if (![fm fileExistsAtPath:themePath isDirectory:nil]) { - if (![fm createDirectoryAtPath:themePath withIntermediateDirectories:YES attributes:nil error:nil]) { + else { + NSLog(@"Bundle file “%@” does not exists", fileName); NSBeep(); - return; } } - - NSString *newPath = [NSString stringWithFormat:@"%@/%@", themePath, [filePath lastPathComponent]]; - - if (![fm fileExistsAtPath:newPath isDirectory:nil]) { - if (![fm moveItemAtPath:filePath toPath:newPath error:nil]) { - NSBeep(); - return; - } + + // Select active tab + [newWindowController selectTabAtIndex:[[window objectForKey:@"selectedTabIndex"] intValue]]; + + // Reset setHideForSingleTab + if ([[NSUserDefaults standardUserDefaults] objectForKey:SPAlwaysShowWindowTabBar]) { + [newWindowController setHideForSingleTab:[[NSUserDefaults standardUserDefaults] boolForKey:SPAlwaysShowWindowTabBar]]; } else { - NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while installing color theme file", @"error while installing color theme file")] - defaultButton:NSLocalizedString(@"OK", @"OK button") - alternateButton:nil - otherButton:nil - informativeTextWithFormat:NSLocalizedString(@"The color theme ‘%@’ already exists.", @"the color theme ‘%@’ already exists."), [filePath lastPathComponent]]; - - [alert setAlertStyle:NSCriticalAlertStyle]; - [alert runModal]; - - return; + [newWindowController setHideForSingleTab:YES]; } } - else if ([[[filePath pathExtension] lowercaseString] isEqualToString:[SPUserBundleFileExtension lowercaseString]]) { - - NSFileManager *fm = [NSFileManager defaultManager]; - - NSString *bundlePath = [fm applicationSupportDirectoryForSubDirectory:SPBundleSupportFolder error:nil]; - - if (!bundlePath) return; - - if (![fm fileExistsAtPath:bundlePath isDirectory:nil]) { - if (![fm createDirectoryAtPath:bundlePath withIntermediateDirectories:YES attributes:nil error:nil]) { - NSBeep(); - NSLog(@"Couldn't create folder “%@”", bundlePath); - return; - } - } - - NSString *newPath = [NSString stringWithFormat:@"%@/%@", bundlePath, [filePath lastPathComponent]]; - - NSError *readError = nil; - NSString *convError = nil; - NSPropertyListFormat format; - NSDictionary *cmdData = nil; - NSString *infoPath = [NSString stringWithFormat:@"%@/%@", filePath, SPBundleFileName]; - NSData *pData = [NSData dataWithContentsOfFile:infoPath options:NSUncachedRead error:&readError]; - - cmdData = [[NSPropertyListSerialization propertyListFromData:pData - mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&convError] retain]; + } + + [spfs release]; + + [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:[NSURL fileURLWithPath:filePath]]; +} - if (!cmdData || readError != nil || [convError length] || !(format == NSPropertyListXMLFormat_v1_0 || format == NSPropertyListBinaryFormat_v1_0)) { - NSLog(@"“%@/%@” file couldn't be read.", filePath, SPBundleFileName); - NSBeep(); - if (cmdData) [cmdData release]; - return; - } - else { - // Check for installed UUIDs - if (![cmdData objectForKey:SPBundleFileUUIDKey]) { - NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while installing Bundle", @"Open Files : Bundle : UUID : Error dialog title")] - defaultButton:NSLocalizedString(@"OK", @"Open Files : Bundle : UUID : OK button") - alternateButton:nil - otherButton:nil - informativeTextWithFormat:NSLocalizedString(@"The Bundle ‘%@’ has no UUID which is necessary to identify installed Bundles.", @"Open Files : Bundle: UUID : UUID-Attribute is missing in bundle's command.plist file"), [filePath lastPathComponent]]; +- (void)openColorThemeFileAtPath:(NSString *)filePath +{ + NSFileManager *fm = [NSFileManager defaultManager]; + + NSString *themePath = [[NSFileManager defaultManager] applicationSupportDirectoryForSubDirectory:SPThemesSupportFolder error:nil]; + + if (!themePath) return; + + if (![fm fileExistsAtPath:themePath isDirectory:nil]) { + if (![fm createDirectoryAtPath:themePath withIntermediateDirectories:YES attributes:nil error:nil]) { + NSBeep(); + return; + } + } + + NSString *newPath = [NSString stringWithFormat:@"%@/%@", themePath, [filePath lastPathComponent]]; + + if (![fm fileExistsAtPath:newPath isDirectory:nil]) { + if (![fm moveItemAtPath:filePath toPath:newPath error:nil]) { + NSBeep(); + return; + } + } + else { + NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while installing color theme file", @"error while installing color theme file")] + defaultButton:NSLocalizedString(@"OK", @"OK button") + alternateButton:nil + otherButton:nil + informativeTextWithFormat:NSLocalizedString(@"The color theme ‘%@’ already exists.", @"the color theme ‘%@’ already exists."), [filePath lastPathComponent]]; + + [alert setAlertStyle:NSCriticalAlertStyle]; + [alert runModal]; + + return; + } +} +- (void)openUserBundleAtPath:(NSString *)filePath +{ + NSFileManager *fm = [NSFileManager defaultManager]; + + NSString *bundlePath = [fm applicationSupportDirectoryForSubDirectory:SPBundleSupportFolder error:nil]; + + if (!bundlePath) return; + + if (![fm fileExistsAtPath:bundlePath isDirectory:nil]) { + if (![fm createDirectoryAtPath:bundlePath withIntermediateDirectories:YES attributes:nil error:nil]) { + NSBeep(); + NSLog(@"Couldn't create folder “%@”", bundlePath); + return; + } + } + + NSString *newPath = [NSString stringWithFormat:@"%@/%@", bundlePath, [filePath lastPathComponent]]; + + NSError *readError = nil; + NSString *convError = nil; + NSPropertyListFormat format; + NSDictionary *cmdData = nil; + NSString *infoPath = [NSString stringWithFormat:@"%@/%@", filePath, SPBundleFileName]; + NSData *pData = [NSData dataWithContentsOfFile:infoPath options:NSUncachedRead error:&readError]; + + cmdData = [[NSPropertyListSerialization propertyListFromData:pData + mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&convError] retain]; + + if (!cmdData || readError != nil || [convError length] || !(format == NSPropertyListXMLFormat_v1_0 || format == NSPropertyListBinaryFormat_v1_0)) { + NSLog(@"“%@/%@” file couldn't be read.", filePath, SPBundleFileName); + NSBeep(); + if (cmdData) [cmdData release]; + return; + } + else { + // Check for installed UUIDs + if (![cmdData objectForKey:SPBundleFileUUIDKey]) { + NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while installing Bundle", @"Open Files : Bundle : UUID : Error dialog title")] + defaultButton:NSLocalizedString(@"OK", @"Open Files : Bundle : UUID : OK button") + alternateButton:nil + otherButton:nil + informativeTextWithFormat:NSLocalizedString(@"The Bundle ‘%@’ has no UUID which is necessary to identify installed Bundles.", @"Open Files : Bundle: UUID : UUID-Attribute is missing in bundle's command.plist file"), [filePath lastPathComponent]]; + + [alert setAlertStyle:NSCriticalAlertStyle]; + [alert runModal]; + if (cmdData) [cmdData release]; + return; + } + + // Reload Bundles if Sequel Pro didn't run + if (![installedBundleUUIDs count]) { + [self reloadBundles:self]; + } + + if ([[installedBundleUUIDs allKeys] containsObject:[cmdData objectForKey:SPBundleFileUUIDKey]]) { + NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Installing Bundle", @"Open Files : Bundle : Already-Installed : 'Update Bundle' question dialog title")] + defaultButton:NSLocalizedString(@"Update", @"Open Files : Bundle : Already-Installed : Update button") + alternateButton:NSLocalizedString(@"Cancel", @"Open Files : Bundle : Already-Installed : Cancel button") + otherButton:nil + informativeTextWithFormat:NSLocalizedString(@"A Bundle ‘%@’ is already installed. Do you want to update it?", @"Open Files : Bundle : Already-Installed : 'Update Bundle' question dialog message"), [[installedBundleUUIDs objectForKey:[cmdData objectForKey:SPBundleFileUUIDKey]] objectForKey:@"name"]]; + + [alert setAlertStyle:NSCriticalAlertStyle]; + NSInteger answer = [alert runModal]; + + if (answer == NSAlertDefaultReturn) { + NSError *error = nil; + NSString *removePath = [[[installedBundleUUIDs objectForKey:[cmdData objectForKey:SPBundleFileUUIDKey]] objectForKey:@"path"] substringToIndex:([(NSString *)[[installedBundleUUIDs objectForKey:[cmdData objectForKey:SPBundleFileUUIDKey]] objectForKey:@"path"] length]-[SPBundleFileName length]-1)]; + NSString *moveToTrashCommand = [NSString stringWithFormat:@"osascript -e 'tell application \"Finder\" to move (POSIX file \"%@\") to the trash'", removePath]; + + [SPBundleCommandRunner runBashCommand:moveToTrashCommand withEnvironment:nil atCurrentDirectoryPath:nil error:&error]; + + if (error != nil) { + alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while moving “%@” to Trash.", @"Open Files : Bundle : Already-Installed : Delete-Old-Error : Could not delete old bundle before installing new version."), removePath] + defaultButton:NSLocalizedString(@"OK", @"Open Files : Bundle : Already-Installed : Delete-Old-Error : OK button") + alternateButton:nil + otherButton:nil + informativeTextWithFormat:@"%@", [error localizedDescription]]; + [alert setAlertStyle:NSCriticalAlertStyle]; [alert runModal]; if (cmdData) [cmdData release]; return; } - - // Reload Bundles if Sequel Pro didn't run - if (![installedBundleUUIDs count]) { - [self reloadBundles:self]; - } - - if ([[installedBundleUUIDs allKeys] containsObject:[cmdData objectForKey:SPBundleFileUUIDKey]]) { - NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Installing Bundle", @"Open Files : Bundle : Already-Installed : 'Update Bundle' question dialog title")] - defaultButton:NSLocalizedString(@"Update", @"Open Files : Bundle : Already-Installed : Update button") - alternateButton:NSLocalizedString(@"Cancel", @"Open Files : Bundle : Already-Installed : Cancel button") - otherButton:nil - informativeTextWithFormat:NSLocalizedString(@"A Bundle ‘%@’ is already installed. Do you want to update it?", @"Open Files : Bundle : Already-Installed : 'Update Bundle' question dialog message"), [[installedBundleUUIDs objectForKey:[cmdData objectForKey:SPBundleFileUUIDKey]] objectForKey:@"name"]]; - - [alert setAlertStyle:NSCriticalAlertStyle]; - NSInteger answer = [alert runModal]; - - if (answer == NSAlertDefaultReturn) { - NSError *error = nil; - NSString *removePath = [[[installedBundleUUIDs objectForKey:[cmdData objectForKey:SPBundleFileUUIDKey]] objectForKey:@"path"] substringToIndex:([(NSString *)[[installedBundleUUIDs objectForKey:[cmdData objectForKey:SPBundleFileUUIDKey]] objectForKey:@"path"] length]-[SPBundleFileName length]-1)]; - NSString *moveToTrashCommand = [NSString stringWithFormat:@"osascript -e 'tell application \"Finder\" to move (POSIX file \"%@\") to the trash'", removePath]; - - [SPBundleCommandRunner runBashCommand:moveToTrashCommand withEnvironment:nil atCurrentDirectoryPath:nil error:&error]; - - if (error != nil) { - alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while moving “%@” to Trash.", @"Open Files : Bundle : Already-Installed : Delete-Old-Error : Could not delete old bundle before installing new version."), removePath] - defaultButton:NSLocalizedString(@"OK", @"Open Files : Bundle : Already-Installed : Delete-Old-Error : OK button") - alternateButton:nil - otherButton:nil - informativeTextWithFormat:@"%@", [error localizedDescription]]; - - [alert setAlertStyle:NSCriticalAlertStyle]; - [alert runModal]; - if (cmdData) [cmdData release]; - return; - } - } - else { - if (cmdData) [cmdData release]; - - return; - } - } - } - - if (cmdData) [cmdData release]; - - if (![fm fileExistsAtPath:newPath isDirectory:nil]) { - if (![fm moveItemAtPath:filePath toPath:newPath error:nil]) { - NSBeep(); - NSLog(@"Couldn't move “%@” to “%@”", filePath, newPath); - return; - } - - // Update Bundle Editor if it was already initialized - for (NSWindow *win in [NSApp windows]) - { - if ([[win delegate] class] == [SPBundleEditorController class]) { - [((SPBundleEditorController *)[win delegate]) reloadBundles:nil]; - break; - } - } - - // Update Bundels' menu - [self reloadBundles:self]; - } else { - NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while installing Bundle", @"Open Files : Bundle : Install-Error : error dialog title")] - defaultButton:NSLocalizedString(@"OK", @"Open Files : Bundle : Install-Error : OK button") - alternateButton:nil - otherButton:nil - informativeTextWithFormat:NSLocalizedString(@"The Bundle ‘%@’ already exists.", @"Open Files : Bundle : Install-Error : Destination path already exists error dialog message"), [filePath lastPathComponent]]; - - [alert setAlertStyle:NSCriticalAlertStyle]; - [alert runModal]; - + if (cmdData) [cmdData release]; + return; } } - else { - NSLog(@"Only files with the extensions ‘%@’, ‘%@’, ‘%@’ or ‘%@’ are allowed.", SPFileExtensionDefault, SPBundleFileExtension, SPColorThemeFileExtension, SPFileExtensionSQL); + } + + if (cmdData) [cmdData release]; + + if (![fm fileExistsAtPath:newPath isDirectory:nil]) { + if (![fm moveItemAtPath:filePath toPath:newPath error:nil]) { + NSBeep(); + NSLog(@"Couldn't move “%@” to “%@”", filePath, newPath); + return; } + + // Update Bundle Editor if it was already initialized + for (NSWindow *win in [NSApp windows]) + { + if ([[win delegate] class] == [SPBundleEditorController class]) { + [((SPBundleEditorController *)[win delegate]) reloadBundles:nil]; + break; + } + } + + // Update Bundels' menu + [self reloadBundles:self]; + + } + else { + NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while installing Bundle", @"Open Files : Bundle : Install-Error : error dialog title")] + defaultButton:NSLocalizedString(@"OK", @"Open Files : Bundle : Install-Error : OK button") + alternateButton:nil + otherButton:nil + informativeTextWithFormat:NSLocalizedString(@"The Bundle ‘%@’ already exists.", @"Open Files : Bundle : Install-Error : Destination path already exists error dialog message"), [filePath lastPathComponent]]; + + [alert setAlertStyle:NSCriticalAlertStyle]; + [alert runModal]; } } @@ -784,8 +773,7 @@ } // make connection window - [self newTab:nil]; - SPDatabaseDocument *doc = [self frontDocument]; + SPDatabaseDocument *doc = [self makeNewConnectionTabOrWindow]; NSMutableDictionary *details = [NSMutableDictionary dictionary]; @@ -904,7 +892,7 @@ return; } - NSString *activeProcessID = [[(SPWindowController *)[[self frontDocumentWindow] delegate] selectedTableDocument] processID]; + NSString *activeProcessID = [[self frontDocument] processID]; SPDatabaseDocument *processDocument = nil; @@ -912,26 +900,24 @@ // For speed check the front most first otherwise iterate through all if(passedProcessID && [passedProcessID length]) { if([activeProcessID isEqualToString:passedProcessID]) { - processDocument = [(SPWindowController *)[[self frontDocumentWindow] delegate] selectedTableDocument]; + processDocument = [self frontDocument]; } else { - for (NSWindow *aWindow in [NSApp orderedWindows]) { - if([[aWindow windowController] isMemberOfClass:[SPWindowController class]]) { - for(SPDatabaseDocument *doc in [[aWindow windowController] documents]) { - if([doc processID] && [[doc processID] isEqualToString:passedProcessID]) { - processDocument = doc; - break; - } + for (NSWindow *aWindow in [self orderedDatabaseConnectionWindows]) { + for(SPDatabaseDocument *doc in [[aWindow windowController] documents]) { + if([doc processID] && [[doc processID] isEqualToString:passedProcessID]) { + processDocument = doc; + goto break_loop; } } - if(processDocument) break; } + break_loop: /* breaking two levels of foreach */; } } // if no processDoc found and no passedProcessID was passed execute // command at front most doc if(!processDocument && !passedProcessID) - processDocument = [(SPWindowController *)[[self frontDocumentWindow] delegate] selectedTableDocument]; + processDocument = [self frontDocument]; if(processDocument && command) { if([command isEqualToString:@"passToDoc"]) { @@ -1273,19 +1259,15 @@ if(docUUID == nil) doc = [self frontDocument]; else { - BOOL found = NO; - for (NSWindow *aWindow in [NSApp orderedWindows]) { - if([[aWindow windowController] isMemberOfClass:[SPWindowController class]]) { - for(SPDatabaseDocument *d in [[aWindow windowController] documents]) { - if([d processID] && [[d processID] isEqualToString:docUUID]) { - [env addEntriesFromDictionary:[d shellVariables]]; - found = YES; - break; - } + for (NSWindow *aWindow in [self orderedDatabaseConnectionWindows]) { + for(SPDatabaseDocument *d in [[aWindow windowController] documents]) { + if([d processID] && [[d processID] isEqualToString:docUUID]) { + [env addEntriesFromDictionary:[d shellVariables]]; + goto break_loop; } } - if(found) break; } + break_loop: /* breaking two levels of foreach */; } id firstResponder = [[NSApp keyWindow] firstResponder]; @@ -1440,13 +1422,7 @@ */ - (SPDatabaseDocument *) frontDocument { - for (NSWindow *aWindow in [NSApp orderedWindows]) { - if ([[aWindow windowController] isMemberOfClass:[SPWindowController class]]) { - return [[aWindow windowController] selectedTableDocument]; - } - } - - return nil; + return [[self frontController] selectedTableDocument]; } /** @@ -2224,14 +2200,11 @@ } // Iterate through each open window - for (NSWindow *aWindow in [NSApp orderedWindows]) + for (NSWindow *aWindow in [self orderedDatabaseConnectionWindows]) { - if (![[aWindow windowController] isMemberOfClass:[SPWindowController class]]) continue; - // Iterate through each document in the window for (SPDatabaseDocument *doc in [[aWindow windowController] documents]) { - // Kill any BASH commands which are currently active for (NSDictionary* cmd in [doc runningActivities]) { diff --git a/Source/SPGrowlController.m b/Source/SPGrowlController.m index be659041..816196bf 100644 --- a/Source/SPGrowlController.m +++ b/Source/SPGrowlController.m @@ -31,6 +31,7 @@ #import "SPGrowlController.h" #import "SPDatabaseDocument.h" #import "SPWindowController.h" +#import "SPAppController.h" #include @@ -167,16 +168,14 @@ static SPGrowlController *sharedGrowlController = nil; NSUInteger documentHash = [[clickContext objectForKey:@"notificationDocumentHash"] unsignedIntegerValue]; // Loop through the windows, looking for the document - for (NSWindow *eachWindow in [NSApp orderedWindows]) + for (NSWindow *eachWindow in [SPAppDelegate orderedDatabaseConnectionWindows]) { - if ([[eachWindow windowController] isKindOfClass:[SPWindowController class]]) { - for (SPDatabaseDocument *eachDocument in [[eachWindow windowController] documents]) - { - if ([eachDocument hash] == documentHash) { - [NSApp activateIgnoringOtherApps:YES]; - [eachDocument makeKeyDocument]; - return; - } + for (SPDatabaseDocument *eachDocument in [[eachWindow windowController] documents]) + { + if ([eachDocument hash] == documentHash) { + [NSApp activateIgnoringOtherApps:YES]; + [eachDocument makeKeyDocument]; + return; } } } diff --git a/Source/SPWindowController.h b/Source/SPWindowController.h index a86a85f9..528c1c84 100644 --- a/Source/SPWindowController.h +++ b/Source/SPWindowController.h @@ -50,6 +50,8 @@ - (IBAction)addNewConnection:(id)sender; - (IBAction)moveSelectedTabInNewWindow:(id)sender; +- (SPDatabaseDocument *)addNewConnection; + /** * @danger THIS IS NOT RETAINED!!! * diff --git a/Source/SPWindowController.m b/Source/SPWindowController.m index 68c89ad8..963919d0 100644 --- a/Source/SPWindowController.m +++ b/Source/SPWindowController.m @@ -103,6 +103,11 @@ enum { * Add a new database connection to the window, in a tab view. */ - (IBAction)addNewConnection:(id)sender +{ + [self addNewConnection]; +} + +- (SPDatabaseDocument *)addNewConnection { // Create a new database connection view SPDatabaseDocument *newTableDocument = [[SPDatabaseDocument alloc] init]; @@ -125,8 +130,8 @@ enum { // Bind the tab bar's progress display to the document [self _updateProgressIndicatorForItem:newItem]; - - [newTableDocument release]; + + return [newTableDocument autorelease]; } /** diff --git a/Source/SPWindowManagement.h b/Source/SPWindowManagement.h index 66f65b6d..4d32d6d4 100644 --- a/Source/SPWindowManagement.h +++ b/Source/SPWindowManagement.h @@ -30,6 +30,8 @@ #import "SPAppController.h" +@class SPWindowController; + /** * @category SPWindowManagement SPWindowManagement.h * @@ -43,6 +45,10 @@ - (IBAction)newTab:(id)sender; - (IBAction)duplicateTab:(id)sender; +- (SPWindowController *)newWindow; +- (SPDatabaseDocument *)makeNewConnectionTabOrWindow; +- (SPWindowController *)frontController; + - (NSWindow *)frontDocumentWindow; - (void)tabDragStarted:(id)sender; diff --git a/Source/SPWindowManagement.m b/Source/SPWindowManagement.m index c30a2826..6225c52f 100644 --- a/Source/SPWindowManagement.m +++ b/Source/SPWindowManagement.m @@ -32,10 +32,16 @@ @implementation SPAppController (SPWindowManagement) + +- (IBAction)newWindow:(id)sender +{ + [self newWindow]; +} + /** * Create a new window, containing a single tab. */ -- (IBAction)newWindow:(id)sender +- (SPWindowController *)newWindow { static NSPoint cascadeLocation = {.x = 0, .y = 0}; @@ -58,7 +64,7 @@ } // Add the connection view - [newWindowController addNewConnection:self]; + [newWindowController addNewConnection]; // Cascade according to the statically stored cascade location. cascadeLocation = [newWindow cascadeTopLeftFromPoint:cascadeLocation]; @@ -69,6 +75,8 @@ // Show the window, and perform frontmost tasks again once the window has drawn [newWindowController showWindow:self]; [[newWindowController selectedTableDocument] didBecomeActiveTabInWindow]; + + return newWindowController; } /** @@ -76,15 +84,7 @@ */ - (IBAction)newTab:(id)sender { - SPWindowController *frontController = nil; - - for (NSWindow *aWindow in [NSApp orderedWindows]) - { - if ([[aWindow windowController] isMemberOfClass:[SPWindowController class]]) { - frontController = [aWindow windowController]; - break; - } - } + SPWindowController *frontController = [self frontController]; // If no window was found, create a new one if (!frontController) { @@ -99,6 +99,25 @@ } } +- (SPDatabaseDocument *)makeNewConnectionTabOrWindow +{ + SPWindowController *frontController = [self frontController]; + + SPDatabaseDocument *frontDocument; + // If no window was found or the front most window has no tabs, create a new one + if (!frontController || [[frontController valueForKeyPath:@"tabView"] numberOfTabViewItems] == 1) { + frontController = [self newWindow]; + frontDocument = [frontController selectedTableDocument]; + } + // Open the spf file in a new tab if the tab bar is visible + else { + if ([[frontController window] isMiniaturized]) [[frontController window] deminiaturize:self]; + frontDocument = [frontController addNewConnection]; + } + + return frontDocument; +} + /** * Duplicate the current connection tab */ @@ -113,7 +132,7 @@ [[self frontDocumentWindow] deminiaturize:self]; } - [[[self frontDocumentWindow] windowController] addNewConnection:self]; + SPDatabaseDocument *newConnection = [[self frontController] addNewConnection]; // Get the state of the previously-frontmost document NSDictionary *allStateDetails = @{ @@ -130,20 +149,25 @@ [frontState setObject:@YES forKey:@"auto_connect"]; // Set the connection on the new tab - [[self frontDocument] setState:frontState]; + [newConnection setState:frontState]; } /** * Retrieve the frontmost document window; returns nil if not found. */ - (NSWindow *)frontDocumentWindow +{ + return [[self frontController] window]; +} + +- (SPWindowController *)frontController { for (NSWindow *aWindow in [NSApp orderedWindows]) { - if ([[aWindow windowController] isMemberOfClass:[SPWindowController class]]) { - return aWindow; + id ctr = [aWindow windowController]; + if ([ctr isMemberOfClass:[SPWindowController class]]) { + return ctr; } } - return nil; } -- cgit v1.2.3 From b21b4ff0c81d9dca416e7945af39c95d0c89167a Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 6 Nov 2015 01:19:36 +0100 Subject: Fix an exception when right-clicking in the CSV field mapping table without active selection (#2219) --- Source/SPFieldMapperController.m | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Source/SPFieldMapperController.m b/Source/SPFieldMapperController.m index 1a6cfc83..82975e0e 100644 --- a/Source/SPFieldMapperController.m +++ b/Source/SPFieldMapperController.m @@ -1604,15 +1604,21 @@ static NSUInteger SPSourceColumnTypeInteger = 1; */ - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { - NSInteger row = [fieldMapperTableView selectedRow]; // Hide/display Remove New Column menu item [[[fieldMapperTableView menu] itemAtIndex:3] setHidden:([toBeEditedRowIndexes containsIndex:row]) ? NO : YES]; if (newTableMode && [menuItem action] == @selector(setAllTypesTo:)) { - NSString *orgTitle = [[menuItem title] substringToIndex:[[menuItem title] rangeOfString:@":"].location]; - [menuItem setTitle:[NSString stringWithFormat:@"%@: %@", orgTitle, [fieldMappingTableTypes objectAtIndex:row]]]; + if(row > -1) { // row == -1 on empty selection + NSString *orgTitle = [[menuItem title] substringToIndex:[[menuItem title] rangeOfString:@":"].location]; + [menuItem setTitle:[NSString stringWithFormat:@"%@: %@", orgTitle, [fieldMappingTableTypes objectAtIndex:row]]]; + [menuItem setHidden:NO]; + } + else { + [menuItem setHidden:YES]; + return NO; + } } else if (!newTableMode && [menuItem action] == @selector(insertNULLValue:)) { return ([[globalValuesTableView selectedRowIndexes] count] == 1) ? YES : NO; -- cgit v1.2.3 From 72951bfbb85dd91095823fc6a170d3ce41718246 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 6 Nov 2015 03:38:57 +0100 Subject: Add support for adding relations on MySQL < 5 (#530) --- Source/SPTableRelations.m | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/Source/SPTableRelations.m b/Source/SPTableRelations.m index 8a2a1568..f358d133 100644 --- a/Source/SPTableRelations.m +++ b/Source/SPTableRelations.m @@ -270,11 +270,25 @@ static NSString *SPRelationOnDeleteKey = @"on_delete"; } // Get all InnoDB tables in the current database - // TODO: MySQL 4 compatibility - SPMySQLResult *result = [connection queryString:[NSString stringWithFormat:@"SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND engine = 'InnoDB' AND table_schema = %@", [[tableDocumentInstance database] tickQuotedString]]]; - [result setDefaultRowReturnType:SPMySQLResultRowAsArray]; - for (NSArray *eachRow in result) { - [refTablePopUpButton addItemWithTitle:[eachRow objectAtIndex:0]]; + if ([[tableDocumentInstance serverSupport] supportsInformationSchema]) { + //MySQL 5.0+ + SPMySQLResult *result = [connection queryString:[NSString stringWithFormat:@"SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND engine = 'InnoDB' AND table_schema = %@", [[tableDocumentInstance database] tickQuotedString]]]; + [result setDefaultRowReturnType:SPMySQLResultRowAsArray]; + for (NSArray *eachRow in result) { + [refTablePopUpButton addItemWithTitle:[eachRow objectAtIndex:0]]; + } + } + else { + //this will work back to 3.23.0, innodb was added in 3.23.49 + SPMySQLResult *result = [connection queryString:[NSString stringWithFormat:@"SHOW TABLE STATUS FROM %@", [[tableDocumentInstance database] backtickQuotedString]]]; + [result setDefaultRowReturnType:SPMySQLResultRowAsArray]; + [result setReturnDataAsStrings:YES]; // some mysql versions would return NSData for string fields otherwise + for (NSArray *eachRow in result) { + // col[1] was named "Type" < 4.1, "Engine" afterwards + if(![[[eachRow objectAtIndex:1] uppercaseString] isEqualToString:@"INNODB"]) continue; + // col[0] is the table name + [refTablePopUpButton addItemWithTitle:[eachRow objectAtIndex:0]]; + } } // Reset other fields -- cgit v1.2.3 From fdef91b45a56b2f94aa477041b8f4185a2e7e7e2 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 7 Nov 2015 04:20:06 +0100 Subject: Allow import of saved export settings (in export dialog) --- Interfaces/English.lproj/ExportDialog.xib | 267 +++++++++++++++---- Source/SPBundleEditorController.m | 2 +- Source/SPConstants.h | 10 + Source/SPConstants.m | 1 + Source/SPExportController.h | 8 + Source/SPExportController.m | 73 +++-- Source/SPExportControllerDelegate.m | 18 +- Source/SPExportFilenameUtilities.h | 1 + Source/SPExportFilenameUtilities.m | 12 + Source/SPExportSettingsPersistence.h | 20 ++ Source/SPExportSettingsPersistence.m | 427 +++++++++++++++++++++++++++++- 11 files changed, 746 insertions(+), 93 deletions(-) diff --git a/Interfaces/English.lproj/ExportDialog.xib b/Interfaces/English.lproj/ExportDialog.xib index e7148f29..7080c27e 100644 --- a/Interfaces/English.lproj/ExportDialog.xib +++ b/Interfaces/English.lproj/ExportDialog.xib @@ -839,6 +839,82 @@ 268 + + + 265 + {{674, 347}, {41, 22}} + + + _NS:9 + YES + + 71303232 + 133120 + + _NS:9 + + 109199360 + 129 + + .LucidaGrandeUI + 11 + 16 + + + + 400 + 75 + + + YES + S + + 1048576 + 2147483647 + 1 + + + _popUpItemAction: + + + YES + + + + + + + Apply saved settings… + o + 1048576 + 2147483647 + + + _popUpItemAction: + + + + + Save current settings… + s + 1048576 + 2147483647 + + + _popUpItemAction: + + + + + -1 + YES + 1 + YES + YES + 2 + + NO + 34 @@ -860,6 +936,7 @@ {413, 22} + YES 134217728 @@ -881,6 +958,7 @@ 292 {{-1, -1}, {32, 25}} + YES 67108864 @@ -906,6 +984,7 @@ 289 {{381, -2}, {32, 25}} + 1 YES @@ -932,6 +1011,7 @@ 289 {{350, -2}, {32, 25}} + YES 67108864 @@ -955,10 +1035,12 @@ {{1, 1}, {412, 21}} + {{21, 20}, {414, 23}} + {0, 0} 67108864 @@ -987,6 +1069,7 @@ 268 {{17, 285}, {207, 22}} + YES -2076180416 @@ -999,9 +1082,9 @@ 400 75 - + - Tables + Filtered Results 1048576 2147483647 @@ -1015,9 +1098,10 @@ OtherViews - + + - Filtered Results + Query Results 1048576 2147483647 @@ -1026,9 +1110,9 @@ _popUpItemAction: - + - Query Results + Tables 1048576 2147483647 @@ -1037,12 +1121,11 @@ _popUpItemAction: - YES - 2 + -1 1 YES YES @@ -1067,6 +1150,7 @@ 266 {{29, 59}, {699, 19}} + YES -1804599231 @@ -1091,6 +1175,7 @@ 266 {{29, 86}, {702, 14}} + YES 68157504 @@ -1117,6 +1202,7 @@ 258 {{21, 4}, {667, 22}} + YES -2077228991 @@ -1133,10 +1219,12 @@ {{1, 1}, {703, 38}} + {{26, 10}, {705, 40}} + {0, 0} 67108864 @@ -1158,10 +1246,12 @@ {{1, 1}, {746, 112}} + {{-11, -4}, {748, 114}} + {0, 0} 67108864 @@ -1183,6 +1273,7 @@ {{0, 207}, {730, 108}} + NSView @@ -1190,6 +1281,7 @@ 266 {{32, 316}, {680, 28}} + YES -2080374720 @@ -1212,6 +1304,7 @@ 268 {{9, 318}, {29, 26}} + YES 67108864 @@ -1229,34 +1322,12 @@ NO - - - 265 - {{674, 344}, {43, 28}} - - _NS:9 - YES - - 67108864 - 134348800 - S… - - _NS:9 - - -2038284288 - 129 - - - 200 - 25 - - NO - 265 - {{582, 344}, {94, 28}} + {{580, 344}, {94, 28}} + YES 67108864 @@ -1278,6 +1349,7 @@ 10 {{20, 312}, {690, 5}} + {0, 0} 67108864 @@ -1298,8 +1370,9 @@ 266 - {{127, 349}, {452, 19}} + {{127, 349}, {450, 19}} + YES -2073034687 @@ -1319,6 +1392,7 @@ 268 {{17, 351}, {105, 14}} + YES 68157504 @@ -1337,6 +1411,7 @@ -2147483380 {{226, 288}, {273, 18}} + YES -2080374784 @@ -1357,21 +1432,23 @@ - 17 + 273 {{438, 16}, {276, 268}} + sql - - 256 + + 274 268 {{11, 195}, {233, 18}} + YES -2080374784 @@ -1395,6 +1472,7 @@ 268 {{12, 219}, {233, 14}} + YES 68157504 @@ -1413,6 +1491,7 @@ 268 {{12, 95}, {233, 14}} + YES 68157504 @@ -1431,6 +1510,7 @@ 268 {{12, 115}, {232, 18}} + YES 67108864 @@ -1454,6 +1534,7 @@ 268 {{12, 135}, {232, 18}} + YES -2080374784 @@ -1477,6 +1558,7 @@ 268 {{12, 71}, {232, 18}} + YES 67108864 @@ -1500,6 +1582,7 @@ 268 {{12, 51}, {232, 18}} + YES -2080374784 @@ -1523,6 +1606,7 @@ 268 {{12, 31}, {233, 14}} + YES 68157504 @@ -1541,6 +1625,7 @@ 268 {{15, 4}, {60, 19}} + YES -1804599231 @@ -1661,6 +1746,7 @@ 268 {{29, 175}, {215, 18}} + YES 67108864 @@ -1684,6 +1770,7 @@ 268 {{29, 156}, {215, 18}} + YES -2080374784 @@ -1704,7 +1791,7 @@ {{10, 7}, {256, 248}} - + SQL @@ -2555,14 +2642,15 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 xml - - 256 + + 274 268 {{11, 131}, {72, 14}} + YES 68157504 @@ -2600,6 +2688,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 268 {{11, 171}, {179, 18}} + YES -2080374784 @@ -2623,6 +2712,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 268 {{12, 195}, {180, 14}} + YES 68157504 @@ -2641,6 +2731,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 268 {{11, 151}, {179, 18}} + YES -2080374784 @@ -2664,6 +2755,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 268 {{59, 212}, {119, 22}} + YES -2076180416 @@ -2719,6 +2811,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 268 {{11, 217}, {46, 14}} + YES 68157504 @@ -2734,6 +2827,8 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 {{10, 7}, {256, 248}} + + XML @@ -2786,13 +2881,13 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 - + 4 YES YES - + @@ -2801,13 +2896,14 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 - 2304 + 2322 4352 {412, 221} + YES NO YES @@ -2816,6 +2912,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 256 {412, 17} + @@ -2823,6 +2920,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 -2147483392 {{144, 0}, {16, 17}} + @@ -2994,6 +3092,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 -2147483392 {{148, 1}, {11, 228}} + NO 256 @@ -3005,6 +3104,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 -2147483392 {{-100, -100}, {191, 15}} + NO 1 @@ -3013,7 +3113,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 - 2304 + 2338 @@ -3028,12 +3128,13 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 {{21, 42}, {414, 239}} - + 133650 + QSAAAEEgAABBmAAAQZgAAA 0.25 4 @@ -3041,6 +3142,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 {730, 378} + NSView @@ -3622,9 +3724,17 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 exportCurrentSettings: - + - SsW-ax-uuL + qlz-iS-nue + + + + importCurrentSettings: + + + + abL-95-pHf @@ -4017,9 +4127,9 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 - - + + Exporter View @@ -5226,17 +5336,45 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 - 77c-r9-26W - + fTa-Pi-oat + - + - DcJ-W4-asV - - + CFF-pW-Ooq + + + + + + + + FZy-tc-1Lv + + + + + + + + + + 7YA-Zh-COE + + + + + n0o-5Z-jWq + + + + + ENy-fo-Iwf + + @@ -5247,6 +5385,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 com.apple.InterfaceBuilder.CocoaPlugin {{538, 113}, {517, 498}} + com.apple.InterfaceBuilder.CocoaPlugin 2 @@ -5585,16 +5724,28 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + ToolTip + + ToolTip + + Replace the current export settings with settings loaded from disk + + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + ToolTip ToolTip - + Save the current export settings to disk - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin diff --git a/Source/SPBundleEditorController.m b/Source/SPBundleEditorController.m index 7757c0ba..9986fa98 100644 --- a/Source/SPBundleEditorController.m +++ b/Source/SPBundleEditorController.m @@ -721,7 +721,7 @@ [panel setNameFieldStringValue:[[self _currentSelectedObject] objectForKey:kBundleNameKey]]; [panel beginSheetModalForWindow:[self window] completionHandler:^(NSInteger returnCode) { - if (returnCode != NSOKButton) return; + if (returnCode != NSFileHandlingPanelOKButton) return; // Panel is still on screen. Hide it first. (This is Apple's recommended way) [panel orderOut:nil]; diff --git a/Source/SPConstants.h b/Source/SPConstants.h index 4246906f..b236cda9 100644 --- a/Source/SPConstants.h +++ b/Source/SPConstants.h @@ -641,6 +641,16 @@ extern NSString *SPURLSchemeQueryResultStatusPathHeader; extern NSString *SPURLSchemeQueryResultMetaPathHeader; extern NSString *SPCommonCryptoExceptionName; +extern NSString *SPErrorDomain; // generic SP error domain for NSError + +typedef NS_ENUM(NSInteger,SPErrorCode) { // error codes in SPErrorDomain + /** When plist deserialization fails with nil return and no NSError or the returned object has the wrong type */ + SPErrorWrongTypeOrNil = 110001, + /** Parsed data is syntactically correct, but semantically wrong (e.g. a SPF file with the wrong content format */ + SPErrorWrongContentType = 110002, + /** Some data has a version that we don't know how to handle (can be used with e.g. SPF files, which have explicit version numbers) */ + SPErrorWrongContentVersion = 110003, +}; #define SPAppDelegate ((SPAppController *)[NSApp delegate]) diff --git a/Source/SPConstants.m b/Source/SPConstants.m index e5a407f4..4098f062 100644 --- a/Source/SPConstants.m +++ b/Source/SPConstants.m @@ -443,6 +443,7 @@ NSString *SPURLSchemeQueryResultStatusPathHeader = @"/tmp/SP_QUERY_RESULT_STAT NSString *SPURLSchemeQueryResultMetaPathHeader = @"/tmp/SP_QUERY_META_"; NSString *SPCommonCryptoExceptionName = @"SPCommonCryptoException"; +NSString *SPErrorDomain = @"SPErrorDomain"; void inline _SPClear(id *addr) { [*addr release], *addr = nil; diff --git a/Source/SPExportController.h b/Source/SPExportController.h index d36c8f81..f94596ca 100644 --- a/Source/SPExportController.h +++ b/Source/SPExportController.h @@ -254,6 +254,14 @@ - (void)openExportErrorsSheetWithString:(NSString *)errors; - (void)displayExportFinishedGrowlNotification; +/** + * Tries to set the export input to a given value or falls back to a default if not valid + * @param input The source to use + * @return YES if the source was accepted, NO otherwise + * @pre _switchTab needs to have been run before this method to decide valid inputs + */ +- (BOOL)setExportInput:(SPExportSource)input; + // IB action methods - (IBAction)export:(id)sender; - (IBAction)closeSheet:(id)sender; diff --git a/Source/SPExportController.m b/Source/SPExportController.m index 95fbc5b6..9113decc 100644 --- a/Source/SPExportController.m +++ b/Source/SPExportController.m @@ -198,9 +198,6 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; [exporters removeAllObjects]; [exportFiles removeAllObjects]; - // Select the 'selected tables' source option - [exportInputPopUpButton selectItemAtIndex:source]; - // If tables were supplied, select them if (exportTables) { @@ -231,6 +228,7 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; // Ensure interface validation [self _switchTab]; [self _updateExportAdvancedOptionsLabel]; + [self setExportInput:source]; [NSApp beginSheet:[self window] modalForWindow:[tableDocumentInstance parentWindow] @@ -306,9 +304,6 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; } [self exportTables:selectedTables asFormat:selectedExportType usingSource:selectedExportSource]; - - // Ensure UI validation - [self switchInput:exportInputPopUpButton]; } /** @@ -337,27 +332,50 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; [[sender window] orderOut:self]; } +- (BOOL)setExportInput:(SPExportSource)input +{ + SPExportSource actualInput = input; + // Dot will always be a TableExport + if(exportType == SPDotExport) { + actualInput = SPTableExport; + } + //check if the type actually is valid + else if(![[exportInputPopUpButton itemAtIndex:input] isEnabled]) { + //...no, pick a valid one instead + for (NSMenuItem *item in [exportInputPopUpButton itemArray]) { + if([item isEnabled]) { + actualInput = [exportInputPopUpButton indexOfItem:item]; + goto set_input; + } + } + // nothing found (should not happen) + SPLog(@"did not find any valid export input!?"); + return NO; + } +set_input: + exportSource = actualInput; + + [exportInputPopUpButton selectItemAtIndex:exportSource]; + + BOOL isSelectedTables = (exportSource == SPTableExport); + + [exportFilePerTableCheck setHidden:(!isSelectedTables) || (exportType == SPSQLExport)]; + [exportTableList setEnabled:isSelectedTables]; + [exportSelectAllTablesButton setEnabled:isSelectedTables]; + [exportDeselectAllTablesButton setEnabled:isSelectedTables]; + [exportRefreshTablesButton setEnabled:isSelectedTables]; + + [self updateAvailableExportFilenameTokens]; // will also update the filename itself + + return (actualInput == input); +} + /** * Enables/disables and shows/hides various interface controls depending on the selected item. */ - (IBAction)switchInput:(id)sender { - if ([sender isKindOfClass:[NSPopUpButton class]]) { - - // Determine what data to use (filtered result, custom query result or selected table(s)) for the export operation - exportSource = (exportType == SPDotExport) ? SPTableExport : [exportInputPopUpButton indexOfSelectedItem]; - - BOOL isSelectedTables = ([sender indexOfSelectedItem] == SPTableExport); - - [exportFilePerTableCheck setHidden:(!isSelectedTables) || (exportType == SPSQLExport)]; - [exportTableList setEnabled:isSelectedTables]; - [exportSelectAllTablesButton setEnabled:isSelectedTables]; - [exportDeselectAllTablesButton setEnabled:isSelectedTables]; - [exportRefreshTablesButton setEnabled:isSelectedTables]; - - [self updateAvailableExportFilenameTokens]; - [self updateDisplayedExportFilename]; - } + [self setExportInput:(SPExportSource)[exportInputPopUpButton indexOfSelectedItem]]; } /** @@ -428,7 +446,7 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; [panel setDirectoryURL:[NSURL URLWithString:[exportPathField stringValue]]]; [panel beginSheetModalForWindow:[self window] completionHandler:^(NSInteger returnCode) { - if (returnCode == NSOKButton) { + if (returnCode == NSFileHandlingPanelOKButton) { NSString *path = [[panel directoryURL] path]; if(!path) { @throw [NSException exceptionWithName:NSInternalInconsistencyException @@ -673,9 +691,6 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; - (IBAction)exportCustomQueryResultAsFormat:(id)sender { [self exportTables:nil asFormat:[sender tag] usingSource:SPQueryExport]; - - // Ensure UI validation - [self switchInput:exportInputPopUpButton]; } #pragma mark - @@ -770,11 +785,11 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; BOOL isSQL = (exportType == SPSQLExport); BOOL isCSV = (exportType == SPCSVExport); BOOL isXML = (exportType == SPXMLExport); - BOOL isHTML = (exportType == SPHTMLExport); - BOOL isPDF = (exportType == SPPDFExport); + //BOOL isHTML = (exportType == SPHTMLExport); + //BOOL isPDF = (exportType == SPPDFExport); BOOL isDot = (exportType == SPDotExport); - BOOL enable = (isCSV || isXML || isHTML || isPDF || isDot); + BOOL enable = (isCSV || isXML /* || isHTML || isPDF */ || isDot); [exportFilePerTableCheck setHidden:(isSQL || isDot)]; [exportTableList setEnabled:(!isDot)]; diff --git a/Source/SPExportControllerDelegate.m b/Source/SPExportControllerDelegate.m index 748fec1d..cf3101cf 100644 --- a/Source/SPExportControllerDelegate.m +++ b/Source/SPExportControllerDelegate.m @@ -32,8 +32,8 @@ #import "SPExportFilenameUtilities.h" #import "SPExportFileNameTokenObject.h" -#define IS_TOKEN(x) [x isKindOfClass:[SPExportFileNameTokenObject class]] -#define IS_STRING(x) [x isKindOfClass:[NSString class]] +static inline BOOL IS_TOKEN(id x); +static inline BOOL IS_STRING(id x); // Defined to suppress warnings @interface SPExportController (SPExportControllerPrivateAPI) @@ -345,5 +345,15 @@ @end -#undef IS_TOKEN -#undef IS_STRING +#pragma mark - + +BOOL IS_TOKEN(id x) +{ + return [x isKindOfClass:[SPExportFileNameTokenObject class]]; +} + +BOOL IS_STRING(id x) +{ + return [x isKindOfClass:[NSString class]]; +} + diff --git a/Source/SPExportFilenameUtilities.h b/Source/SPExportFilenameUtilities.h index 978dfc2c..b0ba9763 100644 --- a/Source/SPExportFilenameUtilities.h +++ b/Source/SPExportFilenameUtilities.h @@ -41,6 +41,7 @@ - (void)updateDisplayedExportFilename; - (void)updateAvailableExportFilenameTokens; +- (NSArray *)currentAllowedExportFilenameTokens; - (NSString *)generateDefaultExportFilename; - (NSString *)currentDefaultExportFileExtension; - (NSString *)expandCustomFilenameFormatUsingTableName:(NSString *)table; diff --git a/Source/SPExportFilenameUtilities.m b/Source/SPExportFilenameUtilities.m index ac09dd3b..69b8f786 100644 --- a/Source/SPExportFilenameUtilities.m +++ b/Source/SPExportFilenameUtilities.m @@ -153,6 +153,18 @@ [self updateDisplayedExportFilename]; } +- (NSArray *)currentAllowedExportFilenameTokens +{ + NSArray *mixed = [exportCustomFilenameTokenPool objectValue]; + NSMutableArray *tokens = [NSMutableArray arrayWithCapacity:[mixed count]]; // ...or less + + for (id obj in mixed) { + if([obj isKindOfClass:[SPExportFileNameTokenObject class]]) [tokens addObject:obj]; + } + + return tokens; +} + /** * Generates the default export filename based on the selected export options. * diff --git a/Source/SPExportSettingsPersistence.h b/Source/SPExportSettingsPersistence.h index 90f29cfe..41609125 100644 --- a/Source/SPExportSettingsPersistence.h +++ b/Source/SPExportSettingsPersistence.h @@ -34,11 +34,31 @@ @interface SPExportController (SPExportSettingsPersistence) - (IBAction)exportCurrentSettings:(id)sender; +- (IBAction)importCurrentSettings:(id)sender; + +/** + * @return The current settings as a dictionary which can be serialized + */ - (NSDictionary *)currentSettingsAsDictionary; +/** Overwrite current export settings with those defined in dict + * @param dict The new settings to apply (passing nil is an error.) + * @param err Errors while applying (will mostly be about invalid format, type) + * Can pass NULL, if not interested in details. + * Will NOT be changed unless the method also returns NO + * @return success + */ +- (BOOL)applySettingsFromDictionary:(NSDictionary *)dict error:(NSError **)err; + /** * @return A serialized form of the "custom filename" field */ - (NSArray *)currentCustomFilenameAsArray; +/** + * @param tokenList A serialized form of the "custom filename" field + * @see currentCustomFilenameAsArray + */ +- (void)setCustomFilenameFromArray:(NSArray *)tokenList; + @end diff --git a/Source/SPExportSettingsPersistence.m b/Source/SPExportSettingsPersistence.m index 68963856..f1fe98a2 100644 --- a/Source/SPExportSettingsPersistence.m +++ b/Source/SPExportSettingsPersistence.m @@ -30,33 +30,69 @@ #import "SPExportSettingsPersistence.h" #import "SPExportFileNameTokenObject.h" +#import "SPExportFilenameUtilities.h" +#import "SPExportController+SharedPrivateAPI.h" /** * converts a ([obj state] == NSOnState) to @YES / @NO * (because doing @([obj state] == NSOnState) will result in an integer 0/1) */ static inline NSNumber *IsOn(id obj); +/** + * Sets the state of obj to NSOnState or NSOffState based on the value of ref + */ +static inline void SetOnOff(NSNumber *ref,id obj); + +static const NSString *SPFExportSettingsContentType = @"export settings"; + +@interface SPExportController (Private) + +- (void)_updateExportAdvancedOptionsLabel; +- (void)_switchTab; + +@end @interface SPExportController (SPExportSettingsPersistence_Private) +// those methods will convert the name of a C enum constant to a NSString + (NSString *)describeExportSource:(SPExportSource)es; + (NSString *)describeExportType:(SPExportType)et; + (NSString *)describeCompressionFormat:(SPFileCompressionFormat)cf; + (NSString *)describeXMLExportFormat:(SPXMLExportFormat)xf; + (NSString *)describeSQLExportInsertDivider:(SPSQLExportInsertDivider)eid; +// these will store the C enum constant named by NSString in dst and return YES, +// if a valid mapping exists. Otherwise will just return NO and not modify dst. ++ (BOOL)copyExportSourceForDescription:(NSString *)esd to:(SPExportSource *)dst; ++ (BOOL)copyCompressionFormatForDescription:(NSString *)esd to:(SPFileCompressionFormat *)dst; ++ (BOOL)copyExportTypeForDescription:(NSString *)esd to:(SPExportType *)dst; ++ (BOOL)copyXMLExportFormatForDescription:(NSString *)xfd to:(SPXMLExportFormat *)dst; ++ (BOOL)copySQLExportInsertDividerForDescription:(NSString *)xfd to:(SPSQLExportInsertDivider *)dst; + - (NSDictionary *)exporterSettings; - (NSDictionary *)csvSettings; - (NSDictionary *)dotSettings; - (NSDictionary *)xmlSettings; - (NSDictionary *)sqlSettings; +- (void)applyExporterSettings:(NSDictionary *)settings; +- (void)applyCsvSettings:(NSDictionary *)settings; +- (void)applyDotSettings:(NSDictionary *)settings; +- (void)applyXmlSettings:(NSDictionary *)settings; +- (void)applySqlSettings:(NSDictionary *)settings; + - (id)exporterSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type; - (id)dotSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type; - (id)xmlSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type; - (id)csvSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type; - (id)sqlSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type; +- (void)applyExporterSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type; +- (void)applyDotSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type; +- (void)applyXmlSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type; +- (void)applyCsvSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type; +- (void)applySqlSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type; + @end #pragma mark - @@ -64,6 +100,7 @@ static inline NSNumber *IsOn(id obj); @implementation SPExportController (SPExportSettingsPersistence) #define NAMEOF(x) case x: return @#x +#define VALUEOF(x,y,dst) if([y isEqualToString:@#x]) { *dst = x; return YES; } + (NSString *)describeExportSource:(SPExportSource)es { @@ -75,6 +112,14 @@ static inline NSNumber *IsOn(id obj); return nil; } ++ (BOOL)copyExportSourceForDescription:(NSString *)esd to:(SPExportSource *)dst +{ + VALUEOF(SPFilteredExport, esd,dst); + VALUEOF(SPQueryExport, esd,dst); + VALUEOF(SPTableExport, esd,dst); + return NO; +} + + (NSString *)describeExportType:(SPExportType)et { switch (et) { @@ -89,6 +134,18 @@ static inline NSNumber *IsOn(id obj); return nil; } ++ (BOOL)copyExportTypeForDescription:(NSString *)etd to:(SPExportType *)dst +{ + VALUEOF(SPSQLExport, etd, dst); + VALUEOF(SPCSVExport, etd, dst); + VALUEOF(SPXMLExport, etd, dst); + VALUEOF(SPDotExport, etd, dst); + //VALUEOF(SPPDFExport, etd, dst); + //VALUEOF(SPHTMLExport, etd, dst); + //VALUEOF(SPExcelExport, etd, dst); + return NO; +} + + (NSString *)describeCompressionFormat:(SPFileCompressionFormat)cf { switch (cf) { @@ -99,6 +156,14 @@ static inline NSNumber *IsOn(id obj); return nil; } ++ (BOOL)copyCompressionFormatForDescription:(NSString *)cfd to:(SPFileCompressionFormat *)dst +{ + VALUEOF(SPNoCompression, cfd, dst); + VALUEOF(SPGzipCompression, cfd, dst); + VALUEOF(SPBzip2Compression, cfd, dst); + return NO; +} + + (NSString *)describeXMLExportFormat:(SPXMLExportFormat)xf { switch (xf) { @@ -108,6 +173,13 @@ static inline NSNumber *IsOn(id obj); return nil; } ++ (BOOL)copyXMLExportFormatForDescription:(NSString *)xfd to:(SPXMLExportFormat *)dst +{ + VALUEOF(SPXMLExportMySQLFormat, xfd, dst); + VALUEOF(SPXMLExportPlainFormat, xfd, dst); + return NO; +} + + (NSString *)describeSQLExportInsertDivider:(SPSQLExportInsertDivider)eid { switch (eid) { @@ -117,7 +189,70 @@ static inline NSNumber *IsOn(id obj); return nil; } ++ (BOOL)copySQLExportInsertDividerForDescription:(NSString *)eidd to:(SPSQLExportInsertDivider *)dst +{ + VALUEOF(SPSQLInsertEveryNDataBytes, eidd, dst); + VALUEOF(SPSQLInsertEveryNRows, eidd, dst); + return NO; +} + #undef NAMEOF +#undef VALUEOF + +- (IBAction)importCurrentSettings:(id)sender +{ + //show open file dialog + NSOpenPanel *panel = [NSOpenPanel openPanel]; + + [panel setAllowedFileTypes:@[SPFileExtensionDefault]]; + [panel setAllowsOtherFileTypes:YES]; + + [panel beginSheetModalForWindow:[self window] completionHandler:^(NSInteger result) { + if(result != NSFileHandlingPanelOKButton) return; + + [panel orderOut:nil]; // Panel is still on screen. Hide it first. (This is Apple's recommended way) + + NSError *err = nil; + NSData *plist = [NSData dataWithContentsOfURL:[panel URL] + options:0 + error:&err]; + + NSDictionary *settings = nil; + if(!err) { + settings = [NSPropertyListSerialization propertyListWithData:plist + options:NSPropertyListImmutable + format:NULL + error:&err]; + } + + if(!err) { + [self applySettingsFromDictionary:settings error:&err]; + if(!err) return; + } + + // give an explanation for some errors + if([[err domain] isEqualToString:SPErrorDomain]) { + if([err code] == SPErrorWrongTypeOrNil) { + NSDictionary *info = @{ + NSLocalizedDescriptionKey: NSLocalizedString(@"Invalid file supplied!", @"export : import settings : file error title"), + NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"The selected file is either not a valid SPF file or severely corrupted.", @"export : import settings : file error description"), + }; + err = [NSError errorWithDomain:[err domain] code:[err code] userInfo:info]; + } + else if([err code] == SPErrorWrongContentType) { + NSDictionary *info = @{ + NSLocalizedDescriptionKey: NSLocalizedString(@"Wrong SPF content type!", @"export : import settings : spf content type error title"), + NSLocalizedRecoverySuggestionErrorKey: [NSString stringWithFormat:NSLocalizedString(@"The selected file contains data of type “%1$@”, but type “%2$@” is needed. Please choose a different file.", @"export : import settings : spf content type error description"),[[err userInfo] objectForKey:@"isType"],[[err userInfo] objectForKey:@"expectedType"]], + }; + err = [NSError errorWithDomain:[err domain] code:[err code] userInfo:info]; + } + } + + NSAlert *alert = [NSAlert alertWithError:err]; + [alert setAlertStyle:NSCriticalAlertStyle]; + [alert runModal]; + }]; +} - (IBAction)exportCurrentSettings:(id)sender { @@ -134,6 +269,9 @@ static inline NSNumber *IsOn(id obj); [panel beginSheetModalForWindow:[self window] completionHandler:^(NSInteger returnCode) { if(returnCode != NSFileHandlingPanelOKButton) return; + // Panel is still on screen. Hide it first. (This is Apple's recommended way) + [panel orderOut:nil]; + NSError *err = nil; NSData *plist = [NSPropertyListSerialization dataWithPropertyList:[self currentSettingsAsDictionary] format:NSPropertyListXMLFormat_v1_0 @@ -172,11 +310,42 @@ static inline NSNumber *IsOn(id obj); return tokenListOut; } +- (void)setCustomFilenameFromArray:(NSArray *)tokenList +{ + NSMutableArray *tokenListOut = [NSMutableArray arrayWithCapacity:[tokenList count]]; + NSArray *allowedTokens = [self currentAllowedExportFilenameTokens]; + + for (id obj in tokenList) { + if([obj isKindOfClass:[NSString class]]) { + [tokenListOut addObject:obj]; + } + else if([obj isKindOfClass:[NSDictionary class]]) { + //there must be at least a non-empty tokenId that is also in the token pool + NSString *tokenId = [obj objectForKey:@"tokenId"]; + if([tokenId length]) { + SPExportFileNameTokenObject *token = [SPExportFileNameTokenObject tokenWithId:tokenId]; + if([allowedTokens containsObject:token]) { + [tokenListOut addObject:token]; + continue; + } + } + SPLog(@"Ignoring an invalid or unknown token with tokenId=%@",tokenId); + } + else { + SPLog(@"unknown object in import token list: %@",obj); + } + } + + [exportCustomFilenameTokenField setObjectValue:tokenListOut]; + + [self updateDisplayedExportFilename]; +} + - (NSDictionary *)currentSettingsAsDictionary { NSMutableDictionary *root = [NSMutableDictionary dictionary]; - [root setObject:@"export settings" forKey:SPFFormatKey]; + [root setObject:SPFExportSettingsContentType forKey:SPFFormatKey]; [root setObject:@1 forKey:SPFVersionKey]; [root setObject:[exportPathField stringValue] forKey:@"exportPath"]; @@ -209,6 +378,98 @@ static inline NSNumber *IsOn(id obj); return root; } +- (BOOL)applySettingsFromDictionary:(NSDictionary *)dict error:(NSError **)err +{ + //check for dict/nil + if(![dict isKindOfClass:[NSDictionary class]]) { + if(err) { + *err = [NSError errorWithDomain:SPErrorDomain + code:SPErrorWrongTypeOrNil + userInfo:nil]; // we don't know where data came from, so we can't provide meaningful help to the user + } + return NO; + } + + //check for export settings + NSString *ctype = [dict objectForKey:SPFFormatKey]; + if (![SPFExportSettingsContentType isEqualToString:ctype]) { + if(err) { + NSDictionary *errInfo = @{ + @"isType": ctype, + @"expectedType": SPFExportSettingsContentType + }; + *err = [NSError errorWithDomain:SPErrorDomain + code:SPErrorWrongContentType + userInfo:errInfo]; + } + return NO; + } + + //check for version + NSInteger version = [[dict objectForKey:SPFVersionKey] integerValue]; + if(version != 1) { + if(err) { + NSDictionary *errInfo = @{ + @"isVersion": @(version), + NSLocalizedDescriptionKey: NSLocalizedString(@"Unsupported version for export settings!", @"export : import settings : file version error title"), + NSLocalizedRecoverySuggestionErrorKey: [NSString stringWithFormat:NSLocalizedString(@"The selected export settings were stored with version\u00A0%1$ld, but only settings with the following versions can be imported: %2$@.\n\nEither save the settings in a backwards compatible way or update your version of Sequel Pro.", @"export : import settings : file version error description ($1 = is version, $2 = list of supported versions)"),version,@"1"], + }; + *err = [NSError errorWithDomain:SPErrorDomain + code:SPErrorWrongContentVersion + userInfo:errInfo]; + } + return NO; + } + + //ok, we can try to import that... + + [exporters removeAllObjects]; + [exportFiles removeAllObjects]; + + id o; + if((o = [dict objectForKey:@"exportPath"])) [exportPathField setStringValue:o]; + + SPExportType et; + if((o = [dict objectForKey:@"exportType"]) && [[self class] copyExportTypeForDescription:o to:&et]) { + [exportTypeTabBar selectTabViewItemAtIndex:et]; + } + + //exportType should be changed first, as exportSource depends on it + SPExportSource es; + if((o = [dict objectForKey:@"exportSource"]) && [[self class] copyExportSourceForDescription:o to:&es]) { + [self setExportInput:es]; //try to set it. might fail e.g. if the settings were saved with "query result" but right now no custom query result exists + } + + // set exporter specific settings + [self applyExporterSettings:[dict objectForKey:@"settings"]]; + + // load schema object settings + if(exportSource == SPTableExport) { + NSDictionary *perObjectSettings = [dict objectForKey:@"schemaObjects"]; + + for (NSString *table in [perObjectSettings allKeys]) { + id settings = [perObjectSettings objectForKey:table]; + [self applyExporterSpecificSettings:settings forSchemaObject:table ofType:SPTableTypeTable]; + } + + [exportTableList reloadData]; + } + + if((o = [dict objectForKey:@"lowMemoryStreaming"])) [exportProcessLowMemoryButton setState:([o boolValue] ? NSOnState : NSOffState)]; + + SPFileCompressionFormat cf; + if((o = [dict objectForKey:@"compressionFormat"]) && [[self class] copyCompressionFormatForDescription:o to:&cf]) [exportOutputCompressionFormatPopupButton selectItemAtIndex:cf]; + + // might have changed + [self _updateExportAdvancedOptionsLabel]; + + // token pool is only valid once the schema object selection is done + [self updateAvailableExportFilenameTokens]; + if((o = [dict objectForKey:@"customFilename"])) [self setCustomFilenameFromArray:o]; + + return YES; +} + - (NSDictionary *)exporterSettings { switch (exportType) { @@ -230,6 +491,27 @@ static inline NSNumber *IsOn(id obj); } } +- (void)applyExporterSettings:(NSDictionary *)settings +{ + switch (exportType) { + case SPCSVExport: + return [self applyCsvSettings:settings]; + case SPSQLExport: + return [self applySqlSettings:settings]; + case SPXMLExport: + return [self applyXmlSettings:settings]; + case SPDotExport: + return [self applyDotSettings:settings]; + case SPExcelExport: + case SPHTMLExport: + case SPPDFExport: + default: + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:@"exportType not implemented!" + userInfo:@{@"exportType": @(exportType)}]; + } +} + - (NSDictionary *)csvSettings { return @{ @@ -243,11 +525,31 @@ static inline NSNumber *IsOn(id obj); }; } +- (void)applyCsvSettings:(NSDictionary *)settings +{ + id o; + if((o = [settings objectForKey:@"exportToMultipleFiles"])) SetOnOff(o,exportFilePerTableCheck); + [self toggleNewFilePerTable:nil]; + + if((o = [settings objectForKey:@"CSVIncludeFieldNames"])) SetOnOff(o, exportCSVIncludeFieldNamesCheck); + if((o = [settings objectForKey:@"CSVFieldsTerminated"])) [exportCSVFieldsTerminatedField setStringValue:o]; + if((o = [settings objectForKey:@"CSVFieldsWrapped"])) [exportCSVFieldsWrappedField setStringValue:o]; + if((o = [settings objectForKey:@"CSVLinesTerminated"])) [exportCSVLinesTerminatedField setStringValue:o]; + if((o = [settings objectForKey:@"CSVFieldsEscaped"])) [exportCSVFieldsEscapedField setStringValue:o]; + if((o = [settings objectForKey:@"CSVNULLValuesAsText"])) [exportCSVNULLValuesAsTextField setStringValue:o]; +} + - (NSDictionary *)dotSettings { return @{@"DotForceLowerTableNames": IsOn(exportDotForceLowerTableNamesCheck)}; } +- (void)applyDotSettings:(NSDictionary *)settings +{ + id o; + if((o = [settings objectForKey:@"DotForceLowerTableNames"])) SetOnOff(o, exportDotForceLowerTableNamesCheck); +} + - (NSDictionary *)xmlSettings { return @{ @@ -259,6 +561,21 @@ static inline NSNumber *IsOn(id obj); }; } +- (void)applyXmlSettings:(NSDictionary *)settings +{ + id o; + SPXMLExportFormat xmlf; + if((o = [settings objectForKey:@"exportToMultipleFiles"])) SetOnOff(o, exportFilePerTableCheck); + [self toggleNewFilePerTable:nil]; + + if((o = [settings objectForKey:@"XMLFormat"]) && [[self class] copyXMLExportFormatForDescription:o to:&xmlf]) [exportXMLFormatPopUpButton selectItemAtIndex:xmlf]; + if((o = [settings objectForKey:@"XMLOutputIncludeStructure"])) SetOnOff(o, exportXMLIncludeStructure); + if((o = [settings objectForKey:@"XMLOutputIncludeContent"])) SetOnOff(o, exportXMLIncludeContent); + if((o = [settings objectForKey:@"XMLNULLString"])) [exportXMLNULLValuesAsTextField setStringValue:o]; + + [self toggleXMLOutputFormat:exportXMLFormatPopUpButton]; +} + - (NSDictionary *)sqlSettings { BOOL includeStructure = ([exportSQLIncludeStructureCheck state] == NSOnState); @@ -267,6 +584,7 @@ static inline NSNumber *IsOn(id obj); @"SQLIncludeStructure": IsOn(exportSQLIncludeStructureCheck), @"SQLIncludeContent": IsOn(exportSQLIncludeContentCheck), @"SQLIncludeErrors": IsOn(exportSQLIncludeErrorsCheck), + @"SQLIncludeDROP": IsOn(exportSQLIncludeDropSyntaxCheck), @"SQLUseUTF8BOM": IsOn(exportUseUTF8BOMButton), @"SQLBLOBFieldsAsHex": IsOn(exportSQLBLOBFieldsAsHexCheck), @"SQLInsertNValue": @([exportSQLInsertNValueTextField integerValue]), @@ -283,6 +601,32 @@ static inline NSNumber *IsOn(id obj); return dict; } +- (void)applySqlSettings:(NSDictionary *)settings +{ + id o; + SPSQLExportInsertDivider div; + + if((o = [settings objectForKey:@"SQLIncludeContent"])) SetOnOff(o, exportSQLIncludeContentCheck); + [self toggleSQLIncludeContent:exportSQLIncludeContentCheck]; + + if((o = [settings objectForKey:@"SQLIncludeDROP"])) SetOnOff(o, exportSQLIncludeDropSyntaxCheck); + [self toggleSQLIncludeDropSyntax:exportSQLIncludeDropSyntaxCheck]; + + if((o = [settings objectForKey:@"SQLIncludeStructure"])) SetOnOff(o, exportSQLIncludeStructureCheck); + [self toggleSQLIncludeStructure:exportSQLIncludeStructureCheck]; + + if((o = [settings objectForKey:@"SQLIncludeErrors"])) SetOnOff(o, exportSQLIncludeErrorsCheck); + if((o = [settings objectForKey:@"SQLUseUTF8BOM"])) SetOnOff(o, exportUseUTF8BOMButton); + if((o = [settings objectForKey:@"SQLBLOBFieldsAsHex"])) SetOnOff(o, exportSQLBLOBFieldsAsHexCheck); + if((o = [settings objectForKey:@"SQLInsertNValue"])) [exportSQLInsertNValueTextField setIntegerValue:[o integerValue]]; + if((o = [settings objectForKey:@"SQLInsertDivider"]) && [[self class] copySQLExportInsertDividerForDescription:o to:&div]) [exportSQLInsertDividerPopUpButton selectItemAtIndex:div]; + + if([exportSQLIncludeStructureCheck state] == NSOnState) { + if((o = [settings objectForKey:@"SQLIncludeAutoIncrementValue"])) SetOnOff(o, exportSQLIncludeAutoIncrementValueButton); + if((o = [settings objectForKey:@"SQLIncludeDropSyntax"])) SetOnOff(o, exportSQLIncludeDropSyntaxCheck); + } +} + - (id)exporterSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type { switch (exportType) { @@ -304,12 +648,38 @@ static inline NSNumber *IsOn(id obj); } } +- (void)applyExporterSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type +{ + switch (exportType) { + case SPCSVExport: + return [self applyCsvSpecificSettings:settings forSchemaObject:name ofType:type]; + case SPSQLExport: + return [self applySqlSpecificSettings:settings forSchemaObject:name ofType:type]; + case SPXMLExport: + return [self applyXmlSpecificSettings:settings forSchemaObject:name ofType:type]; + case SPDotExport: + return [self applyDotSpecificSettings:settings forSchemaObject:name ofType:type]; + case SPExcelExport: + case SPHTMLExport: + case SPPDFExport: + default: + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:@"exportType not implemented!" + userInfo:@{@"exportType": @(exportType)}]; + } +} + - (id)dotSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type { // Dot is a graph of the whole database - nothing to pick from return nil; } +- (void)applyDotSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type +{ + //should never be called +} + - (id)xmlSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type { // XML per table setting is only yes/no @@ -324,6 +694,20 @@ static inline NSNumber *IsOn(id obj); return nil; } +- (void)applyXmlSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type +{ + // XML per table setting is only yes/no + if(type == SPTableTypeTable) { + // we have to look through the table views' rows to find the appropriate table... + for (NSMutableArray *table in tables) { + if([[table objectAtIndex:0] isEqualTo:name]) { + [table replaceObjectAtIndex:2 withObject:@([settings boolValue])]; + return; + } + } + } +} + - (id)csvSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type { // CSV per table setting is only yes/no @@ -338,6 +722,20 @@ static inline NSNumber *IsOn(id obj); return nil; } +- (void)applyCsvSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type +{ + // CSV per table setting is only yes/no + if(type == SPTableTypeTable) { + // we have to look through the table views' rows to find the appropriate table... + for (NSMutableArray *table in tables) { + if([[table objectAtIndex:0] isEqualTo:name]) { + [table replaceObjectAtIndex:2 withObject:@([settings boolValue])]; + return; + } + } + } +} + - (id)sqlSpecificSettingsForSchemaObject:(NSString *)name ofType:(SPTableType)type { BOOL structure = ([exportSQLIncludeStructureCheck state] == NSOnState); @@ -370,6 +768,28 @@ static inline NSNumber *IsOn(id obj); return nil; } +- (void)applySqlSpecificSettings:(id)settings forSchemaObject:(NSString *)name ofType:(SPTableType)type +{ + BOOL structure = ([exportSQLIncludeStructureCheck state] == NSOnState); + BOOL content = ([exportSQLIncludeContentCheck state] == NSOnState); + BOOL drop = ([exportSQLIncludeDropSyntaxCheck state] == NSOnState); + + // SQL allows per table setting of structure/content/drop table + if(type == SPTableTypeTable) { + // we have to look through the table views' rows to find the appropriate table... + for (NSMutableArray *table in tables) { + if([[table objectAtIndex:0] isEqualTo:name]) { + NSArray *flags = settings; + + [table replaceObjectAtIndex:1 withObject:@((structure && [flags containsObject:@"structure"]))]; + [table replaceObjectAtIndex:2 withObject:@((content && [flags containsObject:@"content"]))]; + [table replaceObjectAtIndex:3 withObject:@((drop && [flags containsObject:@"drop"]))]; + return; + } + } + } +} + @end #pragma mark - @@ -378,3 +798,8 @@ NSNumber *IsOn(id obj) { return (([obj state] == NSOnState)? @YES : @NO); } + +void SetOnOff(NSNumber *ref,id obj) +{ + [obj setState:([ref boolValue] ? NSOnState : NSOffState)]; +} -- cgit v1.2.3 From 2059dfa31eb3d9b629a9734c316984bd8afb3915 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 10 Nov 2015 16:13:43 +0100 Subject: Add a "tooltip" when picking a column type in structure view, explaining basic properties of the type (part of #1090) This is mostly to help users understand what "Length" actually does for *INT types. --- Interfaces/English.lproj/DBView.xib | 399 +++++++++++++++++++++++++++++------ Source/SPCategoryAdditions.h | 1 + Source/SPComboBoxCell.h | 73 +++++++ Source/SPComboBoxCell.m | 137 ++++++++++++ Source/SPScreenAdditions.h | 44 ++++ Source/SPScreenAdditions.m | 54 +++++ Source/SPTableStructure.h | 19 +- Source/SPTableStructure.m | 328 ++++++++++++++++++++++++++++ Source/SPTableStructureDelegate.m | 102 +++++++++ Source/SPTooltip.m | 11 +- sequel-pro.xcodeproj/project.pbxproj | 12 ++ 11 files changed, 1107 insertions(+), 73 deletions(-) create mode 100644 Source/SPComboBoxCell.h create mode 100644 Source/SPComboBoxCell.m create mode 100644 Source/SPScreenAdditions.h create mode 100644 Source/SPScreenAdditions.m diff --git a/Interfaces/English.lproj/DBView.xib b/Interfaces/English.lproj/DBView.xib index e42e67e3..465b72fa 100644 --- a/Interfaces/English.lproj/DBView.xib +++ b/Interfaces/English.lproj/DBView.xib @@ -68,7 +68,7 @@ NSApplication - + 274 @@ -218,7 +218,6 @@ {{1, 1}, {218, 38}} - 2 @@ -228,7 +227,6 @@ -2147483392 {{-100, -100}, {15, 8}} - NO @@ -239,7 +237,6 @@ -2147483392 {{-100, -100}, {223, 15}} - NO 1 @@ -249,7 +246,6 @@ {{-1, -13}, {220, 40}} - 133122 @@ -414,7 +410,6 @@ {214, 334} - @@ -423,7 +418,6 @@ -2147483392 {{197, 0}, {15, 292}} - NO @@ -435,7 +429,6 @@ -2147483392 {{-100, -100}, {141, 11}} - NO 257 @@ -445,7 +438,6 @@ {214, 334} - 133648 @@ -459,21 +451,18 @@ {{0, 25}, {214, 334}} - YES {214, 359} - 2 {214, 359} - YES @@ -560,7 +549,6 @@ {216, 166} - 2 @@ -570,7 +558,6 @@ -2147483392 {{-100, -100}, {15, 20}} - NO @@ -581,7 +568,6 @@ -2147483392 {{-100, -100}, {141, 11}} - NO 257 @@ -591,7 +577,6 @@ {{-1, 0}, {216, 166}} - 133648 @@ -681,7 +666,6 @@ {214, 166} - 2 @@ -691,7 +675,6 @@ -2147483392 {{-100, -100}, {15, 20}} - NO @@ -703,7 +686,6 @@ -2147483392 {{-100, -100}, {141, 11}} - NO 257 @@ -713,7 +695,6 @@ {214, 166} - 133632 @@ -727,14 +708,12 @@ {{0, 360}, {214, 166}} - YES {{0, 23}, {214, 526}} - 2 @@ -1127,7 +1106,6 @@ {214, 549} - YES @@ -1140,13 +1118,12 @@ 274 {{-7, -10}, {741, 564}} - - + source - + 274 @@ -1196,7 +1173,7 @@ 4352 {694, 288} - + YES NO YES @@ -1820,7 +1797,7 @@ {{-1, 22}, {696, 306}} - + 133682 @@ -2186,7 +2163,7 @@ 4352 {694, 141} - + YES NO YES @@ -2506,7 +2483,7 @@ {{-1, 22}, {696, 159}} - + 133682 @@ -2600,7 +2577,6 @@ {{93, 0}, {602, 23}} - YES 0 @@ -2629,6 +2605,7 @@ {{10, 7}, {706, 544}} + Structure @@ -3732,7 +3709,7 @@ 6418 {694, 141} - + @@ -3824,7 +3801,7 @@ {{0, 1}, {696, 143}} - + 133650 @@ -3950,7 +3927,7 @@ 4352 {694, 197} - + YES NO YES @@ -4061,13 +4038,12 @@ {{0, -1}, {696, 215}} - + 133682 - QSAAAEEgAABBkAAAQZAAAA 0.25 4 @@ -4683,7 +4659,7 @@ 2322 {672, 72} - + @@ -4759,7 +4735,7 @@ {{12, 12}, {672, 72}} - + 133648 @@ -4810,7 +4786,7 @@ status - + 274 @@ -4899,7 +4875,6 @@ {{1, 1}, {541, 70}} - @@ -4911,7 +4886,6 @@ -2147483392 {{-100, -100}, {87, 18}} - NO 1 @@ -4924,7 +4898,6 @@ 256 {{542, 1}, {11, 70}} - NO 256 @@ -4934,7 +4907,6 @@ {{109, 0}, {554, 72}} - 133138 @@ -4949,7 +4921,6 @@ 268 {{4, 58}, {100, 14}} - YES @@ -5035,7 +5006,6 @@ {{1, 1}, {541, 197}} - @@ -5047,7 +5017,6 @@ -2147483392 {{-100, -100}, {87, 18}} - NO 1 @@ -5060,8 +5029,6 @@ 256 {{542, 1}, {11, 197}} - - NO 256 @@ -5070,7 +5037,6 @@ {{109, 0}, {554, 199}} - 133138 @@ -5085,7 +5051,6 @@ 268 {{3, 185}, {101, 14}} - YES @@ -5103,7 +5068,6 @@ {{0, 81}, {663, 199}} - YES NSView @@ -5111,7 +5075,6 @@ {{12, 30}, {663, 280}} - TableInfoSplitter @@ -5120,7 +5083,6 @@ 268 {{22, 501}, {91, 14}} - YES @@ -5878,8 +5840,6 @@ {{10, 7}, {706, 544}} - - Status @@ -6734,26 +6694,24 @@ - + 134217731 YES YES - + {{215, 0}, {728, 549}} - YES {943, 549} - YES 2 @@ -6761,8 +6719,6 @@ {943, 549} - - NSView @@ -12597,7 +12553,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA - + 3 MCAwAA @@ -12672,6 +12628,161 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA _NS:9 NSView + + 145 + 2 + {{196, 132}, {410, 165}} + -461896704 + Type Info + NSPanel + + + + + 256 + + + + 274 + + + + 2322 + + + + 2322 + {384, 152} + + + _NS:13 + + + + + Help dummy text + + + + Helvetica + 12 + 16 + + + + + + + + + + + 166 + + + + 384 + 1 + + + 10273 + 0 + + + + + + + + + + + + + + + 1 + + 6 + {463, 10000000} + + + + {384, 152} + + + _NS:11 + + + + {4, 5} + + 12582912 + + + + + + TU0AKgAAAHCAFUqgBVKsAAAAwdVQUqwaEQeIRGJRGFlYqwWLQ+JxuOQpVRmEx2RROKwOQyOUQSPyaUym +SxqWyKXyeYxyZzWbSuJTScRCbz2Nz+gRKhUOfTqeUai0OSxiWTiBQSHSGFquGwekxyAgAAAOAQAAAwAA +AAEAEAAAAQEAAwAAAAEAEAAAAQIAAwAAAAIACAAIAQMAAwAAAAEABQAAAQYAAwAAAAEAAQAAAREABAAA +AAEAAAAIARIAAwAAAAEAAQAAARUAAwAAAAEAAgAAARYAAwAAAAEAEAAAARcABAAAAAEAAABnARwAAwAA +AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA + + + + + + + + 2 + + + + -2147483392 + {{-100, -100}, {15, 133}} + + _NS:85 + NO + + _doScroller: + 1 + 0.85256409645080566 + + + + -2147483392 + {{-100, -100}, {87, 18}} + + + _NS:33 + NO + 1 + + _doScroller: + 1 + 0.94565218687057495 + + + {{13, 6}, {384, 152}} + + + _NS:9 + 153600 + + + + 0.25 + 4 + 1 + + + {410, 165} + _NS:21 + + {{0, 0}, {1920, 1178}} + YES + NO @@ -14868,6 +14979,22 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA 7087 + + + structureHelpPanel + + + + bLb-Jh-Jm1 + + + + structureHelpText + + + + 0cG-kD-PJR + gearMenuItemSelected: @@ -16012,6 +16139,14 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA 7498 + + + spDelegate + + + + bLE-2M-Nym + menu @@ -24288,6 +24423,48 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA + + NkY-EN-Fxn + + + + + + Structure Type Help + + + h2r-1x-8ZD + + + + + + + + VTe-1x-Phd + + + + + + + + + + Bfr-0R-dqh + + + + + mBC-nL-nLZ + + + + + INC-ms-oeO + + + @@ -24313,6 +24490,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA StatusTabView com.apple.InterfaceBuilder.CocoaPlugin + SPComboBoxCell com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -27110,6 +27288,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -27117,7 +27296,11 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + {98, 659.5} + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -27125,6 +27308,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA {-318.5, -215.5} com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -27134,11 +27318,13 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -27167,6 +27353,24 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA ../Source/ImageAndTextCell.h + + NSComboBoxCell + + popUp: + id + + + popUp: + + popUp: + id + + + + IBProjectSource + ../Source/SPComboBoxCell.m + + NSTextView @@ -27349,6 +27553,25 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA ../Source/SPTextViewAdditions.m + + SPComboBoxCell + NSComboBoxCell + + spDelegate + id + + + spDelegate + + spDelegate + id + + + + IBProjectSource + ../Source/SPComboBoxCell.h + + SPComboPopupButton NSPopUpButton @@ -29593,6 +29816,48 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA ../Source/SPExportController.m + + SPExportController + + id + id + + + + exportCurrentSettings: + id + + + importCurrentSettings: + id + + + + IBProjectSource + ../Source/SPExportSettingsPersistence.h + + + + SPExportController + + id + id + + + + exportCurrentSettings: + id + + + importCurrentSettings: + id + + + + IBProjectSource + ../Source/SPExportSettingsPersistence.m + + SPExtendedTableInfo NSObject @@ -31376,6 +31641,8 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA id id id + NSPanel + NSTextView SPTableData SPDatabaseDocument SPTableInfo @@ -31465,6 +31732,14 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA structureGrabber id + + structureHelpPanel + NSPanel + + + structureHelpText + NSTextView + tableDataInstance SPTableData diff --git a/Source/SPCategoryAdditions.h b/Source/SPCategoryAdditions.h index 58c40d80..991cb60c 100644 --- a/Source/SPCategoryAdditions.h +++ b/Source/SPCategoryAdditions.h @@ -48,6 +48,7 @@ #import "SPColorAdditions.h" #import "SPFileManagerAdditions.h" #import "SPDateAdditions.h" +#import "SPScreenAdditions.h" #import "NSNotificationCenterThreadingAdditions.h" #import "NSMutableArray-MultipleSort.h" diff --git a/Source/SPComboBoxCell.h b/Source/SPComboBoxCell.h new file mode 100644 index 00000000..39c8f505 --- /dev/null +++ b/Source/SPComboBoxCell.h @@ -0,0 +1,73 @@ +// +// SPComboBoxCell.h +// sequel-pro +// +// Created by Max Lohrmann on 08.11.15. +// Copyright (c) 2015 Max Lohrmann. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at + +#import + +@class SPComboBoxCell; + +@protocol SPComboBoxCellDelegate + +@optional +- (void)comboBoxCell:(SPComboBoxCell *)cell willPopUpWindow:(NSWindow *)win; +- (void)comboBoxCell:(SPComboBoxCell *)cell willDismissWindow:(NSWindow *)win; +- (void)comboBoxCellSelectionDidChange:(SPComboBoxCell *)cell; + +@end + +/** + * See this class as a kind of "rapid prototype". + * It heavily relies on the inner workings of the NSComboBoxCell to implement + * the additional features we want (namely the option to show a tooltip + * window beside the popup list). + * + * App Store-wise it would probably we better to implement this from scratch, + * but NSComboBoxCell uses some quite difficult logic inside -filterEvents: + * which is the core method of the selection process. + * + * We could somewhat work around the private interface by relying on notification + * NSComboBoxCellWillPopUpNotification + * and overriding -[NSWindow addChildWindow:] / -[NSWindow removeChildWindow:] + * but NSTableView foils that as it will copy the cell right before the notification + * is sent, so we don't know what object to observe. + */ +@interface SPComboBoxCell : NSComboBoxCell { + id spDelegate; +} + +@property (assign) IBOutlet id spDelegate; // NSComboBoxCell already has a delegate property + +/** + * The popUp window that contains the item list. + * Will return nil if the implementation changes and the underlying ivar + * is removed or no longer a NSWindow. + */ +- (NSWindow *)spPopUpWindow; + +@end diff --git a/Source/SPComboBoxCell.m b/Source/SPComboBoxCell.m new file mode 100644 index 00000000..15ddc1db --- /dev/null +++ b/Source/SPComboBoxCell.m @@ -0,0 +1,137 @@ +// +// SPComboBoxCell.m +// sequel-pro +// +// Created by Max Lohrmann on 08.11.15. +// Copyright (c) 2015 Max Lohrmann. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at + +#import "SPComboBoxCell.h" +#import + +static NSString *_CellSelectionDidChangeNotification = @"NSComboBoxCellSelectionDidChangeNotification"; +static NSString *_CellWillPopUpNotification = @"NSComboBoxCellWillPopUpNotification"; +static NSString *_CellWillDismissNotification = @"NSComboBoxCellWillDismissNotification"; + +@interface NSComboBoxCell (Apple_Private) +- (IBAction)popUp:(id)sender; +@end + +@interface SPComboBoxCell () + +- (void)sp_selectionDidChange:(NSNotification *)notification; +- (void)sp_cellWillPopUp:(NSNotification *)notification; +- (void)sp_cellWillDismiss:(NSNotification *)notification; +@end + +@implementation SPComboBoxCell + +@synthesize spDelegate; + +- (id)copyWithZone:(NSZone *)zone +{ + SPComboBoxCell *cpy = [super copyWithZone:zone]; + + cpy->spDelegate = [self spDelegate]; + + return cpy; +} + +- (void)dealloc +{ + [self setSpDelegate:nil]; + [super dealloc]; +} + +- (NSWindow *)spPopUpWindow +{ + NSWindow *popUp; + Ivar popUpVar = object_getInstanceVariable(self,"_popUp",(void **)&popUp); + if(popUpVar) { + const char *typeEnc = ivar_getTypeEncoding(popUpVar); + if(typeEnc[0] == '@' && [popUp isKindOfClass:[NSWindow class]]) { // it is an object and of class NSWindow + return popUp; + } + } + return nil; +} + +- (void)popUp:(id)sender +{ + // this notification will be sent after the popup window is resized and moved to the correct position + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(sp_cellWillPopUp:) + name:_CellWillPopUpNotification + object:self]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(sp_cellWillDismiss:) + name:_CellWillDismissNotification + object:self]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(sp_selectionDidChange:) + name:_CellSelectionDidChangeNotification + object:self]; + + [super popUp:sender]; // this method won't return until the window is closed again + + [[NSNotificationCenter defaultCenter] removeObserver:self + name:_CellSelectionDidChangeNotification + object:self]; + + [[NSNotificationCenter defaultCenter] removeObserver:self + name:_CellWillPopUpNotification + object:self]; + + [[NSNotificationCenter defaultCenter] removeObserver:self + name:_CellWillDismissNotification + object:self]; +} + +- (void)sp_selectionDidChange:(NSNotification *)notification +{ + if([[self spDelegate] respondsToSelector:@selector(comboBoxCellSelectionDidChange:)]) { + [[self spDelegate] comboBoxCellSelectionDidChange:self]; + } +} + +- (void)sp_cellWillPopUp:(NSNotification *)notification +{ + NSWindow *popUp = [self spPopUpWindow]; + if(popUp && [[self spDelegate] respondsToSelector:@selector(comboBoxCell:willPopUpWindow:)]) { + [[self spDelegate] comboBoxCell:self willPopUpWindow:popUp]; + } +} + +- (void)sp_cellWillDismiss:(NSNotification *)notification +{ + NSWindow *popUp = [self spPopUpWindow]; + if(popUp && [[self spDelegate] respondsToSelector:@selector(comboBoxCell:willDismissWindow:)]) { + [[self spDelegate] comboBoxCell:self willDismissWindow:popUp]; + } +} + +@end diff --git a/Source/SPScreenAdditions.h b/Source/SPScreenAdditions.h new file mode 100644 index 00000000..7720ce78 --- /dev/null +++ b/Source/SPScreenAdditions.h @@ -0,0 +1,44 @@ +// +// SPScreenAdditions.h +// sequel-pro +// +// Created by Max Lohrmann on 09.11.15. +// Copyright (c) 2015 Max Lohrmann. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at + +#import + +@interface NSScreen (SPScreenAdditions) + ++ (NSScreen *)screenAtPoint:(NSPoint)p; + +/** + * Use this method instead of [[NSScreen screenAtPoint:p] frame] as the + * return value of [nil frame] is not neccesarily reliable. + * This method will return NSZeroRect if no screen is found. + */ ++ (NSRect)rectOfScreenAtPoint:(NSPoint)p; + +@end diff --git a/Source/SPScreenAdditions.m b/Source/SPScreenAdditions.m new file mode 100644 index 00000000..edac0d68 --- /dev/null +++ b/Source/SPScreenAdditions.m @@ -0,0 +1,54 @@ +// +// SPScreenAdditions.m +// sequel-pro +// +// Created by Max Lohrmann on 09.11.15. +// Copyright (c) 2015 Max Lohrmann. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at + +#import "SPScreenAdditions.h" + +@implementation NSScreen (SPScreenAdditions) + ++ (NSScreen *)screenAtPoint:(NSPoint)p +{ + for(NSScreen* candidate in [NSScreen screens]) + { + NSRect cf = [candidate frame]; + if(NSPointInRect(p,cf)) { + return candidate; + } + } + return nil; +} + ++ (NSRect)rectOfScreenAtPoint:(NSPoint)p +{ + NSScreen *s = [NSScreen screenAtPoint:p]; + + return (s? [s frame] : NSZeroRect); +} + +@end diff --git a/Source/SPTableStructure.h b/Source/SPTableStructure.h index 781717f8..c8b0ec0a 100644 --- a/Source/SPTableStructure.h +++ b/Source/SPTableStructure.h @@ -41,6 +41,18 @@ @class SPExtendedTableInfo; @class SPTableInfo; +@interface SPFieldTypeHelp : NSObject { + NSString *typeName; + NSString *typeDefinition; + NSString *typeRange; + NSString *typeDescription; +} +@property(readonly) NSString *typeName; +@property(readonly) NSString *typeDefinition; +@property(readonly) NSString *typeRange; +@property(readonly) NSString *typeDescription; +@end + @interface SPTableStructure : NSObject #ifdef SP_CODA @@ -55,7 +67,10 @@ #endif IBOutlet SPIndexesController *indexesController; IBOutlet SPDatabaseData *databaseDataInstance; - + + IBOutlet NSPanel *structureHelpPanel; + IBOutlet NSTextView *structureHelpText; + #ifndef SP_CODA /* ivars */ IBOutlet id keySheet; IBOutlet id resetAutoIncrementSheet; @@ -158,4 +173,6 @@ // Split view interaction - (IBAction)unhideIndexesView:(id)sender; ++ (SPFieldTypeHelp *)helpForFieldType:(NSString *)typeName; + @end diff --git a/Source/SPTableStructure.m b/Source/SPTableStructure.m index d242ae88..81d0c531 100644 --- a/Source/SPTableStructure.m +++ b/Source/SPTableStructure.m @@ -53,6 +53,42 @@ static NSString *SPRemoveField = @"SPRemoveField"; static NSString *SPRemoveFieldAndForeignKey = @"SPRemoveFieldAndForeignKey"; +@interface SPFieldTypeHelp () +@property(copy,readwrite) NSString *typeName; +@property(copy,readwrite) NSString *typeDefinition; +@property(copy,readwrite) NSString *typeRange; +@property(copy,readwrite) NSString *typeDescription; +@end + +@implementation SPFieldTypeHelp + +@synthesize typeName; +@synthesize typeDefinition; +@synthesize typeRange; +@synthesize typeDescription; + +- (void)dealloc +{ + [self setTypeName:nil]; + [self setTypeDefinition:nil]; + [self setTypeRange:nil]; + [self setTypeDescription:nil]; + [super dealloc]; +} + +@end + +static inline SPFieldTypeHelp *MakeFieldTypeHelp(NSString *typeName,NSString *typeDefinition,NSString *typeRange,NSString *typeDescription) { + SPFieldTypeHelp *obj = [[SPFieldTypeHelp alloc] init]; + + [obj setTypeName: typeName]; + [obj setTypeDefinition: typeDefinition]; + [obj setTypeRange: typeRange]; + [obj setTypeDescription:typeDescription]; + + return [obj autorelease]; +} + @interface SPTableStructure (PrivateAPI) - (void)_removeFieldAndForeignKey:(NSNumber *)removeForeignKey; @@ -1495,4 +1531,296 @@ static NSString *SPRemoveFieldAndForeignKey = @"SPRemoveFieldAndForeignKey"; [super dealloc]; } ++ (SPFieldTypeHelp *)helpForFieldType:(NSString *)typeName +{ + static dispatch_once_t token; + static NSArray *list; + dispatch_once(&token, ^{ + // NSString *FN(NSNumber *): format a number using the user locale (to make large numbers more legible) +#define FN(x) [NSNumberFormatter localizedStringFromNumber:x numberStyle:NSNumberFormatterDecimalStyle] + NSString *intRangeTpl = NSLocalizedString(@"Signed: %@ to %@\nUnsigned: %@ to %@",@"range of integer types"); + // NSString *INTR(NSNumber *sMin, NSNumber *sMax, NSNumber *uMin, NSNumber *uMax): return formatted string for integer types (signed min/max, unsigned min/max) +#define INTR(sMin,sMax,uMin,uMax) [NSString stringWithFormat:intRangeTpl,FN(sMin),FN(sMax),FN(uMin),FN(uMax)] + list = [@[ + MakeFieldTypeHelp( + SPMySQLTinyIntType, + @"TINYINT[(M)] [UNSIGNED] [ZEROFILL]", + INTR(@(-128),@127,@0,@255), + NSLocalizedString(@"The smallest integer type, requires 1 byte storage space. M is the optional display width and does not affect the possible value range.",@"description of tinyint") + ), + MakeFieldTypeHelp( + SPMySQLSmallIntType, + @"SMALLINT[(M)] [UNSIGNED] [ZEROFILL]", + INTR(@(-32768), @32767, @0, @65535), + NSLocalizedString(@"Requires 2 bytes storage space. M is the optional display width and does not affect the possible value range.",@"description of smallint") + ), + MakeFieldTypeHelp( + SPMySQLMediumIntType, + @"MEDIUMINT[(M)] [UNSIGNED] [ZEROFILL]", + INTR(@(-8388608), @8388607, @0, @16777215), + NSLocalizedString(@"Requires 3 bytes storage space. M is the optional display width and does not affect the possible value range.",@"description of mediumint") + ), + MakeFieldTypeHelp( + SPMySQLIntType, + @"INT[(M)] [UNSIGNED] [ZEROFILL]", + INTR(@(-2147483648), @2147483647, @0, @4294967295), + NSLocalizedString(@"Requires 4 bytes storage space. M is the optional display width and does not affect the possible value range. INTEGER is an alias to this type.",@"description of int") + ), + MakeFieldTypeHelp( + SPMySQLBigIntType, + @"BIGINT[(M)] [UNSIGNED] [ZEROFILL]", + INTR([NSDecimalNumber decimalNumberWithString:@"-9223372036854775808"], [NSDecimalNumber decimalNumberWithString:@"9223372036854775807"], @0, [NSDecimalNumber decimalNumberWithString:@"18446744073709551615"]), + NSLocalizedString(@"Requires 8 bytes storage space. M is the optional display width and does not affect the possible value range. Note: Arithmetic operations might fail for large numbers.",@"description of bigint") + ), + MakeFieldTypeHelp( + SPMySQLFloatType, + @"FLOAT[(M,D)] [UNSIGNED] [ZEROFILL]", + NSLocalizedString(@"Accurate to approx. 7 decimal places", @"range of float"), + NSLocalizedString(@"IEEE 754 single-precision floating-point value. M is the maxium number of digits, of which D may be after the decimal point. Note: Many decimal numbers can only be approximated by floating-point values. See DECIMAL if you require exact results.",@"description of float") + ), + MakeFieldTypeHelp( + SPMySQLDoubleType, + @"DOUBLE[(M,D)] [UNSIGNED] [ZEROFILL]", + NSLocalizedString(@"Accurate to approx. 15 decimal places", @"range of double"), + NSLocalizedString(@"IEEE 754 double-precision floating-point value. M is the maxium number of digits, of which D may be after the decimal point. Note: Many decimal numbers can only be approximated by floating-point values. See DECIMAL if you require exact results.",@"description of double") + ), + MakeFieldTypeHelp( + SPMySQLDoublePrecisionType, + @"DOUBLE PRECISION[(M,D)] [UNSIGNED] [ZEROFILL]", + @"", + NSLocalizedString(@"This is an alias for DOUBLE.",@"description of double precision") + ), + MakeFieldTypeHelp( + SPMySQLRealType, + @"REAL[(M,D)] [UNSIGNED] [ZEROFILL]", + @"", + NSLocalizedString(@"This is an alias for DOUBLE, unless REAL_AS_FLOAT is configured.",@"description of double real") + ), + MakeFieldTypeHelp( + SPMySQLDecimalType, + @"DECIMAL[(M[,D])] [UNSIGNED] [ZEROFILL]", + NSLocalizedString(@"M (precision): Up to 65 digits\nD (scale): 0 to 30 digits", @"range of decimal"), + NSLocalizedString(@"A fixed-point, exact decimal value. M is the maxium number of digits, of which D may be after the decimal point. When rounding, 0-4 is always rounded down, 5-9 up (“round towards nearest”).",@"description of decimal") + ), + MakeFieldTypeHelp( + SPMySQLSerialType, + @"SERIAL", + [NSString stringWithFormat:NSLocalizedString(@"Range: %@ to %@", @"range for serial type"),FN(@0),FN([NSDecimalNumber decimalNumberWithString:@"18446744073709551615"])], + NSLocalizedString(@"This is an alias for BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE.",@"description of serial") + ), + MakeFieldTypeHelp( + SPMySQLBitType, + @"BIT[(M)]", + NSLocalizedString(@"M: 1 (default) to 64", @"range for bit type"), + NSLocalizedString(@"A bit-field type. M specifies the number of bits. If shorter values are inserted, they will be aligned on the least significant bit. See the SET type if you want to explicitly name each bit.",@"description of bit") + ), + MakeFieldTypeHelp( + SPMySQLBoolType, + @"BOOL", + @"", + NSLocalizedString(@"This is an alias for TINYINT(1).",@"description of bool") + ), + MakeFieldTypeHelp( + SPMySQLBoolean, + @"BOOLEAN", + @"", + NSLocalizedString(@"This is an alias for TINYINT(1).",@"description of boolean") + ), + MakeFieldTypeHelp( + SPMySQLDecType, + @"DEC[(M[,D])] [UNSIGNED] [ZEROFILL]", + @"", + NSLocalizedString(@"This is an alias for DECIMAL.",@"description of dec") + ), + MakeFieldTypeHelp( + SPMySQLFixedType, + @"FIXED[(M[,D])] [UNSIGNED] [ZEROFILL]", + @"", + NSLocalizedString(@"This is an alias for DECIMAL.",@"description of fixed") + ), + MakeFieldTypeHelp( + SPMySQLNumericType, + @"NUMERIC[(M[,D])] [UNSIGNED] [ZEROFILL]", + @"", + NSLocalizedString(@"This is an alias for DECIMAL.",@"description of numeric") + ), + // ---------------------------------------------------------------------------------- + MakeFieldTypeHelp( + SPMySQLCharType, + @"CHAR(M)", + NSLocalizedString(@"M: 0 to 255 characters", @"range for char type"), + NSLocalizedString(@"A character string that will require M×w bytes per row, independent of the actual content length. w is the maximum number of bytes a single character can occupy in the given encoding.",@"description of char") + ), + MakeFieldTypeHelp( + SPMySQLVarCharType, + @"VARCHAR(M)", + [NSString stringWithFormat:NSLocalizedString(@"M: %@ to %@ characters", @"range for varchar type"),FN(@0),FN(@(65535))], + NSLocalizedString(@"A character string that can store up to M bytes, but requires less space for shorter values. The actual number of characters is further limited by the used encoding and the values of other fields in the row.",@"description of varchar") + ), + MakeFieldTypeHelp( + SPMySQLTinyTextType, + @"TINYTEXT", + NSLocalizedString(@"Up to 255 characters", @"range for tinytext type"), + NSLocalizedString(@"A character string that can store up to 255 bytes, but requires less space for shorter values. The actual number of characters is further limited by the used encoding. Unlike VARCHAR this type does not count towards the maximum row length.",@"description of tinytext") + ), + MakeFieldTypeHelp( + SPMySQLTextType, + @"TEXT[(M)]", + [NSString stringWithFormat:NSLocalizedString(@"M: %@ to %@ characters", @"range for text type"),FN(@0),FN(@(65535))], + NSLocalizedString(@"A character string that can store up to M bytes, but requires less space for shorter values. The actual number of characters is further limited by the used encoding. Unlike VARCHAR this type does not count towards the maximum row length.",@"description of text") + ), + MakeFieldTypeHelp( + SPMySQLMediumTextType, + @"MEDIUMTEXT", + [NSString stringWithFormat:NSLocalizedString(@"Up to %@ characters (16 MiB)", @"range for mediumtext type"),FN(@16777215)], + NSLocalizedString(@"A character string with variable length. The actual number of characters is further limited by the used encoding. Unlike VARCHAR this type does not count towards the maximum row length.",@"description of mediumtext") + ), + MakeFieldTypeHelp( + SPMySQLLongTextType, + @"LONGTEXT", + [NSString stringWithFormat:NSLocalizedString(@"M: %@ to %@ characters (4 GiB)", @"range for longtext type"),FN(@0),FN(@4294967295)], + NSLocalizedString(@"A character string with variable length. The actual number of characters is further limited by the used encoding. Unlike VARCHAR this type does not count towards the maximum row length.",@"description of longtext") + ), + MakeFieldTypeHelp( + SPMySQLTinyBlobType, + @"TINYBLOB", + NSLocalizedString(@"Up to 255 bytes", @"range for tinyblob type"), + NSLocalizedString(@"A byte array with variable length. Unlike VARBINARY this type does not count towards the maximum row length.",@"description of tinyblob") + ), + MakeFieldTypeHelp( + SPMySQLMediumBlobType, + @"MEDIUMBLOB", + [NSString stringWithFormat:NSLocalizedString(@"Up to %@ bytes (16 MiB)", @"range for mediumblob type"),FN(@16777215)], + NSLocalizedString(@"A byte array with variable length. Unlike VARBINARY this type does not count towards the maximum row length.",@"description of mediumblob") + ), + MakeFieldTypeHelp( + SPMySQLBlobType, + @"BLOB[(M)]", + [NSString stringWithFormat:NSLocalizedString(@"M: %@ to %@ bytes", @"range for blob type"),FN(@0),FN(@65535)], + NSLocalizedString(@"A byte array with variable length. Unlike VARBINARY this type does not count towards the maximum row length.",@"description of blob") + ), + MakeFieldTypeHelp( + SPMySQLLongBlobType, + @"LONGBLOB", + [NSString stringWithFormat:NSLocalizedString(@"Up to %@ bytes (4 GiB)", @"range for longblob type"),FN(@4294967295)], + NSLocalizedString(@"A byte array with variable length. Unlike VARBINARY this type does not count towards the maximum row length.",@"description of longblob") + ), + MakeFieldTypeHelp( + SPMySQLBinaryType, + @"BINARY(M)", + NSLocalizedString(@"M: 0 to 255 bytes", @"range for binary type"), + NSLocalizedString(@"A byte array with fixed length. Shorter values will always be padded to the right with 0x00 until they fit M.",@"description of binary") + ), + MakeFieldTypeHelp( + SPMySQLVarBinaryType, + @"VARBINARY(M)", + [NSString stringWithFormat:NSLocalizedString(@"M: %@ to %@ bytes", @"range for varbinary type"),FN(@0),FN(@(65535))], + NSLocalizedString(@"A byte array with variable length. The actual number of bytes is further limited by the values of other fields in the row.",@"description of varbinary") + ), + MakeFieldTypeHelp( + SPMySQLEnumType, + @"ENUM('member',...)", + [NSString stringWithFormat:NSLocalizedString(@"Up to %@ distinct members (<%@ in practice)\n1-2 bytes storage", @"range for enum type"),FN(@(65535)),FN(@3000)], + NSLocalizedString(@"Defines a list of members, of which every field can use at most one. Values are sorted by their index number (starting at 0 for the first member).",@"description of enum") + ), + MakeFieldTypeHelp( + SPMySQLSetType, + @"SET('member',...)", + NSLocalizedString(@"Range: 1 to 64 members\n1, 2, 3, 4 or 8 bytes storage", @"range for set type"), + NSLocalizedString(@"A SET can define up to 64 members (as strings) of which a field can use one or more using a comma-separated list. Upon insertion the order of members is automatically normalized and duplicate members will be eliminated. Assignment of numbers is supported using the same semantics as for BIT types.",@"description of set") + ), + // -------------------------------------------------------------------------- + MakeFieldTypeHelp( + SPMySQLDateType, + @"DATE", + NSLocalizedString(@"Range: 1000-01-01 to 9999-12-31", @"range for date type"), + NSLocalizedString(@"Stores a date without time information. The representation is YYYY-MM-DD. Invalid values are converted to 0000-00-00.",@"description of date") + ), + MakeFieldTypeHelp( + SPMySQLDatetimeType, + @"DATETIME[(F)]", + NSLocalizedString(@"Range: 1000-01-01 00:00:00.0 to 9999-12-31 23:59:59.999999\nF (precision): 0 (1s) to 6 (1µs)", @"range for datetime type"), + NSLocalizedString(@"Stores a date and time of day. The representation is YYYY-MM-DD HH:MM:SS[.I*], I being fractional seconds. Invalid values are converted to 0000-00-00 00:00:00.0. Fractional seconds were added in MySQL 5.6.4 with a precision down to microseconds (6), specified by F.",@"description of datetime") + ), + MakeFieldTypeHelp( + SPMySQLTimestampType, + @"TIMETSTAMP[(F)]", + NSLocalizedString(@"Range: 1970-01-01 00:00:01.0 to 2038-01-19 03:14:07.999999\nF (precision): 0 (1s) to 6 (1µs)", @"range for timestamp type"), + NSLocalizedString(@"Stores a date and time of day as seconds since the beginning of the UNIX epoch (1970-01-01 00:00:00). The representation is the same as for DATETIME. Invalid values, as well as \"second zero\", are converted to 0000-00-00 00:00:00.0. Fractional seconds were added in MySQL 5.6.4 with a precision down to microseconds (6), specified by F. Some additional rules may apply.",@"description of timestamp") + ), + MakeFieldTypeHelp( + SPMySQLTimeType, + @"TIME[(F)]", + NSLocalizedString(@"Range: -838:59:59.0 to 838:59:59.0\nF (precision): 0 (1s) to 6 (1µs)", @"range for time type"), + NSLocalizedString(@"Stores a time of day, duration or time interval. The representation is HH:MM:SS[.I*], I being fractional seconds.Invalid values are converted to 00:00:00. Fractional seconds were added in MySQL 5.6.4 with a precision down to microseconds (6), specified by F.",@"description of time") + ), + MakeFieldTypeHelp( + SPMySQLYearType, + @"YEAR(4)", + NSLocalizedString(@"Range: 0000, 1901 to 2155", @"range for year type"), + NSLocalizedString(@"Represents a 4 digit year value, stored as 1 byte. Invalid values are converted to 0000 and two digit values 0 to 69 will be converted to years 2000 to 2069, resp. 70 to 99 to years 1970 to 1999.\nThe YEAR(2) type was removed in MySQL 5.7.5.",@"description of year") + ), + // -------------------------------------------------------------------------- + MakeFieldTypeHelp( + SPMySQLGeometryType, + @"GEOMETRY", + @"", + NSLocalizedString(@"Can store a single spatial value of types POINT, LINESTRING or POLYGON. Spatial support in MySQL is based on the OpenGIS Geometry Model.",@"description of geometry") + ), + MakeFieldTypeHelp( + SPMySQLPointType, + @"POINT", + @"", + NSLocalizedString(@"Represents a single location in coordinate space using X and Y coordinates. The point is zero-dimensional.",@"description of point") + ), + MakeFieldTypeHelp( + SPMySQLLineStringType, + @"LINESTRING", + @"", + NSLocalizedString(@"Represents an ordered set of coordinates where each consecutive pair of two points is connected by a straight line.",@"description of linestring") + ), + MakeFieldTypeHelp( + SPMySQLPolygonType, + @"POLYGON", + @"", + NSLocalizedString(@"Creates a surface by combining one LinearRing (ie. a LineString that is closed and simple) as the outside boundary with zero or more inner LinearRings acting as \"holes\".",@"description of polygon") + ), + MakeFieldTypeHelp( + SPMySQLMultiPointType, + @"MULTIPOINT", + @"", + NSLocalizedString(@"Represents a set of Points without specifying any kind of relation and/or order between them.",@"description of multipoint") + ), + MakeFieldTypeHelp( + SPMySQLMultiLineStringType, + @"MULTILINESTRING", + @"", + NSLocalizedString(@"Represents a collection of LineStrings.",@"description of multilinestring") + ), + MakeFieldTypeHelp( + SPMySQLMultiPolygonType, + @"MULTIPOLYGON", + @"", + NSLocalizedString(@"Represents a collection of Polygons. The Polygons making up the MultiPolygon must not intersect.",@"description of multipolygon") + ), + MakeFieldTypeHelp( + SPMySQLGeometryCollectionType, + @"GEOMETRYCOLLECTION", + @"", + NSLocalizedString(@"Represents a collection of objects of any other single- or multi-valued spatial type. The only restriction being, that all objects must share a common coordinate system.",@"description of geometrycollection") + ), + ] retain]; +#undef FN +#undef INTR + }); + + for (SPFieldTypeHelp *item in list) { + if ([[item typeName] isEqualToString:typeName]) { + return item; + } + } + + return nil; +} + @end diff --git a/Source/SPTableStructureDelegate.m b/Source/SPTableStructureDelegate.m index 6b2249dc..45ec320d 100644 --- a/Source/SPTableStructureDelegate.m +++ b/Source/SPTableStructureDelegate.m @@ -40,6 +40,7 @@ #import "SPTablesList.h" #import "SPPillAttachmentCell.h" #import "SPIdMenu.h" +#import "SPComboBoxCell.h" #import @@ -65,6 +66,8 @@ static void _BuildMenuWithPills(NSMenu *menu,struct _cmpMap *map,size_t mapEntri - (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo; - (NSString *)_buildPartialColumnDefinitionString:(NSDictionary *)theRow; +- (void)_displayFieldTypeHelpIfPossible:(SPComboBoxCell *)cell; + @end @implementation SPTableStructure (SPTableStructureDelegate) @@ -650,6 +653,105 @@ static void _BuildMenuWithPills(NSMenu *menu,struct _cmpMap *map,size_t mapEntri return @""; } +- (void)comboBoxCell:(SPComboBoxCell *)cell willPopUpWindow:(NSWindow *)win +{ + // the selected item in the popup list is independent of the displayed text, we have to explicitly set it, too + NSInteger pos = [typeSuggestions indexOfObject:[cell stringValue]]; + if(pos != NSNotFound) { + [cell selectItemAtIndex:pos]; + } + + //set up the help window to the right position + NSRect listFrame = [win frame]; + NSRect helpFrame = [structureHelpPanel frame]; + helpFrame.origin.y = listFrame.origin.y; + helpFrame.size.height = listFrame.size.height; + [structureHelpPanel setFrame:helpFrame display:YES]; + + [self _displayFieldTypeHelpIfPossible:cell]; +} + +- (void)comboBoxCell:(SPComboBoxCell *)cell willDismissWindow:(NSWindow *)win +{ + //hide the window if it is still visible + [structureHelpPanel orderOut:nil]; +} + +- (void)comboBoxCellSelectionDidChange:(SPComboBoxCell *)cell +{ + [self _displayFieldTypeHelpIfPossible:cell]; +} + +- (void)_displayFieldTypeHelpIfPossible:(SPComboBoxCell *)cell +{ + NSString *selected = [typeSuggestions objectOrNilAtIndex:[cell indexOfSelectedItem]]; + + const SPFieldTypeHelp *help = [[self class] helpForFieldType:selected]; + + if(help) { + NSMutableAttributedString *as = [[NSMutableAttributedString alloc] init]; + + //title + { + NSDictionary *titleAttr = @{NSFontAttributeName: [NSFont boldSystemFontOfSize:[NSFont systemFontSize]]}; + NSAttributedString *title = [[NSAttributedString alloc] initWithString:[help typeDefinition] attributes:titleAttr]; + [as appendAttributedString:[title autorelease]]; + [[as mutableString] appendString:@"\n"]; + } + + //range + if([[help typeRange] length]) { + NSDictionary *rangeAttr = @{NSFontAttributeName: [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]}; + NSAttributedString *range = [[NSAttributedString alloc] initWithString:[help typeRange] attributes:rangeAttr]; + [as appendAttributedString:[range autorelease]]; + [[as mutableString] appendString:@"\n"]; + } + + [[as mutableString] appendString:@"\n"]; + + //description + { + NSDictionary *descAttr = @{NSFontAttributeName: [NSFont systemFontOfSize:[NSFont systemFontSize]]}; + NSAttributedString *desc = [[NSAttributedString alloc] initWithString:[help typeDescription] attributes:descAttr]; + [as appendAttributedString:[desc autorelease]]; + } + + [as addAttribute:NSParagraphStyleAttributeName value:[NSParagraphStyle defaultParagraphStyle] range:NSMakeRange(0, [as length])]; + + [[structureHelpText textStorage] setAttributedString:[as autorelease]]; + + CGRect rect = [as boundingRectWithSize:CGSizeMake([structureHelpText frame].size.width-2, CGFLOAT_MAX) options:NSStringDrawingUsesFontLeading|NSStringDrawingUsesLineFragmentOrigin]; + + NSRect winRect = [structureHelpPanel frame]; + + CGFloat winAddonSize = (winRect.size.height - [[structureHelpPanel contentView] frame].size.height) + (6*2); + + NSRect popUpFrame = [[cell spPopUpWindow] frame]; + + //determine the side on which to add our window based on the space left on screen + NSPoint topRightCorner = NSMakePoint(popUpFrame.origin.x, NSMaxY(popUpFrame)); + NSRect screenRect = [NSScreen rectOfScreenAtPoint:topRightCorner]; + + if(NSMaxX(popUpFrame)+10+winRect.size.width > NSMaxX(screenRect)-10) { + // exceeds right border, display on the left + winRect.origin.x = popUpFrame.origin.x - 10 - winRect.size.width; + } + else { + // display on the right + winRect.origin.x = NSMaxX(popUpFrame)+10; + } + + winRect.size.height = rect.size.height + winAddonSize; + winRect.origin.y = NSMaxY(popUpFrame) - winRect.size.height; + [structureHelpPanel setFrame:winRect display:YES]; + + [structureHelpPanel orderFront:nil]; + } + else { + [structureHelpPanel orderOut:nil]; + } +} + #pragma mark - #pragma mark Menu delegate methods (encoding/collation dropdown menu) diff --git a/Source/SPTooltip.m b/Source/SPTooltip.m index eefa7520..54a997f6 100644 --- a/Source/SPTooltip.m +++ b/Source/SPTooltip.m @@ -317,16 +317,7 @@ static CGFloat slow_in_out (CGFloat t) NSPoint pos = NSMakePoint([self frame].origin.x, [self frame].origin.y + [self frame].size.height); // Find the screen which we are displaying on - NSRect screenFrame = [[NSScreen mainScreen] frame]; - NSScreen* candidate; - for(candidate in [NSScreen screens]) - { - NSRect cf = [candidate frame]; - if(NSPointInRect(pos,cf)) { - screenFrame = cf; - break; - } - } + NSRect screenFrame = [NSScreen rectOfScreenAtPoint:pos]; // is contentView a webView calculate actual rendered size via JavaScript if([[[[self contentView] class] description] isEqualToString:@"WebView"]) { diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index 7abb7567..143aa9dd 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -179,6 +179,8 @@ 4DECC3370EC2A170008D359E /* Growl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4DECC3340EC2A170008D359E /* Growl.framework */; }; 4DECC48F0EC2B436008D359E /* Sparkle.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 4DECC3320EC2A170008D359E /* Sparkle.framework */; }; 4DECC4910EC2B436008D359E /* Growl.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 4DECC3340EC2A170008D359E /* Growl.framework */; }; + 500DA4B71BEFF877000773FE /* SPComboBoxCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 500DA4B61BEFF877000773FE /* SPComboBoxCell.m */; }; + 500DA4BC1BF0CD57000773FE /* SPScreenAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 500DA4BB1BF0CD57000773FE /* SPScreenAdditions.m */; }; 501B1D181728A3DA0017C92E /* SPCharsetCollationHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 501B1D171728A3DA0017C92E /* SPCharsetCollationHelper.m */; }; 502D21F61BA50710000D4CE7 /* SPDataAdditionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 502D21F51BA50710000D4CE7 /* SPDataAdditionsTests.m */; }; 502D21F81BA50966000D4CE7 /* SPDataAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = BC2C16D30FEBEDF10003993B /* SPDataAdditions.m */; }; @@ -903,6 +905,10 @@ 4D90B7A1101E0D1500D116A1 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/UserManagerView.xib; sourceTree = ""; }; 4DECC3320EC2A170008D359E /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = Frameworks/Sparkle.framework; sourceTree = ""; }; 4DECC3340EC2A170008D359E /* Growl.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Growl.framework; path = Frameworks/Growl.framework; sourceTree = ""; }; + 500DA4B51BEFF877000773FE /* SPComboBoxCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPComboBoxCell.h; sourceTree = ""; }; + 500DA4B61BEFF877000773FE /* SPComboBoxCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPComboBoxCell.m; sourceTree = ""; }; + 500DA4BA1BF0CD57000773FE /* SPScreenAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPScreenAdditions.h; sourceTree = ""; }; + 500DA4BB1BF0CD57000773FE /* SPScreenAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPScreenAdditions.m; sourceTree = ""; }; 501B1D161728A3DA0017C92E /* SPCharsetCollationHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPCharsetCollationHelper.h; sourceTree = ""; }; 501B1D171728A3DA0017C92E /* SPCharsetCollationHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPCharsetCollationHelper.m; sourceTree = ""; }; 502D21F51BA50710000D4CE7 /* SPDataAdditionsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDataAdditionsTests.m; sourceTree = ""; }; @@ -1449,6 +1455,8 @@ BC5750D312A6233900911BA2 /* SPActivityTextFieldCell.m */, 1A56463D14569A0B56EE8BAC /* SPPillAttachmentCell.m */, 1A564C0C0FFB444D2E5CA447 /* SPPillAttachmentCell.h */, + 500DA4B51BEFF877000773FE /* SPComboBoxCell.h */, + 500DA4B61BEFF877000773FE /* SPComboBoxCell.m */, ); name = Cells; sourceTree = ""; @@ -2641,6 +2649,8 @@ 1798F19715501838004B0AB8 /* SPMutableArrayAdditions.m */, 584D899B15162CBE00F24774 /* SPDataBase64EncodingAdditions.h */, 584D899C15162CBE00F24774 /* SPDataBase64EncodingAdditions.m */, + 500DA4BA1BF0CD57000773FE /* SPScreenAdditions.h */, + 500DA4BB1BF0CD57000773FE /* SPScreenAdditions.m */, ); name = "Category Additions"; sourceTree = ""; @@ -3304,6 +3314,7 @@ 1785EB60127DD5A800F468C8 /* SPNotificationsPreferencePane.m in Sources */, 1785EB63127DD5DE00F468C8 /* SPAutoUpdatePreferencePane.m in Sources */, 1785EB66127DD5EA00F468C8 /* SPNetworkPreferencePane.m in Sources */, + 500DA4B71BEFF877000773FE /* SPComboBoxCell.m in Sources */, 1785EB6A127DD79300F468C8 /* SPEditorPreferencePane.m in Sources */, 17FDB04C1280778B00DBBBC2 /* SPFontPreviewTextField.m in Sources */, 17D3DC201281816E002A163A /* SPDatabaseViewController.m in Sources */, @@ -3323,6 +3334,7 @@ 583CA21512EC8B2200C9E763 /* SPWindow.m in Sources */, 582F02311370B52600B30621 /* SPExportFileNameTokenObject.m in Sources */, 1713C740140D8AEF00CFD461 /* SPQueryDocumentsController.m in Sources */, + 500DA4BC1BF0CD57000773FE /* SPScreenAdditions.m in Sources */, 1713C75F140D8D5900CFD461 /* SPQueryConsoleDataSource.m in Sources */, 17902612141025BB005F677F /* SPQueryControllerInitializer.m in Sources */, 584D878B15140FEB00F24774 /* SPObjectAdditions.m in Sources */, -- cgit v1.2.3 From 2ae3aec0c721c70941f0d23eecfa880861faf3cf Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 10 Nov 2015 18:09:29 +0100 Subject: buildbot didn't like that... --- Source/SPTableStructureDelegate.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/SPTableStructureDelegate.m b/Source/SPTableStructureDelegate.m index 45ec320d..da173271 100644 --- a/Source/SPTableStructureDelegate.m +++ b/Source/SPTableStructureDelegate.m @@ -720,7 +720,7 @@ static void _BuildMenuWithPills(NSMenu *menu,struct _cmpMap *map,size_t mapEntri [[structureHelpText textStorage] setAttributedString:[as autorelease]]; - CGRect rect = [as boundingRectWithSize:CGSizeMake([structureHelpText frame].size.width-2, CGFLOAT_MAX) options:NSStringDrawingUsesFontLeading|NSStringDrawingUsesLineFragmentOrigin]; + NSRect rect = [as boundingRectWithSize:NSMakeSize([structureHelpText frame].size.width-2, CGFLOAT_MAX) options:NSStringDrawingUsesFontLeading|NSStringDrawingUsesLineFragmentOrigin]; NSRect winRect = [structureHelpPanel frame]; -- cgit v1.2.3 From 74a90251228a07995ea5ab1bae0fb2428f9cfbc7 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 11 Nov 2015 00:08:45 +0100 Subject: Fix an issue where changing a table collation could cause an exception (fixes #2320) This issue probably was introduced in f02fb787063caabe246a0ee420394f5676c55a9c The empty item at the top of the collation list will now no longer have a selection mark, though. --- Interfaces/English.lproj/DBView.xib | 9 +++++++ Source/SPPopUpButtonCell.h | 48 +++++++++++++++++++++++++++++++++ Source/SPPopUpButtonCell.m | 51 ++++++++++++++++++++++++++++++++++++ Source/SPTableStructureDelegate.m | 14 +++++----- sequel-pro.xcodeproj/project.pbxproj | 6 +++++ 5 files changed, 121 insertions(+), 7 deletions(-) create mode 100644 Source/SPPopUpButtonCell.h create mode 100644 Source/SPPopUpButtonCell.m diff --git a/Interfaces/English.lproj/DBView.xib b/Interfaces/English.lproj/DBView.xib index 465b72fa..63bbd031 100644 --- a/Interfaces/English.lproj/DBView.xib +++ b/Interfaces/English.lproj/DBView.xib @@ -26580,6 +26580,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA com.apple.InterfaceBuilder.CocoaPlugin + SPPopUpButtonCell com.apple.InterfaceBuilder.CocoaPlugin SPIdMenu @@ -30321,6 +30322,14 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA ../Source/SPIndexesController.m + + SPPopUpButtonCell + NSPopUpButtonCell + + IBProjectSource + ../Source/SPPopUpButtonCell.h + + SPProcessListController NSWindowController diff --git a/Source/SPPopUpButtonCell.h b/Source/SPPopUpButtonCell.h new file mode 100644 index 00000000..52c8a074 --- /dev/null +++ b/Source/SPPopUpButtonCell.h @@ -0,0 +1,48 @@ +// +// SPPopUpButtonCell.h +// sequel-pro +// +// Created by Max Lohrmann on 10.11.15. +// Copyright (c) 2015 Max Lohrmann. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at + +#import + +/** + * The only difference here is that objectValue/setObjectValue: + * will use the representedObject of the menu instead of the menu item's index + * + * nil will act the same as @(-1) would with the regular NSPopUpButtonCell. + * + * Note that in theory multiple menu items could have the same representedObject + * and identification is done via isEqual: so this class would only ever + * pick the first one. + */ +@interface SPPopUpButtonCell : NSPopUpButtonCell + +- (id)objectValue; +- (void)setObjectValue:(id)objectValue; + +@end diff --git a/Source/SPPopUpButtonCell.m b/Source/SPPopUpButtonCell.m new file mode 100644 index 00000000..a20a2d92 --- /dev/null +++ b/Source/SPPopUpButtonCell.m @@ -0,0 +1,51 @@ +// +// SPPopUpButtonCell.m +// sequel-pro +// +// Created by Max Lohrmann on 10.11.15. +// Copyright (c) 2015 Max Lohrmann. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at + +#import "SPPopUpButtonCell.h" + +@implementation SPPopUpButtonCell + +- (id)objectValue +{ + NSInteger n = [[super objectValue] integerValue]; + + // this method can be called for invalid selections, which return -1, which fails with itemAtIndex: + if(n < 0 || n >= [self numberOfItems]) return nil; + + return [[self itemAtIndex:n] representedObject]; +} + +- (void)setObjectValue:(id)objectValue +{ + NSInteger n = [self indexOfItemWithRepresentedObject:objectValue]; + [super setObjectValue:@(n)]; +} + +@end diff --git a/Source/SPTableStructureDelegate.m b/Source/SPTableStructureDelegate.m index da173271..a7fa9337 100644 --- a/Source/SPTableStructureDelegate.m +++ b/Source/SPTableStructureDelegate.m @@ -102,7 +102,7 @@ static void _BuildMenuWithPills(NSMenu *menu,struct _cmpMap *map,size_t mapEntri [[collationCell lastItem] setTitle:@""]; //if this is not set the column either has no encoding (numeric etc.) or retrieval failed. Either way we can't provide collations - if(columnEncoding) { + if([columnEncoding length]) { collations = [databaseDataInstance getDatabaseCollationsForEncoding:columnEncoding]; if ([collations count] > 0) { @@ -129,19 +129,18 @@ static void _BuildMenuWithPills(NSMenu *menu,struct _cmpMap *map,size_t mapEntri } } - //look up the right item - NSInteger idx = [collationCell indexOfItemWithRepresentedObject:columnCollation]; - if(idx > 0) return @(idx); + // the popup cell is subclassed to take the representedObject instead of the item index + return columnCollation; } } - return @0; + return nil; } else if ([[tableColumn identifier] isEqualToString:@"encoding"]) { // the encoding menu was already configured during setTableDetails: NSString *columnEncoding = [rowData objectForKey:@"encodingName"]; - if(columnEncoding) { + if([columnEncoding length]) { NSInteger idx = [encodingPopupCell indexOfItemWithRepresentedObject:columnEncoding]; if(idx > 0) return @(idx); } @@ -193,7 +192,8 @@ static void _BuildMenuWithPills(NSMenu *menu,struct _cmpMap *map,size_t mapEntri return; } else if ([[aTableColumn identifier] isEqualToString:@"collation"]) { - NSString *newCollation = [[(NSPopUpButtonCell *)[aTableColumn dataCell] itemAtIndex:[anObject integerValue]] representedObject]; + //the popup button is subclassed to return the representedObject instead of the item index + NSString *newCollation = anObject; if(!newCollation) [currentRow removeObjectForKey:@"collationName"]; diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index 143aa9dd..7d3dc6a5 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -200,6 +200,7 @@ 507FF26A1BC8450100104523 /* SPExportSettingsPersistence.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF2691BC8450100104523 /* SPExportSettingsPersistence.m */; }; 507FF2A11BCD27A700104523 /* SPFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF1111BBCC57600104523 /* SPFunctions.m */; }; 507FF2A21BCD27AE00104523 /* SPOSInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 50EAB5B71A8FBB08008F627A /* SPOSInfo.m */; }; + 50805B0D1BF2A068005F7A99 /* SPPopUpButtonCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 50805B0C1BF2A068005F7A99 /* SPPopUpButtonCell.m */; }; 5089B0271BE714E300E226CD /* SPIdMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 5089B0261BE714E300E226CD /* SPIdMenu.m */; }; 50A9F8B119EAD4B90053E571 /* SPGotoDatabaseController.m in Sources */ = {isa = PBXBuildFile; fileRef = 50A9F8B019EAD4B90053E571 /* SPGotoDatabaseController.m */; }; 50D3C3491A75B8A800B5429C /* GotoDatabaseDialog.xib in Resources */ = {isa = PBXBuildFile; fileRef = 50D3C34B1A75B8A800B5429C /* GotoDatabaseDialog.xib */; }; @@ -923,6 +924,8 @@ 507FF1111BBCC57600104523 /* SPFunctions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPFunctions.m; sourceTree = ""; }; 507FF2681BC8450100104523 /* SPExportSettingsPersistence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPExportSettingsPersistence.h; sourceTree = ""; }; 507FF2691BC8450100104523 /* SPExportSettingsPersistence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExportSettingsPersistence.m; sourceTree = ""; }; + 50805B0B1BF2A068005F7A99 /* SPPopUpButtonCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPPopUpButtonCell.h; sourceTree = ""; }; + 50805B0C1BF2A068005F7A99 /* SPPopUpButtonCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPPopUpButtonCell.m; sourceTree = ""; }; 5089B0251BE714E300E226CD /* SPIdMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPIdMenu.h; sourceTree = ""; }; 5089B0261BE714E300E226CD /* SPIdMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPIdMenu.m; sourceTree = ""; }; 50A9F8AF19EAD4B90053E571 /* SPGotoDatabaseController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPGotoDatabaseController.h; sourceTree = ""; }; @@ -2326,6 +2329,8 @@ 17FDB04B1280778B00DBBBC2 /* SPFontPreviewTextField.m */, 58D2A6A516FBDEFF002EB401 /* SPComboPopupButton.h */, 58D2A6A616FBDEFF002EB401 /* SPComboPopupButton.m */, + 50805B0B1BF2A068005F7A99 /* SPPopUpButtonCell.h */, + 50805B0C1BF2A068005F7A99 /* SPPopUpButtonCell.m */, ); name = Controls; sourceTree = ""; @@ -3286,6 +3291,7 @@ 173C837A11AAD2AE00B8B084 /* SPHTMLExporter.m in Sources */, 173C837B11AAD2AE00B8B084 /* SPPDFExporter.m in Sources */, 173C839011AAD32A00B8B084 /* SPCSVExporterDelegate.m in Sources */, + 50805B0D1BF2A068005F7A99 /* SPPopUpButtonCell.m in Sources */, 173C839111AAD32A00B8B084 /* SPDotExporterDelegate.m in Sources */, 173C839211AAD32A00B8B084 /* SPHTMLExporterDelegate.m in Sources */, 173C839311AAD32A00B8B084 /* SPPDFExporterDelegate.m in Sources */, -- cgit v1.2.3 From afd6f72c968547a0b228b02780409d8f01096da5 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 11 Nov 2015 02:38:52 +0100 Subject: In the table quick info section: Display shorter name for collation if it has the same prefix as encoding to save some space --- Source/SPTableInfo.m | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Source/SPTableInfo.m b/Source/SPTableInfo.m index 942cf63f..1767d3b0 100644 --- a/Source/SPTableInfo.m +++ b/Source/SPTableInfo.m @@ -192,7 +192,16 @@ } [info addObject:[NSString stringWithFormat:NSLocalizedString(@"size: %@", @"Table Info Section : table size on disk"), [NSString stringForByteSize:[[tableStatus objectForKey:@"Data_length"] longLongValue]]]]; - [info addObject:[NSString stringWithFormat:NSLocalizedString(@"encoding: %1$@ (%2$@)", @"Table Info Section : $1 = table charset, $2 = table collation"), [tableDataInstance tableEncoding], [tableStatus objectForKey:@"Collation"]]]; + NSString *tableEnc = [tableDataInstance tableEncoding]; + NSString *tableColl = [tableStatus objectForKey:@"Collation"]; + if([tableColl length]) { + // instead of @"latin1 (latin1_german_ci)" we can just show @"latin1 (german_ci)" + if([tableColl hasPrefix:[NSString stringWithFormat:@"%@_",tableEnc]]) tableColl = [tableColl substringFromIndex:([tableEnc length]+1)]; + [info addObject:[NSString stringWithFormat:NSLocalizedString(@"encoding: %1$@ (%2$@)", @"Table Info Section : $1 = table charset, $2 = table collation"), tableEnc, tableColl]]; + } + else { + [info addObject:[NSString stringWithFormat:NSLocalizedString(@"encoding: %1$@", @"Table Info Section : $1 = table charset"), tableEnc]]; + } if (![[tableStatus objectForKey:@"Auto_increment"] isNSNull]) { [info addObject:[NSString stringWithFormat:NSLocalizedString(@"auto_increment: %@", @"Table Info Section : current value of auto_increment"), -- cgit v1.2.3 From 0dbadf887635bb3904d3aa64a0588a0b23d884dc Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 12 Nov 2015 01:42:02 +0100 Subject: In order to enable even more people to shoot themselves in the foot, this commit adds support for remote server shutdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 💣 --- .../SPMySQLConnection Categories/Server Info.h | 7 + .../SPMySQLConnection Categories/Server Info.m | 17 +++ Interfaces/English.lproj/MainMenu.xib | 160 ++++++++++++++++----- Source/SPDatabaseDocument.h | 1 + Source/SPDatabaseDocument.m | 34 +++++ 5 files changed, 187 insertions(+), 32 deletions(-) diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h index 82607cdb..844a6b5f 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h @@ -45,4 +45,11 @@ - (SPMySQLResult *)listProcesses; - (BOOL)killQueryOnThreadID:(unsigned long)theThreadID; +/** + * mysql_shutdown() - If the user has the permission, will shutdown the (remote) server + * @return Whether the command was executed successfully + * Note: this can also be NO if the user denied a reconnect attempt. + */ +- (BOOL)serverShutdown; + @end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m index 5925d138..f9949121 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m @@ -148,4 +148,21 @@ return ![self queryErrored]; } +- (BOOL)serverShutdown +{ + if([self _checkConnectionIfNecessary]) { + [self _lockConnection]; + // Ensure per-thread variables are set up + [self _validateThreadSetup]; + //only SHUTDOWN_DEFAULT is supported right now + int res = mysql_shutdown(mySQLConnection, SHUTDOWN_DEFAULT); + //update or clear error + [self _updateLastErrorInfos]; + [self _unlockConnection]; + + return (res == 0); + } + return NO; +} + @end diff --git a/Interfaces/English.lproj/MainMenu.xib b/Interfaces/English.lproj/MainMenu.xib index 7a2988d2..31f3ff83 100644 --- a/Interfaces/English.lproj/MainMenu.xib +++ b/Interfaces/English.lproj/MainMenu.xib @@ -1845,6 +1845,14 @@ + + + Shutdown Server… + + 2147483647 + + + @@ -3419,6 +3427,14 @@ mhI-Hy-VAZ + + + shutdownServer: + + + + S1h-UE-mTv + delegate @@ -4385,6 +4401,7 @@ + @@ -5130,6 +5147,11 @@ + + jZI-ad-FsC + + + @@ -5766,6 +5788,7 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -6069,17 +6092,6 @@ id - - bundleEditorWindow - NSWindow - - - bundleEditorWindow - - bundleEditorWindow - NSWindow - - IBProjectSource ../Source/SPAppController.h @@ -6741,7 +6753,6 @@ id id id - id id id NSMenuItem @@ -6792,10 +6803,6 @@ makeSelectedFavoriteDefault: id - - nodeDoubleClicked: - id - removeNode: id @@ -7111,7 +7118,6 @@ id id id - id id id id @@ -7160,10 +7166,6 @@ makeSelectedFavoriteDefault: id - - nodeDoubleClicked: - id - removeNode: id @@ -7855,6 +7857,7 @@ id id id + id id id @@ -8027,6 +8030,10 @@ showUserManager: id + + shutdownServer: + id + toggleNavigator: id @@ -8093,6 +8100,7 @@ SPTableData id NSScrollView + SPSplitView NSTableView id SPTableStructure @@ -8332,6 +8340,10 @@ tableInfoScrollView NSScrollView + + tableInfoSplitView + SPSplitView + tableInfoTable NSTableView @@ -8435,6 +8447,7 @@ id id id + id id id @@ -8607,6 +8620,10 @@ showUserManager: id + + shutdownServer: + id + toggleNavigator: id @@ -8862,7 +8879,7 @@ NSComboBox NSTextField NSTokenField - NSTokenField + NSTokenField NSView NSButton NSButton @@ -8962,8 +8979,8 @@ exportCustomFilenameTokenField NSTokenField - - exportCustomFilenameTokensField + + exportCustomFilenameTokenPool NSTokenField @@ -9227,13 +9244,55 @@ ../Source/SPExportController.m + + SPExportController + + id + id + + + + exportCurrentSettings: + id + + + importCurrentSettings: + id + + + + IBProjectSource + ../Source/SPExportSettingsPersistence.h + + + + SPExportController + + id + id + + + + exportCurrentSettings: + id + + + importCurrentSettings: + id + + + + IBProjectSource + ../Source/SPExportSettingsPersistence.m + + SPExtendedTableInfo NSObject id id - id + id id id id @@ -9247,8 +9306,8 @@ resetAutoIncrement: id - - resetAutoIncrementValueWasEdited: + + tableRowAutoIncrementWasEdited: id @@ -9383,7 +9442,7 @@ id id - id + id id id id @@ -9397,8 +9456,8 @@ resetAutoIncrement: id - - resetAutoIncrementValueWasEdited: + + tableRowAutoIncrementWasEdited: id @@ -10868,7 +10927,7 @@ NSObject NSTableView - id + NSTableView id id NSScrollView @@ -10882,7 +10941,7 @@ infoTable - id + NSTableView tableDataInstance @@ -10978,6 +11037,8 @@ id id id + NSPanel + NSTextView SPTableData SPDatabaseDocument SPTableInfo @@ -11067,6 +11128,14 @@ structureGrabber id + + structureHelpPanel + NSPanel + + + structureHelpText + NSTextView + tableDataInstance SPTableData @@ -12323,6 +12392,33 @@ Quartz.framework/Frameworks/PDFKit.framework/Headers/PDFView.h + + PSMTabBarControl + NSControl + + id + id + NSTabView + + + + delegate + id + + + partnerView + id + + + tabView + NSTabView + + + + IBFrameworkSource + PSMTabBar.framework/Headers/PSMTabBarControl.h + + QCView NSView diff --git a/Source/SPDatabaseDocument.h b/Source/SPDatabaseDocument.h index b094a65f..e36b4a58 100644 --- a/Source/SPDatabaseDocument.h +++ b/Source/SPDatabaseDocument.h @@ -359,6 +359,7 @@ - (IBAction) makeTableListFilterHaveFocus:(id)sender; - (IBAction)showServerVariables:(id)sender; - (IBAction)showServerProcesses:(id)sender; +- (IBAction)shutdownServer:(id)sender; - (IBAction)openCurrentConnectionInNewWindow:(id)sender; - (IBAction)showGotoDatabase:(id)sender; #endif diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index d4c568b2..05bdc849 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -968,6 +968,40 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; [processListController displayProcessListWindow]; } + +- (IBAction)shutdownServer:(id)sender +{ + // confirm user action + SPBeginAlertSheet( + NSLocalizedString(@"Do you really want to shutdown the server?", @"shutdown server : confirmation dialog : title"), + NSLocalizedString(@"Shutdown", @"shutdown server : confirmation dialog : shutdown button"), + NSLocalizedString(@"Cancel", @"shutdown server : confirmation dialog : cancel button"), + nil, + parentWindow, + self, + @selector(shutdownAlertDidEnd:returnCode:contextInfo:), + NULL, + NSLocalizedString(@"This will wait for open transactions to complete and then quit the mysql daemon. Afterwards neither you nor anyone else can connect to this database!\n\nFull management access to the server's operating system is required to restart MySQL!", @"shutdown server : confirmation dialog : message") + ); +} + +- (void)shutdownAlertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo +{ + if(returnCode != NSAlertDefaultReturn) return; //cancelled by user + + if(![mySQLConnection serverShutdown]) { + if([mySQLConnection isConnected]) { + SPOnewayAlertSheet( + NSLocalizedString(@"Shutdown failed!", @"shutdown server : error dialog : title"), + parentWindow, + [NSString stringWithFormat:NSLocalizedString(@"MySQL said:\n%@", @"shutdown server : error dialog : message"),[mySQLConnection lastErrorMessage]] + ); + } + } + // shutdown successful. + // Until s.o. has a good UI idea, do nothing. Sequel Pro should figure out the connection loss soon enough +} + #endif /** -- cgit v1.2.3 From 017ad9b38b87af09134c015086b47799acbcee0b Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 13 Nov 2015 17:34:23 +0100 Subject: Fix: "Custom filename" in export dialog would accept multi line text (#2325) --- Interfaces/English.lproj/ExportDialog.xib | 2 +- Source/SPExportControllerDelegate.m | 2 +- Source/SPStringAdditions.h | 7 ++++++ Source/SPStringAdditions.m | 38 +++++++++++++++++++++++++++++++ UnitTests/SPStringAdditionsTests.m | 21 +++++++++++++++++ 5 files changed, 68 insertions(+), 2 deletions(-) diff --git a/Interfaces/English.lproj/ExportDialog.xib b/Interfaces/English.lproj/ExportDialog.xib index 7080c27e..1eea6eab 100644 --- a/Interfaces/English.lproj/ExportDialog.xib +++ b/Interfaces/English.lproj/ExportDialog.xib @@ -1154,7 +1154,7 @@ YES -1804599231 - 272761856 + 272761920 diff --git a/Source/SPExportControllerDelegate.m b/Source/SPExportControllerDelegate.m index cf3101cf..0f18ef25 100644 --- a/Source/SPExportControllerDelegate.m +++ b/Source/SPExportControllerDelegate.m @@ -157,7 +157,7 @@ static inline BOOL IS_STRING(id x); // if the string came from another app, paste it literal, tokenfield will take care of any conversions NSString *raw = [pboard stringForType:NSPasteboardTypeString]; if(raw) { - return @[raw]; + return @[[raw stringByReplacingCharactersInSet:[NSCharacterSet newlineCharacterSet] withString:@" "]]; } return nil; diff --git a/Source/SPStringAdditions.h b/Source/SPStringAdditions.h index de6d8377..a5b1b468 100644 --- a/Source/SPStringAdditions.h +++ b/Source/SPStringAdditions.h @@ -78,6 +78,13 @@ static inline id NSMutableAttributedStringAttributeAtIndex(NSMutableAttributedSt - (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet *)charSet; - (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet *)charSet options:(NSUInteger)mask; +/** + * Replace all occurances of any character in set with the replacement string + * @param set Characters to look for (MUST NOT be nil) + * @param string A replacement string (can be nil == empty string) + * @return A string with replacements applied + */ +- (NSString *)stringByReplacingCharactersInSet:(NSCharacterSet *)set withString:(NSString *)string; - (CGFloat)levenshteinDistanceWithWord:(NSString *)stringB; diff --git a/Source/SPStringAdditions.m b/Source/SPStringAdditions.m index f8aeb296..25a21541 100644 --- a/Source/SPStringAdditions.m +++ b/Source/SPStringAdditions.m @@ -344,6 +344,44 @@ static NSInteger _smallestOf(NSInteger a, NSInteger b, NSInteger c); return lineRangesArray; } +- (NSString *)stringByReplacingCharactersInSet:(NSCharacterSet *)set withString:(NSString *)string +{ + NSUInteger len = [self length]; + NSMutableString *newString = [NSMutableString string]; + + NSRange range = NSMakeRange (0, len); + + while (true) { + NSRange substringRange; + NSUInteger pos = range.location; + BOOL endAfterInsert = NO; + + range = [self rangeOfCharacterFromSet:set options:0 range:range]; + + if(range.location == NSNotFound) { + // insert the current substring up to the end + substringRange = NSMakeRange(pos, len - pos); + endAfterInsert = YES; + } + else { + // insert the current substring up to range.location + substringRange = NSMakeRange(pos, range.location - pos); + } + + [newString appendString:[self substringWithRange:substringRange]]; + + if(endAfterInsert) break; + + // insert the replacement character + [newString appendStringOrNil:string]; + + // continue after the replaced characters + range.location += range.length; + range.length = len - range.location; + } + return newString; +} + /** * Returns the string by removing the characters in the supplied set and options. */ diff --git a/UnitTests/SPStringAdditionsTests.m b/UnitTests/SPStringAdditionsTests.m index 23ec82e9..00cbdc14 100644 --- a/UnitTests/SPStringAdditionsTests.m +++ b/UnitTests/SPStringAdditionsTests.m @@ -39,6 +39,7 @@ - (void)testStringWithNewUUID; - (void)testCreateViewSyntaxPrettifier; - (void)testNonConsecutivelySearchStringMatchingRanges; +- (void)testStringByReplacingCharactersInSetWithString; @end @@ -195,6 +196,26 @@ static NSRange RangeFromArray(NSArray *a,NSUInteger idx); } +- (void)testStringByReplacingCharactersInSetWithString +{ + { + //test against empty string + STAssertEqualObjects([@"" stringByReplacingCharactersInSet:[NSCharacterSet whitespaceCharacterSet] withString:@"x"], @"", @"replacement on empty string must result in empty string"); + } + { + //test match at begin, middle, end / consecutive matches + STAssertEqualObjects([@" ab c " stringByReplacingCharactersInSet:[NSCharacterSet whitespaceCharacterSet] withString:@"_"], @"_ab__c_", @"Testing matches at both end, replacement of consecutive matches"); + } + { + //test replacement of different characters + STAssertEqualObjects([@"ab\r\ncd" stringByReplacingCharactersInSet:[NSCharacterSet newlineCharacterSet] withString:@"*"], @"ab**cd", @"Testing replacement of different characters in set"); + } + { + // nil for replacement char + STAssertEqualObjects([@"ab\r\ncd" stringByReplacingCharactersInSet:[NSCharacterSet newlineCharacterSet] withString:nil], @"abcd", @"testing replacement with nil"); + } +} + @end NSRange RangeFromArray(NSArray *a,NSUInteger idx) -- cgit v1.2.3 From 0f40c5e34a075bf3b3eb74b57fe6cfba6c84a02c Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 13 Nov 2015 18:31:01 +0100 Subject: Fix another set of fields that allowed multi-line input (#2325) --- Interfaces/English.lproj/BundleEditor.xib | 10 ++++---- Interfaces/English.lproj/Console.xib | 2 +- Interfaces/English.lproj/ContentFilterManager.xib | 2 +- Interfaces/English.lproj/ContentPaginationView.xib | 4 +-- Interfaces/English.lproj/DBView.xib | 30 +++++++++++----------- Interfaces/English.lproj/DataMigrationDialog.xib | 2 +- Interfaces/English.lproj/DatabaseProcessList.xib | 4 +-- .../English.lproj/DatabaseServerVariables.xib | 2 +- Interfaces/English.lproj/ExportDialog.xib | 14 +++++----- Interfaces/English.lproj/FieldEditorSheet.xib | 6 ++--- Interfaces/English.lproj/ImportAccessory.xib | 8 +++--- Interfaces/English.lproj/IndexesView.xib | 4 +-- Interfaces/English.lproj/Navigator.xib | 2 +- Interfaces/English.lproj/Preferences.xib | 20 +++++++-------- Interfaces/English.lproj/QueryFavoriteManager.xib | 4 +-- Interfaces/English.lproj/SSHQuestionDialog.xib | 2 +- Interfaces/English.lproj/SaveSPFAccessory.xib | 2 +- Interfaces/English.lproj/UserManagerView.xib | 10 ++++---- 18 files changed, 64 insertions(+), 64 deletions(-) diff --git a/Interfaces/English.lproj/BundleEditor.xib b/Interfaces/English.lproj/BundleEditor.xib index ffd2103e..231712da 100644 --- a/Interfaces/English.lproj/BundleEditor.xib +++ b/Interfaces/English.lproj/BundleEditor.xib @@ -1052,7 +1052,7 @@ YES -1804599231 - 272761856 + 272761920 [sub menu category] @@ -1216,7 +1216,7 @@ YES -1804599231 - 272761856 + 272761920 [menu label tooltip] @@ -1256,7 +1256,7 @@ YES -1804599231 - 272761856 + 272761920 [menu label name] @@ -1605,7 +1605,7 @@ YES -1804599231 - 272761856 + 272761920 @@ -1642,7 +1642,7 @@ YES -1804599231 - 272761856 + 272761920 diff --git a/Interfaces/English.lproj/Console.xib b/Interfaces/English.lproj/Console.xib index d48195cf..f82b4035 100644 --- a/Interfaces/English.lproj/Console.xib +++ b/Interfaces/English.lproj/Console.xib @@ -133,7 +133,7 @@ YES 342884416 - 268567552 + 268567616 Filter diff --git a/Interfaces/English.lproj/ContentFilterManager.xib b/Interfaces/English.lproj/ContentFilterManager.xib index 89104d4a..636c7761 100644 --- a/Interfaces/English.lproj/ContentFilterManager.xib +++ b/Interfaces/English.lproj/ContentFilterManager.xib @@ -828,7 +828,7 @@ YES -1804599231 - 272761856 + 272761920 [no selection] diff --git a/Interfaces/English.lproj/ContentPaginationView.xib b/Interfaces/English.lproj/ContentPaginationView.xib index 4865483d..3d5e3420 100644 --- a/Interfaces/English.lproj/ContentPaginationView.xib +++ b/Interfaces/English.lproj/ContentPaginationView.xib @@ -156,7 +156,7 @@ YES -1804599231 - -2076048384 + -2076048320 @@ -374,7 +374,7 @@ YES -1804599231 - 67240960 + 67241024 diff --git a/Interfaces/English.lproj/DBView.xib b/Interfaces/English.lproj/DBView.xib index 63bbd031..dd04904d 100644 --- a/Interfaces/English.lproj/DBView.xib +++ b/Interfaces/English.lproj/DBView.xib @@ -267,7 +267,7 @@ YES 342884417 - 268567552 + 268567616 @@ -3270,7 +3270,7 @@ YES 879755329 - 268567552 + 268567616 @@ -6919,7 +6919,7 @@ YES -1804599231 - 272761856 + 272761920 _NS:9 @@ -7165,7 +7165,7 @@ YES -1804599231 - 4326400 + 4326464 @@ -7358,7 +7358,7 @@ YES -1804599231 - 4326400 + 4326464 @@ -7489,7 +7489,7 @@ YES -1804599231 - 4326400 + 4326464 @@ -7812,7 +7812,7 @@ YES -1804599231 - 272761856 + 272761920 _NS:9 @@ -8084,7 +8084,7 @@ IGRvIHlvdSB3YW50IHRvIGFkZCBmb3IgdGhpcyBmaWVsZD8 YES -1804599231 - 4326400 + 4326464 @@ -8368,7 +8368,7 @@ IGRvIHlvdSB3YW50IHRvIGFkZCBmb3IgdGhpcyBmaWVsZD8 YES -1804599231 - 272761856 + 272761920 Generate one @@ -9018,7 +9018,7 @@ IGRvIHlvdSB3YW50IHRvIGFkZCBmb3IgdGhpcyBmaWVsZD8 YES -1804599231 - 272761856 + 272761920 @@ -10142,7 +10142,7 @@ IGRvIHlvdSB3YW50IHRvIGFkZCBmb3IgdGhpcyBmaWVsZD8 YES 341835841 - 268568576 + 268568640 Search @@ -11270,7 +11270,7 @@ IGRvIHlvdSB3YW50IHRvIGFkZCBmb3IgdGhpcyBmaWVsZD8 YES -1804599231 - 4326400 + 4326464 @@ -11433,7 +11433,7 @@ IGRvIHlvdSB3YW50IHRvIGFkZCBmb3IgdGhpcyBmaWVsZD8 YES 342884417 - 272630784 + 272630848 @@ -11921,7 +11921,7 @@ IGRvIHlvdSB3YW50IHRvIGFkZCBmb3IgdGhpcyBmaWVsZD8 YES 342884417 - 272794624 + 272794688 Filter @@ -11984,7 +11984,7 @@ IGRvIHlvdSB3YW50IHRvIGFkZCBmb3IgdGhpcyBmaWVsZD8 YES 342884417 - 272794624 + 272794688 Filter diff --git a/Interfaces/English.lproj/DataMigrationDialog.xib b/Interfaces/English.lproj/DataMigrationDialog.xib index 740dedcb..b79f2a50 100644 --- a/Interfaces/English.lproj/DataMigrationDialog.xib +++ b/Interfaces/English.lproj/DataMigrationDialog.xib @@ -1549,7 +1549,7 @@ YES -1804599231 - 272761856 + 272761920 diff --git a/Interfaces/English.lproj/DatabaseProcessList.xib b/Interfaces/English.lproj/DatabaseProcessList.xib index 7b271a72..4ade9787 100644 --- a/Interfaces/English.lproj/DatabaseProcessList.xib +++ b/Interfaces/English.lproj/DatabaseProcessList.xib @@ -482,7 +482,7 @@ YES 342884416 - 268567552 + 268567616 Filter @@ -1019,7 +1019,7 @@ YES -1804599231 - 272630784 + 272630848 diff --git a/Interfaces/English.lproj/DatabaseServerVariables.xib b/Interfaces/English.lproj/DatabaseServerVariables.xib index e32ec48c..df9b7818 100644 --- a/Interfaces/English.lproj/DatabaseServerVariables.xib +++ b/Interfaces/English.lproj/DatabaseServerVariables.xib @@ -351,7 +351,7 @@ YES 342884416 - 268567552 + 268567616 Filter diff --git a/Interfaces/English.lproj/ExportDialog.xib b/Interfaces/English.lproj/ExportDialog.xib index 1eea6eab..5275ec30 100644 --- a/Interfaces/English.lproj/ExportDialog.xib +++ b/Interfaces/English.lproj/ExportDialog.xib @@ -1629,7 +1629,7 @@ YES -1804599231 - -1874721792 + -1874721728 @@ -2175,7 +2175,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 YES 342884417 - 132096 + 132160 , @@ -2278,7 +2278,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 YES 342884417 - 132096 + 132160 " @@ -2363,7 +2363,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 YES 342884417 - 132096 + 132160 \ @@ -2466,7 +2466,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 YES 342884417 - 132096 + 132160 \n @@ -2610,7 +2610,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 YES -1804599231 - 272761856 + 272761920 @@ -2672,7 +2672,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 YES -1267728319 - 272761856 + 272761920 diff --git a/Interfaces/English.lproj/FieldEditorSheet.xib b/Interfaces/English.lproj/FieldEditorSheet.xib index 6d8c7119..f9a9fc84 100644 --- a/Interfaces/English.lproj/FieldEditorSheet.xib +++ b/Interfaces/English.lproj/FieldEditorSheet.xib @@ -2374,7 +2374,7 @@ YES -1804599231 - 71566336 + 71566400 @@ -2398,7 +2398,7 @@ YES -1804074943 - 71566336 + 71566400 @@ -2435,7 +2435,7 @@ YES -2075131840 - 71566336 + 71566400 diff --git a/Interfaces/English.lproj/ImportAccessory.xib b/Interfaces/English.lproj/ImportAccessory.xib index 2ddd8d6b..4f1d47c9 100644 --- a/Interfaces/English.lproj/ImportAccessory.xib +++ b/Interfaces/English.lproj/ImportAccessory.xib @@ -107,7 +107,7 @@ YES 342884417 - 132352 + 132416 " @@ -255,7 +255,7 @@ YES 342884417 - 132352 + 132416 \ or " @@ -363,7 +363,7 @@ YES 342884417 - 132352 + 132416 \n @@ -471,7 +471,7 @@ YES 342884417 - 132352 + 132416 , diff --git a/Interfaces/English.lproj/IndexesView.xib b/Interfaces/English.lproj/IndexesView.xib index 0de04c01..5855753f 100644 --- a/Interfaces/English.lproj/IndexesView.xib +++ b/Interfaces/English.lproj/IndexesView.xib @@ -160,7 +160,7 @@ YES -1267728319 - 4326400 + 4326464 PRIMARY optional @@ -676,7 +676,7 @@ YES -1804599231 - -1874721792 + -1874721728 diff --git a/Interfaces/English.lproj/Navigator.xib b/Interfaces/English.lproj/Navigator.xib index 0ae61c2c..505724bb 100644 --- a/Interfaces/English.lproj/Navigator.xib +++ b/Interfaces/English.lproj/Navigator.xib @@ -93,7 +93,7 @@ YES 342884416 - 268600320 + 268600384 YES diff --git a/Interfaces/English.lproj/Preferences.xib b/Interfaces/English.lproj/Preferences.xib index 28387db8..7b3a337f 100644 --- a/Interfaces/English.lproj/Preferences.xib +++ b/Interfaces/English.lproj/Preferences.xib @@ -187,7 +187,7 @@ YES -1804599231 - 4326400 + 4326464 @@ -1207,7 +1207,7 @@ YES -1804599231 - 138413056 + 138413120 @@ -1781,7 +1781,7 @@ YES -1804599232 - 71304192 + 71304256 @@ -1857,7 +1857,7 @@ YES -1804599231 - 272630784 + 272630848 NULL @@ -2855,7 +2855,7 @@ AQAAAAA YES -1804599231 - -1874852864 + -1874852800 @@ -3031,7 +3031,7 @@ AQAAAAA YES -1804599231 - -2076048384 + -2076048320 @@ -3413,7 +3413,7 @@ AQAAAAA YES -1804599231 - 71435264 + 71435328 @@ -3762,7 +3762,7 @@ AQAAAAA YES -1538260928 - 71435264 + 71435328 @@ -3902,7 +3902,7 @@ AQAAAAA YES -1538260928 - -2076048384 + -2076048320 0.1 @@ -4139,7 +4139,7 @@ AQAAAAA YES -1804599231 - 272630784 + 272630848 System default diff --git a/Interfaces/English.lproj/QueryFavoriteManager.xib b/Interfaces/English.lproj/QueryFavoriteManager.xib index 336aaad1..5a92ba6a 100644 --- a/Interfaces/English.lproj/QueryFavoriteManager.xib +++ b/Interfaces/English.lproj/QueryFavoriteManager.xib @@ -926,7 +926,7 @@ YES -1804599231 - 272761856 + 272761920 @@ -994,7 +994,7 @@ YES -1804599231 - 272761856 + 272761920 [no selection] diff --git a/Interfaces/English.lproj/SSHQuestionDialog.xib b/Interfaces/English.lproj/SSHQuestionDialog.xib index f02f07dc..2460cc53 100644 --- a/Interfaces/English.lproj/SSHQuestionDialog.xib +++ b/Interfaces/English.lproj/SSHQuestionDialog.xib @@ -344,7 +344,7 @@ YES 342884416 - 272630784 + 272630848 diff --git a/Interfaces/English.lproj/SaveSPFAccessory.xib b/Interfaces/English.lproj/SaveSPFAccessory.xib index f9150a70..7e67bd64 100644 --- a/Interfaces/English.lproj/SaveSPFAccessory.xib +++ b/Interfaces/English.lproj/SaveSPFAccessory.xib @@ -156,7 +156,7 @@ YES 342884416 - 272761856 + 272761920 YES diff --git a/Interfaces/English.lproj/UserManagerView.xib b/Interfaces/English.lproj/UserManagerView.xib index 9939ae49..5d728a37 100644 --- a/Interfaces/English.lproj/UserManagerView.xib +++ b/Interfaces/English.lproj/UserManagerView.xib @@ -574,7 +574,7 @@ YES -1804599231 - 272630784 + 272630848 @@ -606,7 +606,7 @@ YES 342884416 - 272630784 + 272630848 @@ -2066,7 +2066,7 @@ YES -1804599231 - 272630784 + 272630848 @@ -2131,7 +2131,7 @@ YES -1804599231 - 272630784 + 272630848 @@ -2187,7 +2187,7 @@ YES -1804599231 - 272630784 + 272630848 -- cgit v1.2.3 From 0b9fb7e62aeba608081eb3ca1bc52dc7020c8add Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 14 Nov 2015 02:23:22 +0100 Subject: * Make a difficult mutex check a bit easier to read and hopefully a bit safer, too * Reorder some code * Replace some duplicate code with goto * Simplify some if's Maybe this also has some influence on our current top crash ("Attempted to connect a connection that is not disconnected") --- Source/SPConstants.h | 9 ++ Source/SPDatabaseStructure.h | 1 - Source/SPDatabaseStructure.m | 366 +++++++++++++++++++++---------------------- 3 files changed, 186 insertions(+), 190 deletions(-) diff --git a/Source/SPConstants.h b/Source/SPConstants.h index b236cda9..67d1a91f 100644 --- a/Source/SPConstants.h +++ b/Source/SPConstants.h @@ -682,3 +682,12 @@ typedef NSUInteger NSCellHitResult; // See http://stackoverflow.com/questions/4415524 #define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x]))))) + +// This definition is mostly for legibility +#ifndef ESUCCESS + #define ESUCCESS 0 +#else + #if ESUCCESS != 0 + #error 'ESUCCESS' must be defined as zero! + #endif +#endif diff --git a/Source/SPDatabaseStructure.h b/Source/SPDatabaseStructure.h index cfc0b3eb..05ae9ecc 100644 --- a/Source/SPDatabaseStructure.h +++ b/Source/SPDatabaseStructure.h @@ -51,7 +51,6 @@ // Setup and teardown - (id)initWithDelegate:(SPDatabaseDocument *)theDelegate; - (void)setConnectionToClone:(SPMySQLConnection *)aConnection; -- (void)destroy:(NSNotification *)notification; // Information - (SPMySQLConnection *)connection; diff --git a/Source/SPDatabaseStructure.m b/Source/SPDatabaseStructure.m index 6644e922..e11959b1 100644 --- a/Source/SPDatabaseStructure.m +++ b/Source/SPDatabaseStructure.m @@ -39,9 +39,16 @@ @interface SPDatabaseStructure (Private_API) +- (void)_destroy:(NSNotification *)notification; + - (void)_updateGlobalVariablesWithStructure:(NSDictionary *)aStructure keys:(NSArray *)theKeys; - (void)_cloneConnectionFromConnection:(SPMySQLConnection *)aConnection; -- (BOOL)_ensureConnection; +- (BOOL)_ensureConnectionUnsafe; // Use _checkConnection instead, where possible + +- (void)_addToListAndWaitForFrontCancellingOtherThreads:(BOOL)killOthers; +- (void)_removeThreadFromList; +- (void)_cancelAllThreadsAndWait; +- (BOOL)_checkConnection; @end @@ -81,9 +88,9 @@ allKeysofDbStructure = [[NSMutableArray alloc] initWithCapacity:20]; [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(destroy:) - name:SPDocumentWillCloseNotification - object:delegate]; + selector:@selector(_destroy:) + name:SPDocumentWillCloseNotification + object:delegate]; // Set up the connection, thread management and data locks pthread_mutex_init(&threadManagementLock, NULL); @@ -108,40 +115,16 @@ object:aConnection]; } -/** - * Ensure that processing is completed. - */ -- (void)destroy:(NSNotification *)notification -{ - delegate = nil; - - // Ensure all the retrieval threads have ended - pthread_mutex_lock(&threadManagementLock); - - if ([structureRetrievalThreads count]) { - for (NSThread *eachThread in structureRetrievalThreads) - { - [eachThread cancel]; - } - - while ([structureRetrievalThreads count]) - { - pthread_mutex_unlock(&threadManagementLock); - usleep(100000); - pthread_mutex_lock(&threadManagementLock); - } - } - - pthread_mutex_unlock(&threadManagementLock); - -} - #pragma mark - #pragma mark Information - (SPMySQLConnection *)connection { - return mySQLConnection; + // this much is needed to make the accessor atomic and thread-safe + pthread_mutex_lock(&connectionCheckLock); + SPMySQLConnection *c = [mySQLConnection retain]; + pthread_mutex_unlock(&connectionCheckLock); + return [c autorelease]; } #pragma mark - @@ -165,43 +148,14 @@ NSAutoreleasePool *queryPool = [[NSAutoreleasePool alloc] init]; BOOL structureWasUpdated = NO; - // Lock the management lock - pthread_mutex_lock(&threadManagementLock); - - // If 'cancelQuerying' is set try to interrupt any current querying - if (userInfo && [userInfo objectForKey:@"cancelQuerying"]) { - for (NSThread *eachThread in structureRetrievalThreads) { - [eachThread cancel]; - } - } - - // Add this thread to the group - [structureRetrievalThreads addObject:[NSThread currentThread]]; - - // Only allow one request to be running against the server at any one time, to prevent - // escessive server i/o or slowdown. Loop until this is the first thread in the array - while ([structureRetrievalThreads objectAtIndex:0] != [NSThread currentThread]) { - if ([[NSThread currentThread] isCancelled]) { - [structureRetrievalThreads removeObject:[NSThread currentThread]]; - pthread_mutex_unlock(&threadManagementLock); - [queryPool release]; - return; - } - - pthread_mutex_unlock(&threadManagementLock); - usleep(1000000); - pthread_mutex_lock(&threadManagementLock); - } - pthread_mutex_unlock(&threadManagementLock); + [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. +#warning Should not set delegate as the notification source object [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureIsUpdating" object:delegate]; - NSString *connectionID; - if([delegate respondsToSelector:@selector(connectionID)]) - connectionID = [NSString stringWithString:[delegate connectionID]]; - else - connectionID = @"_"; + NSString *connectionID = ([delegate respondsToSelector:@selector(connectionID)])? [NSString stringWithString:[delegate connectionID]] : @"_"; // Re-init with already cached data from navigator controller NSMutableDictionary *queriedStructure = [NSMutableDictionary dictionary]; @@ -242,33 +196,31 @@ } } - NSString *currentDatabase = nil; - if ([delegate respondsToSelector:@selector(database)]) - currentDatabase = [delegate database]; + 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; // If no database is selected, no need to check further - if(!currentDatabase || (currentDatabase && ![currentDatabase length])) { + if(![currentDatabase length]) { shouldQueryStructure = NO; - + } // Otherwise, build up the schema key for the database to be retrieved. - } else { + else { db_id = [NSString stringWithFormat:@"%@%@%@", connectionID, SPUniqueSchemaDelimiter, currentDatabase]; // Check to see if a cache already exists for the database. - if ([queriedStructure objectForKey:db_id] && [[queriedStructure objectForKey:db_id] isKindOfClass:[NSDictionary class]]) { + 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 isEqualToString:@"mysql"] || [currentDatabase isEqualToString:@"information_schema"] || [currentDatabase isEqualToString:@"performance_schema"]) { + 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 == nil || ![userInfo objectForKey:@"forceUpdate"] || ![[userInfo objectForKey:@"forceUpdate"] boolValue]) { + else if (![[userInfo objectForKey:@"forceUpdate"] boolValue]) { shouldQueryStructure = NO; } } @@ -276,20 +228,7 @@ // If it has been determined that no new structure needs to be retrieved, clean up and return. if (!shouldQueryStructure) { - - // Update the global variables - [self _updateGlobalVariablesWithStructure:queriedStructure keys:queriedStructureKeys]; - - if (structureWasUpdated) { - [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureWasUpdated" object:delegate]; - } - - pthread_mutex_lock(&threadManagementLock); - [structureRetrievalThreads removeObject:[NSThread currentThread]]; - pthread_mutex_unlock(&threadManagementLock); - - [queryPool release]; - return; + goto update_globals_and_cleanup; } // Retrieve the tables and views for this database from SPTablesList @@ -313,19 +252,14 @@ if ([tablesAndViews count] > 2000) { NSLog(@"%lu items in database %@. Only 2000 items can be parsed. Stopped parsing.", (unsigned long)[tablesAndViews count], currentDatabase); - pthread_mutex_lock(&threadManagementLock); - [structureRetrievalThreads removeObject:[NSThread currentThread]]; - pthread_mutex_unlock(&threadManagementLock); - - [queryPool release]; - return; + goto cleanup_thread_and_pool; } // 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 && [userInfo objectForKey:@"affectedItem"]) { + if([userInfo objectForKey:@"affectedItem"]) { affectedItem = [userInfo objectForKey:@"affectedItem"]; if([userInfo objectForKey:@"affectedItemType"]) affectedItemType = [[userInfo objectForKey:@"affectedItemType"] intValue]; @@ -342,8 +276,6 @@ [queriedStructure setObject:[NSMutableDictionary dictionary] forKey:db_id]; NSMutableDictionary *databaseStructure = [queriedStructure objectForKey:db_id]; - NSString *currentDatabaseEscaped = [currentDatabase stringByReplacingOccurrencesOfString:@"`" withString:@"``"]; - NSUInteger uniqueCounter = 0; // used to make field data unique SPMySQLResult *theResult; @@ -353,48 +285,15 @@ // Extract the name NSString *aTableName = [aTableDict objectForKey:@"name"]; - if(!aTableName) continue; - if(![aTableName isKindOfClass:[NSString class]]) continue; - if(![aTableName length]) continue; - - BOOL cancelThread = NO; + if(![aTableName isKindOfClass:[NSString class]] || ![aTableName length]) continue; - // If the thread has been cancelled, abort without saving - if ([[NSThread currentThread] isCancelled]) cancelThread = YES; - - // Check connection state before use - while (!cancelThread && pthread_mutex_trylock(&connectionCheckLock)) { - usleep(100000); - if ([[NSThread currentThread] isCancelled]) { - cancelThread = YES; - break; - } - } - - if (cancelThread) { - pthread_mutex_trylock(&connectionCheckLock); - pthread_mutex_unlock(&connectionCheckLock); - pthread_mutex_lock(&threadManagementLock); - [structureRetrievalThreads removeObject:[NSThread currentThread]]; - pthread_mutex_unlock(&threadManagementLock); - - [queryPool release]; - return; - } - - if (![self _ensureConnection]) { - pthread_mutex_unlock(&connectionCheckLock); - pthread_mutex_lock(&threadManagementLock); - [structureRetrievalThreads removeObject:[NSThread currentThread]]; - pthread_mutex_unlock(&threadManagementLock); - - [queryPool release]; - return; + // check the connection. + if(![self _checkConnection]) { + goto cleanup_thread_and_pool; } - pthread_mutex_unlock(&connectionCheckLock); // Retrieve the column details - theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW FULL COLUMNS FROM `%@` FROM `%@`", [aTableName stringByReplacingOccurrencesOfString:@"`" withString:@"``"], currentDatabaseEscaped]]; + theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW FULL COLUMNS FROM %@ FROM %@", [aTableName backtickQuotedString], [currentDatabase backtickQuotedString]]]; if (!theResult) { continue; } @@ -406,8 +305,8 @@ [queriedStructureKeys addObject:table_id]; // Add a mutable dictionary to the structure and store a reference - [databaseStructure setObject:[NSMutableDictionary dictionary] forKey:table_id]; - NSMutableDictionary *tableStructure = [databaseStructure objectForKey:table_id]; + NSMutableDictionary *tableStructure = [NSMutableDictionary dictionary]; + [databaseStructure setObject:tableStructure forKey:table_id]; // Loop through the fields, extracting details for each for (NSArray *row in theResult) { @@ -445,34 +344,9 @@ // If the MySQL version is higher than 5, also retrieve function/procedure details via the information_schema table if ([mySQLConnection serverMajorVersion] >= 5) { - BOOL cancelThread = NO; - - if ([[NSThread currentThread] isCancelled]) cancelThread = YES; - - // Check connection state before use - while (!cancelThread && pthread_mutex_trylock(&connectionCheckLock)) { - usleep(100000); - if ([[NSThread currentThread] isCancelled]) { - cancelThread = YES; - break; - } - } - - if (!cancelThread) { - if (![self _ensureConnection]) cancelThread = YES; - pthread_mutex_unlock(&connectionCheckLock); - }; - - // Return if the thread is due to be cancelled - if (cancelThread) { - pthread_mutex_trylock(&connectionCheckLock); - pthread_mutex_unlock(&connectionCheckLock); - pthread_mutex_lock(&threadManagementLock); - [structureRetrievalThreads removeObject:[NSThread currentThread]]; - pthread_mutex_unlock(&threadManagementLock); - - [queryPool release]; - return; + // check the connection. + if(![self _checkConnection]) { + goto cleanup_thread_and_pool; } // Retrieve the column details (only those we need so we don't fetch the whole function body which might be huge) @@ -507,17 +381,19 @@ } } +update_globals_and_cleanup: // Update the global variables [self _updateGlobalVariablesWithStructure:queriedStructure keys:queriedStructureKeys]; - // Notify that the structure querying has been performed - [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureWasUpdated" object:delegate]; + if(structureWasUpdated) { + // Notify that the structure querying has been performed +#warning Should not set delegate as the notification source object + [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureWasUpdated" object:delegate]; + } +cleanup_thread_and_pool: // Remove this thread from the processing stack - pthread_mutex_lock(&threadManagementLock); - [structureRetrievalThreads removeObject:[NSThread currentThread]]; - pthread_mutex_unlock(&threadManagementLock); - + [self _removeThreadFromList]; [queryPool release]; } @@ -574,7 +450,7 @@ { [[NSNotificationCenter defaultCenter] removeObserver:self]; - [self destroy:nil]; + [self _destroy:nil]; SPClear(structureRetrievalThreads); pthread_mutex_destroy(&threadManagementLock); @@ -595,6 +471,17 @@ @implementation SPDatabaseStructure (Private_API) +/** + * Ensure that processing is completed. + */ +- (void)_destroy:(NSNotification *)notification +{ + delegate = nil; + + // Ensure all the retrieval threads have ended + [self _cancelAllThreadsAndWait]; +} + /** * Update the global variables, using the data lock for multithreading safety. */ @@ -625,20 +512,9 @@ // If a connection is already set, ensure it's idle before releasing it if (mySQLConnection) { - pthread_mutex_lock(&threadManagementLock); - if ([structureRetrievalThreads count]) { - for (NSThread *eachThread in structureRetrievalThreads) { - [eachThread cancel]; - } - while ([structureRetrievalThreads count]) { - pthread_mutex_unlock(&threadManagementLock); - usleep(100000); - pthread_mutex_lock(&threadManagementLock); - } - } - pthread_mutex_unlock(&threadManagementLock); + [self _cancelAllThreadsAndWait]; - SPClear(mySQLConnection); + [mySQLConnection autorelease], mySQLConnection = nil; // note: aConnection could be == mySQLConnection } // Create a copy of the supplied connection @@ -648,14 +524,20 @@ [mySQLConnection setDelegate:self]; // Trigger the connection - [self _ensureConnection]; + [self _ensureConnectionUnsafe]; pthread_mutex_unlock(&connectionCheckLock); [connectionPool drain]; } -- (BOOL)_ensureConnection +/** + * Check if the MySQL connection is still available (reconnecting if possible) + * + * **Unsafe** means this function holds no lock on connectionCheckLock. + * You MUST obtain that lock before calling this method! + */ +- (BOOL)_ensureConnectionUnsafe { if (!mySQLConnection || !delegate) return NO; @@ -679,4 +561,110 @@ return YES; } +/** + * Wait until either + * * there are no other threads of this object doing structure retrievals + * * or the current thread was cancelled + * + * @param killOthers Whether to cancel all other running threads first + */ +- (void)_addToListAndWaitForFrontCancellingOtherThreads:(BOOL)killOthers +{ + // Lock the management lock + pthread_mutex_lock(&threadManagementLock); + + // If 'cancelQuerying' is set try to interrupt any current querying + if (killOthers) { + for (NSThread *eachThread in structureRetrievalThreads) { + [eachThread cancel]; + } + } + + // Add this thread to the group + [structureRetrievalThreads addObject:[NSThread currentThread]]; + + // Only allow one request to be running against the server at any one time, to prevent + // escessive server i/o or slowdown. Loop until this is the first thread in the array + while ([structureRetrievalThreads objectAtIndex:0] != [NSThread currentThread]) { + pthread_mutex_unlock(&threadManagementLock); + + if ([[NSThread currentThread] isCancelled]) return; + + usleep(1000000); + + pthread_mutex_lock(&threadManagementLock); + } + pthread_mutex_unlock(&threadManagementLock); +} + +/** + * Remove the current thread from the list of running threads + */ +- (void)_removeThreadFromList +{ + pthread_mutex_lock(&threadManagementLock); + [structureRetrievalThreads removeObject:[NSThread currentThread]]; + pthread_mutex_unlock(&threadManagementLock); +} + +/** + * Cancel all running threads and wait until they have exited + */ +- (void)_cancelAllThreadsAndWait +{ + pthread_mutex_lock(&threadManagementLock); + + for (NSThread *eachThread in structureRetrievalThreads) { + [eachThread cancel]; + } + + while ([structureRetrievalThreads count]) { + pthread_mutex_unlock(&threadManagementLock); + usleep(100000); + pthread_mutex_lock(&threadManagementLock); + } + + pthread_mutex_unlock(&threadManagementLock); +} + +/** + * @return YES if the connection is available + * NO if either the connection failed, or this thread was cancelled + */ +- (BOOL)_checkConnection +{ + while (1) { + // we can fail to get the lock for two reasons + // 1. another thread is running this code + // => a regular pthread_mutex_lock() would be fine, it would succeed once the other thread is done + // 2. another thread is running _cloneConnectionFromConnection + // => that method will not let go of the lock until all other threads have exited. + // Since we are an "other thread", calling pthread_mutex_lock() would result in a deadlock! + // That is why we try to get the lock and if that fails check if we are cancelled (indicating the 2. case). + if(pthread_mutex_trylock(&connectionCheckLock) == ESUCCESS) { + break; + } + + // If this thread has been cancelled, abort + if([[NSThread currentThread] isCancelled]) { + return NO; + } + + usleep(100000); + } + // we now hold the connectionCheckLock! + + BOOL cancelThread = ([[NSThread currentThread] isCancelled]); + BOOL connected = NO; + + if (!cancelThread) { + // Check connection state before use + connected = [self _ensureConnectionUnsafe]; + } + + pthread_mutex_unlock(&connectionCheckLock); + + return connected; // cancelThread → ¬connected +} + @end -- cgit v1.2.3 From c7b56fe8818ea02de033193318c544eea5abf024 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 15 Nov 2015 02:48:51 +0100 Subject: * Add QuickLook support for "export settings" files * Add MGTemplateEngine to the QL plugin * Reorder the generator code a bit, so it's not one monolithic function * Add constants for some other sfp format types --- .../SPQLPluginExportSettingsTemplate.html | 77 ++ Source/GeneratePreviewForURL.m | 967 ++++++++++++--------- Source/SPConstants.h | 6 + Source/SPConstants.m | 6 + Source/SPContentFilterManager.m | 2 +- Source/SPDatabaseDocument.m | 6 +- Source/SPExportSettingsPersistence.m | 4 +- Source/SPQueryFavoriteManager.m | 2 +- Source/main.c | 2 +- sequel-pro.xcodeproj/project.pbxproj | 46 + 10 files changed, 686 insertions(+), 432 deletions(-) create mode 100644 Resources/English.lproj/SPQLPluginExportSettingsTemplate.html diff --git a/Resources/English.lproj/SPQLPluginExportSettingsTemplate.html b/Resources/English.lproj/SPQLPluginExportSettingsTemplate.html new file mode 100644 index 00000000..29a2e9d7 --- /dev/null +++ b/Resources/English.lproj/SPQLPluginExportSettingsTemplate.html @@ -0,0 +1,77 @@ + + + + + + + +
+ + + + + + + + +
+
+ Sequel Pro Saved Export Settings
+ {{ filename }} +


+
+ + + + + + + {% if customFilename %} + + + + + {% /if %} + + + + + + + + + + + + + + + +
Path: {{ exportPath }}
Filename: {{ part.name }}{{ part.name }}
Format: {{ exportType }}
Compression: {{ compressionFormat }}
Input data: {{ exportSource }}
Low Memory:

(format specific settings not shown)
+
+
+ + diff --git a/Source/GeneratePreviewForURL.m b/Source/GeneratePreviewForURL.m index 2209f746..53087eb0 100644 --- a/Source/GeneratePreviewForURL.m +++ b/Source/GeneratePreviewForURL.m @@ -36,11 +36,29 @@ #import "SPDataAdditions.h" #import "SPEditorTokens.h" #import "SPSyntaxParser.h" +#import "MGTemplateEngine.h" +#import "ICUTemplateMatcher.h" OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options, CGSize maxSize); OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options); void CancelPreviewGeneration(void* thisInterface, QLPreviewRequestRef preview); +static NSString *PreviewForSPF(NSURL *myURL, NSInteger *previewHeight); +static NSString *PreviewForConnectionSPF(NSDictionary *spf, NSURL *myURL, NSInteger *previewHeight); +static NSString *PreviewForContentFiltersSPF(NSDictionary *spf, NSURL *myURL, NSInteger *previewHeight); +static NSString *PreviewForQueryFavoritesSPF(NSDictionary *spf, NSURL *myURL, NSInteger *previewHeight); +static NSString *PreviewForExportSettingsSPF(NSDictionary *spf, NSURL *myURL, NSInteger *previewHeight); + +static NSString *PreviewForSPFS(NSURL *myURL,NSInteger *previewHeight); +static NSString *PreviewForSQL(NSURL *myURL, NSInteger *previewHeight, QLPreviewRequestRef preview); + +static inline NSString *PathForHTMLResource(NSString *named) +{ + return [[NSBundle bundleWithIdentifier:@"com.sequelpro.SequelPro.qlgenerator"] pathForResource:named ofType:@"html"]; +} + +#pragma mark - + /* ----------------------------------------------------------------------------- Generate a preview for file @@ -55,467 +73,570 @@ OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, NSURL *myURL = (NSURL *)url; NSString *urlExtension = [[[myURL path] pathExtension] lowercaseString]; - NSError *templateReadError = nil; - - NSString *html = @""; - NSString *template = nil; - + NSString *html = nil; NSInteger previewHeight = 280; - NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[myURL path] error:nil]; - // Dispatch different file extensions if([urlExtension isEqualToString:@"spf"]) { - - NSError *readError = nil; - NSString *convError = nil; - NSPropertyListFormat format; - NSDictionary *spf = nil; - - // Get spf data as dictionary - NSData *pData = [NSData dataWithContentsOfFile:[myURL path] options:NSUncachedRead error:&readError]; - spf = [[NSPropertyListSerialization propertyListFromData:pData - mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&convError] retain]; - - if(!spf || readError != nil || [convError length] || !(format == NSPropertyListXMLFormat_v1_0 || format == NSPropertyListBinaryFormat_v1_0)) { - if(spf) SPClear(spf); - if(pool) SPClear(pool); - return noErr; + 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]]; + +#warning Shouldn't that be "> 1"? + if([iconImages count] > 0) + 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." + 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]; + } - // Dispatch different spf formats - if([[spf objectForKey:SPFFormatKey] isEqualToString:@"connection"]) { - template = [NSString stringWithContentsOfFile:[[NSBundle bundleWithIdentifier:@"com.sequelpro.SequelPro.qlgenerator"] pathForResource:@"SPQLPluginConnectionTemplate" ofType:@"html"] - encoding:NSUTF8StringEncoding error:&templateReadError]; - - if (template == nil || ![template length] || templateReadError != nil) { - if(spf) SPClear(spf); - if(pool) SPClear(pool); - return noErr; - } - - NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; - [dateFormatter setTimeStyle:NSDateFormatterShortStyle]; - [dateFormatter setDateStyle:NSDateFormatterMediumStyle]; - [dateFormatter setLocale:[NSLocale currentLocale]]; - - NSString *name = @"••••"; - NSString *host = @"••••"; - NSString *user = @"••••"; - NSString *database = @"••••"; - NSString *autoConnect = ([[spf objectForKey:@"auto_connect"] boolValue]) ? @"checked" : @""; + [pool release]; - if([[spf objectForKey:@"data"] isKindOfClass:[NSDictionary class]]) { - if([[spf objectForKey:@"data"] objectForKey:@"connection"] && [[[spf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"name"]) - name = [[[spf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"name"]; - else - name = @""; - if([[spf objectForKey:@"data"] objectForKey:@"connection"] && [[[spf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"host"]) - host = [[[spf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"host"]; - else - host = @""; - if([[spf objectForKey:@"data"] objectForKey:@"connection"] && [[[spf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"user"]) - user = [[[spf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"user"]; - else - user = @""; - if([[spf objectForKey:@"data"] objectForKey:@"connection"] && [[[spf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"database"]) - database = [[[spf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"database"]; - else - database = @""; - } + return noErr; +} - // compose the html - html = [NSString stringWithFormat:template, - [spf objectForKey:@"rdbms_type"], - [spf objectForKey:@"rdbms_version"], - [name stringByReplacingOccurrencesOfString:@" " withString:@" "], - [host stringByReplacingOccurrencesOfString:@" " withString:@" "], - [user stringByReplacingOccurrencesOfString:@" " withString:@" "], - [database stringByReplacingOccurrencesOfString:@" " withString:@" "], - [NSString stringForByteSize:[[fileAttributes objectForKey:NSFileSize] longLongValue]], - [dateFormatter stringFromDate:[fileAttributes fileModificationDate]], - autoConnect - ]; +void CancelPreviewGeneration(void* thisInterface, QLPreviewRequestRef preview) +{ + // Implement only if supported +} - [dateFormatter release]; +#pragma mark - + +NSString *PreviewForSPF(NSURL *myURL, NSInteger *previewHeight) { + + NSError *readError = nil; + NSString *convError = nil; + NSPropertyListFormat format; + NSString *html = nil; + + // Get spf data as dictionary + NSData *pData = [NSData dataWithContentsOfFile:[myURL path] options:NSUncachedRead error:&readError]; + NSDictionary *spf = [[NSPropertyListSerialization propertyListFromData:pData + mutabilityOption:NSPropertyListImmutable + format:&format + errorDescription:&convError] retain]; + + if(!readError && spf && ![convError length] && (format == NSPropertyListXMLFormat_v1_0 || format == NSPropertyListBinaryFormat_v1_0)) { + NSString *spfType = [spf objectForKey:SPFFormatKey]; + NSString *(*fp)(NSDictionary *spf,NSURL *myURL, NSInteger *previewHeight) = NULL; + // Dispatch different spf formats + if([spfType isEqualToString:SPFConnectionContentType]) { + fp = &PreviewForConnectionSPF; } - - else if([[spf objectForKey:SPFFormatKey] isEqualToString:@"content filters"]) { - - template = [NSString stringWithContentsOfFile:[[NSBundle bundleWithIdentifier:@"com.sequelpro.SequelPro.qlgenerator"] pathForResource:@"SPQLPluginContentFiltersTemplate" ofType:@"html"] - encoding:NSUTF8StringEncoding error:&templateReadError]; - - if (template == nil || ![template length] || templateReadError != nil) { - if(spf) SPClear(spf); - if(pool) SPClear(pool); - return noErr; - } - // compose the html - html = [NSString stringWithFormat:template, - [NSString stringWithContentsOfFile:[myURL path] encoding:NSUTF8StringEncoding error:nil] - ]; + else if([spfType isEqualToString:SPFContentFiltersContentType]) { + fp = &PreviewForContentFiltersSPF; } - - else if([[spf objectForKey:SPFFormatKey] isEqualToString:@"query favorites"]) { - - template = [NSString stringWithContentsOfFile:[[NSBundle bundleWithIdentifier:@"com.sequelpro.SequelPro.qlgenerator"] pathForResource:@"SPQLPluginQueryFavoritesTemplate" ofType:@"html"] - encoding:NSUTF8StringEncoding error:&templateReadError]; - - if (template == nil || ![template length] || templateReadError != nil) { - if(spf) SPClear(spf); - if(pool) SPClear(pool); - return noErr; - } - // compose the html - html = [NSString stringWithFormat:template, - [NSString stringWithContentsOfFile:[myURL path] encoding:NSUTF8StringEncoding error:nil] - ]; + else if([spfType isEqualToString:SPFQueryFavoritesContentType]) { + fp = &PreviewForQueryFavoritesSPF; + } + else if([spfType isEqualToString:SPFExportSettingsContentType]) { + fp = &PreviewForExportSettingsSPF; + } + + if(fp) { + html = (*fp)(spf,myURL,previewHeight); } + } - [spf release]; + [spf release]; + + return html; +} +NSString *PreviewForConnectionSPF(NSDictionary *spf, NSURL *myURL, NSInteger *previewHeight) +{ + NSError *templateReadError = nil; + NSString *template = [NSString stringWithContentsOfFile:PathForHTMLResource(@"SPQLPluginConnectionTemplate") + encoding:NSUTF8StringEncoding error:&templateReadError]; + + if (templateReadError != nil || ![template length]) { + return nil; + } + + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setTimeStyle:NSDateFormatterShortStyle]; + [dateFormatter setDateStyle:NSDateFormatterMediumStyle]; + [dateFormatter setLocale:[NSLocale currentLocale]]; + + NSString *name = @"••••"; + NSString *host = @"••••"; + NSString *user = @"••••"; + NSString *database = @"••••"; + NSString *autoConnect = ([[spf objectForKey:@"auto_connect"] boolValue]) ? @"checked" : @""; + + if([[spf objectForKey:@"data"] isKindOfClass:[NSDictionary class]]) { + if([[spf objectForKey:@"data"] objectForKey:@"connection"] && [[[spf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"name"]) + name = [[[spf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"name"]; + else + name = @""; + if([[spf objectForKey:@"data"] objectForKey:@"connection"] && [[[spf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"host"]) + host = [[[spf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"host"]; + else + host = @""; + if([[spf objectForKey:@"data"] objectForKey:@"connection"] && [[[spf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"user"]) + user = [[[spf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"user"]; + else + user = @""; + if([[spf objectForKey:@"data"] objectForKey:@"connection"] && [[[spf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"database"]) + database = [[[spf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"database"]; + else + database = @""; } + + NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[myURL path] error:nil]; + + // compose the html + NSString *html = [NSString stringWithFormat:template, + [spf objectForKey:@"rdbms_type"], + [spf objectForKey:@"rdbms_version"], + [name stringByReplacingOccurrencesOfString:@" " withString:@" "], + [host stringByReplacingOccurrencesOfString:@" " withString:@" "], + [user stringByReplacingOccurrencesOfString:@" " withString:@" "], + [database stringByReplacingOccurrencesOfString:@" " withString:@" "], + [NSString stringForByteSize:[[fileAttributes objectForKey:NSFileSize] longLongValue]], + [dateFormatter stringFromDate:[fileAttributes fileModificationDate]], + autoConnect + ]; + + [dateFormatter release]; + + return html; +} - else if([urlExtension isEqualToString:@"spfs"]) { +NSString *PreviewForContentFiltersSPF(NSDictionary *spf, NSURL *myURL, NSInteger *previewHeight) +{ + NSError *templateReadError = nil; + NSString *template = [NSString stringWithContentsOfFile:PathForHTMLResource(@"SPQLPluginContentFiltersTemplate") + encoding:NSUTF8StringEncoding error:&templateReadError]; + + if (templateReadError != nil || ![template length]) { + return nil; + } + + // compose the html + NSString *html = [NSString stringWithFormat:template, + [NSString stringWithContentsOfFile:[myURL path] encoding:NSUTF8StringEncoding error:nil] + ]; + + return html; +} - template = [NSString stringWithContentsOfFile:[[NSBundle bundleWithIdentifier:@"com.sequelpro.SequelPro.qlgenerator"] pathForResource:@"SPQLPluginConnectionBundleTemplate" ofType:@"html"] - encoding:NSUTF8StringEncoding error:&templateReadError]; +NSString *PreviewForQueryFavoritesSPF(NSDictionary *spf, NSURL *myURL, NSInteger *previewHeight) +{ + NSError *templateReadError = nil; + NSString *template = [NSString stringWithContentsOfFile:PathForHTMLResource(@"SPQLPluginQueryFavoritesTemplate") + encoding:NSUTF8StringEncoding error:&templateReadError]; + + if (templateReadError != nil || ![template length]) { + return nil; + } + + // compose the html + NSString *html = [NSString stringWithFormat:template, + [NSString stringWithContentsOfFile:[myURL path] encoding:NSUTF8StringEncoding error:nil] + ]; + + return html; +} - if (template == nil || ![template length] || templateReadError != nil) { - if(pool) SPClear(pool); - return noErr; +static inline void MapIf(NSDictionary *src,NSString *key,NSDictionary *map,NSMutableDictionary *dst) +{ + id srcObj, mappedObj; + + if((srcObj = [src objectForKey:key])) { + if((mappedObj = [map objectForKey:srcObj])) { + [dst setObject:mappedObj forKey:key]; } - - NSString *windowTemplate = [NSString stringWithContentsOfFile:[[NSBundle bundleWithIdentifier:@"com.sequelpro.SequelPro.qlgenerator"] pathForResource:@"SPQLPluginConnectionBundleWindowTemplate" ofType:@"html"] - encoding:NSUTF8StringEncoding error:&templateReadError]; - - if (windowTemplate == nil || ![windowTemplate length] || templateReadError != nil) { - if(pool) SPClear(pool); - return noErr; + else { + [dst setObject:srcObj forKey:key]; } + } +} - NSError *readError = nil; - NSString *convError = nil; - NSPropertyListFormat format; - NSDictionary *spf = nil; - - // Get info.plist data as dictionary - NSData *pData = [NSData dataWithContentsOfFile:[NSString stringWithFormat:@"%@/info.plist", [myURL path]] options:NSUncachedRead error:&readError]; - spf = [[NSPropertyListSerialization propertyListFromData:pData - mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&convError] retain]; +static inline void CopyIf(NSDictionary *src,NSString *key,NSMutableDictionary *dst) +{ + id srcObj; + if((srcObj = [src objectForKey:key])) { + [dst setObject:srcObj forKey:key]; + } +} - if(!spf || readError != nil || [convError length] || !(format == NSPropertyListXMLFormat_v1_0 || format == NSPropertyListBinaryFormat_v1_0)) { - if(spf) SPClear(spf); - if(pool) SPClear(pool); - return noErr; +NSString *PreviewForExportSettingsSPF(NSDictionary *spf, NSURL *myURL, NSInteger *previewHeight) +{ + NSError *templateReadError = nil; + NSString *template = [NSString stringWithContentsOfFile:PathForHTMLResource(@"SPQLPluginExportSettingsTemplate") + encoding:NSUTF8StringEncoding error:&templateReadError]; + + if (templateReadError != nil || ![template length]) { + return nil; + } + + NSMutableDictionary *vars = [NSMutableDictionary dictionary]; + [vars setObject:[[myURL path] lastPathComponent] forKey:@"filename"]; + CopyIf(spf,@"exportPath",vars); + NSArray *customFilename = [spf objectForKey:@"customFilename"]; + if([customFilename isKindOfClass:[NSArray class]]) { + NSMutableArray *items = [NSMutableArray array]; + for (id obj in customFilename) { + if([obj isKindOfClass:[NSString class]]) + [items addObject:@{@"name":obj,@"isToken":@NO}]; + else if([obj isKindOfClass:[NSDictionary class]] && [obj objectForKey:@"tokenId"]) + [items addObject:@{@"name": [obj objectForKey:@"tokenId"] ,@"isToken":@YES}]; } + [vars setObject:items forKey:@"customFilename"]; + } + NSDictionary *types = @{ + @"SPSQLExport": @"SQL", + @"SPCSVExport": @"CSV", + @"SPXMLExport": @"XML", + @"SPDotExport": @"Dot", + }; + MapIf(spf, @"exportType", types, vars); + + NSDictionary *compression = @{ + @"SPNoCompression": NSLocalizedString(@"None", @"compression: none"), + @"SPGzipCompression": @"Gzip", + @"SPBzip2Compression": @"Bzip2", + }; + MapIf(spf, @"compressionFormat", compression, vars); + + NSDictionary *source = @{ + @"SPQueryExport": NSLocalizedString(@"Query results", @"export source"), + @"SPFilteredExport": NSLocalizedString(@"Filtered table content", @"export source"), + @"SPTableExport": NSLocalizedString(@"Database", @"export source"), + }; + MapIf(spf, @"exportSource", source, vars); + + CopyIf(spf, @"lowMemoryStreaming", vars); + + // compose the html + MGTemplateEngine *engine = [MGTemplateEngine templateEngine]; + [engine setMatcher:[ICUTemplateMatcher matcherWithTemplateEngine:engine]]; + + NSString *html = [engine processTemplate:template withVariables:vars]; + + return html; +} - NSMutableString *spfsHTML = [NSMutableString string]; - NSInteger connectionCounter = 0; - - NSArray *theWindows = [[[spf objectForKey:@"windows"] reverseObjectEnumerator] allObjects]; - for(NSDictionary *window in theWindows) { - - NSInteger tabCounter = 0; - NSInteger selectedTab = [[window objectForKey:@"selectedTabIndex"] integerValue]; - - [spfsHTML appendString:@""]; - - NSArray *theTabs = [window objectForKey:@"tabs"]; - for(NSDictionary *tab in theTabs) { - - connectionCounter++; - - if(tabCounter == selectedTab) - [spfsHTML appendString:@""]; + } - - if(connectionCounter > 1) - previewHeight = 495; - - html = [NSString stringWithFormat:template, + + [spfsHTML appendString:@"
"]; - else - [spfsHTML appendString:@"
"]; - - NSString *spfPath = @""; - NSString *spfPathDisplay = @""; - if([[tab objectForKey:@"isAbsolutePath"] boolValue]) { - spfPath = [tab objectForKey:@"path"]; - if([spfPath hasPrefix:NSHomeDirectory()]) { - spfPathDisplay = [spfPath stringByReplacingOccurrencesOfString:NSHomeDirectory() withString:@"~"]; - } else { - spfPathDisplay = spfPath; - } - spfPathDisplay = [NSString stringWithFormat:@" (%@)", spfPathDisplay]; - +NSString *PreviewForSPFS(NSURL *myURL,NSInteger *previewHeight) +{ + NSError *templateReadError = nil; + NSString *template = [NSString stringWithContentsOfFile:PathForHTMLResource(@"SPQLPluginConnectionBundleTemplate") + encoding:NSUTF8StringEncoding error:&templateReadError]; + + if (templateReadError != nil || ![template length]) { + return nil; + } + + NSString *windowTemplate = [NSString stringWithContentsOfFile:PathForHTMLResource(@"SPQLPluginConnectionBundleWindowTemplate") + encoding:NSUTF8StringEncoding error:&templateReadError]; + + if (templateReadError != nil || ![windowTemplate length]) { + return nil; + } + + NSError *readError = nil; + NSString *convError = nil; + NSPropertyListFormat format; + NSDictionary *spf = nil; + + // Get info.plist data as dictionary + NSData *pData = [NSData dataWithContentsOfFile:[NSString stringWithFormat:@"%@/info.plist", [myURL path]] options:NSUncachedRead error:&readError]; + spf = [[NSPropertyListSerialization propertyListFromData:pData + mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&convError] retain]; + + if(!spf || readError != nil || [convError length] || !(format == NSPropertyListXMLFormat_v1_0 || format == NSPropertyListBinaryFormat_v1_0)) { + [spf release]; + return nil; + } + + NSMutableString *spfsHTML = [NSMutableString string]; + NSInteger connectionCounter = 0; + + NSArray *theWindows = [[[spf objectForKey:@"windows"] reverseObjectEnumerator] allObjects]; + for(NSDictionary *window in theWindows) { + + NSInteger tabCounter = 0; + NSInteger selectedTab = [[window objectForKey:@"selectedTabIndex"] integerValue]; + + [spfsHTML appendString:@""]; + + NSArray *theTabs = [window objectForKey:@"tabs"]; + for(NSDictionary *tab in theTabs) { + + connectionCounter++; + + if(tabCounter == selectedTab) + [spfsHTML appendString:@""]; - } - - [spfsHTML appendString:@"
"]; + else + [spfsHTML appendString:@"
"]; + + NSString *spfPath = @""; + NSString *spfPathDisplay = @""; + if([[tab objectForKey:@"isAbsolutePath"] boolValue]) { + spfPath = [tab objectForKey:@"path"]; + if([spfPath hasPrefix:NSHomeDirectory()]) { + spfPathDisplay = [spfPath stringByReplacingOccurrencesOfString:NSHomeDirectory() withString:@"~"]; } else { - spfPathDisplay = @""; - spfPath = [NSString stringWithFormat:@"%@/Contents/%@", [myURL path], [tab objectForKey:@"path"]]; + spfPathDisplay = spfPath; } - - if(spfPath == nil || ![spfPath length]) { - [spfsHTML appendString:@"    ∅"]; - continue; + spfPathDisplay = [NSString stringWithFormat:@" (%@)", spfPathDisplay]; + + } else { + spfPathDisplay = @""; + spfPath = [NSString stringWithFormat:@"%@/Contents/%@", [myURL path], [tab objectForKey:@"path"]]; + } + + if(spfPath == nil || ![spfPath length]) { + [spfsHTML appendString:@"    ∅"]; + continue; + } + // Get info.plist data as dictionary + NSDictionary *sessionSpf; + pData = [NSData dataWithContentsOfFile:spfPath options:NSUncachedRead error:&readError]; + sessionSpf = [[NSPropertyListSerialization propertyListFromData:pData + mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&convError] retain]; + + if(!sessionSpf || readError != nil || [convError length] || !(format == NSPropertyListXMLFormat_v1_0 || format == NSPropertyListBinaryFormat_v1_0)) { + [spfsHTML appendFormat:@"    %@ ∅", [tab objectForKey:@"path"]]; + } else { + + NSString *name = @"••••"; + NSString *host = @"••••"; + NSString *user = @"••••"; + NSString *database = @"••••"; + + if([[sessionSpf objectForKey:@"data"] isKindOfClass:[NSDictionary class]]) { + if([[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] && [[[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"name"]) + name = [[[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"name"]; + else + name = @""; + if([[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] && [[[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"host"]) + host = [[[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"host"]; + else + host = @""; + if([[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] && [[[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"user"]) + user = [[[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"user"]; + else + user = @""; + if([[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] && [[[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"database"]) + database = [[[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"database"]; + else + database = @""; } - // Get info.plist data as dictionary - NSDictionary *sessionSpf; - pData = [NSData dataWithContentsOfFile:spfPath options:NSUncachedRead error:&readError]; - sessionSpf = [[NSPropertyListSerialization propertyListFromData:pData - mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&convError] retain]; - - if(!sessionSpf || readError != nil || [convError length] || !(format == NSPropertyListXMLFormat_v1_0 || format == NSPropertyListBinaryFormat_v1_0)) { - [spfsHTML appendFormat:@"    %@ ∅", [tab objectForKey:@"path"]]; - } else { - - NSString *name = @"••••"; - NSString *host = @"••••"; - NSString *user = @"••••"; - NSString *database = @"••••"; - - if([[sessionSpf objectForKey:@"data"] isKindOfClass:[NSDictionary class]]) { - if([[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] && [[[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"name"]) - name = [[[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"name"]; - else - name = @""; - if([[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] && [[[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"host"]) - host = [[[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"host"]; - else - host = @""; - if([[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] && [[[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"user"]) - user = [[[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"user"]; - else - user = @""; - if([[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] && [[[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"database"]) - database = [[[sessionSpf objectForKey:@"data"] objectForKey:@"connection"] objectForKey:@"database"]; - else - database = @""; - } - - [spfsHTML appendFormat:windowTemplate, - [sessionSpf objectForKey:@"rdbms_type"], - [sessionSpf objectForKey:@"rdbms_version"], - [name stringByReplacingOccurrencesOfString:@" " withString:@" "], - spfPathDisplay, - [host stringByReplacingOccurrencesOfString:@" " withString:@" "], - [user stringByReplacingOccurrencesOfString:@" " withString:@" "], - [database stringByReplacingOccurrencesOfString:@" " withString:@" "] + + [spfsHTML appendFormat:windowTemplate, + [sessionSpf objectForKey:@"rdbms_type"], + [sessionSpf objectForKey:@"rdbms_version"], + [name stringByReplacingOccurrencesOfString:@" " withString:@" "], + spfPathDisplay, + [host stringByReplacingOccurrencesOfString:@" " withString:@" "], + [user stringByReplacingOccurrencesOfString:@" " withString:@" "], + [database stringByReplacingOccurrencesOfString:@" " withString:@" "] ]; - } - - tabCounter++; - - [spfsHTML appendString:@"

"]; - + + tabCounter++; + + [spfsHTML appendString:@"

"]; + + } + + if(connectionCounter > 1 && previewHeight != NULL) + *previewHeight = 495; + + NSString *html = [NSString stringWithFormat:template, connectionCounter, spfsHTML - ]; - + ]; + + [spf release]; + + return html; +} +NSString *PreviewForSQL(NSURL *myURL, NSInteger *previewHeight, QLPreviewRequestRef preview) +{ + NSError *templateReadError = nil; + NSString *template = [NSString stringWithContentsOfFile:PathForHTMLResource(@"SPQLPluginSQLTemplate") + encoding:NSUTF8StringEncoding error:&templateReadError]; + + if (templateReadError != nil || ![template length]) { + return nil; } - - else if([urlExtension isEqualToString:@"sql"]) { - - template = [NSString stringWithContentsOfFile:[[NSBundle bundleWithIdentifier:@"com.sequelpro.SequelPro.qlgenerator"] pathForResource:@"SPQLPluginSQLTemplate" ofType:@"html"] - encoding:NSUTF8StringEncoding error:&templateReadError]; - - if (template == nil || ![template length] || templateReadError != nil) { - if(pool) SPClear(pool); - return noErr; + + NSError *readError = nil; + + NSStringEncoding sqlEncoding = NSUTF8StringEncoding; + + NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[myURL path] error:nil]; + + if(!fileAttributes) return nil; + + NSNumber *filesize = [fileAttributes objectForKey:NSFileSize]; + NSUInteger kMaxSQLFileSize = (0.7f * 1024 * 1024); + + // compose the html and perform syntax highlighting + + // read the file and try to get a proper encoding + NSString *sqlText = [[NSString alloc] initWithContentsOfFile:[myURL path] encoding:sqlEncoding error:&readError]; + NSMutableString *sqlHTML = [[NSMutableString alloc] initWithCapacity:[sqlText length]]; + NSString *truncatedString = [[NSString alloc] init]; + + if(readError != nil) { + // cocoa tries to detect the encoding + sqlText = [[NSString alloc] initWithContentsOfFile:[myURL path] usedEncoding:&sqlEncoding error:&readError]; + // fall back to latin1 if no sqlText couldn't read + if(sqlText == nil) { + sqlEncoding = NSISOLatin1StringEncoding; + sqlText = [[NSString alloc] initWithContentsOfFile:[myURL path] encoding:sqlEncoding error:&readError]; } - - NSError *readError = nil; - - NSStringEncoding sqlEncoding = NSUTF8StringEncoding; - - if(fileAttributes) - { - - NSNumber *filesize = [fileAttributes objectForKey:NSFileSize]; - NSUInteger kMaxSQLFileSize = (0.7f * 1024 * 1024); - - // compose the html and perform syntax highlighting - - // read the file and try to get a proper encoding - NSString *sqlText = [[NSString alloc] initWithContentsOfFile:[myURL path] encoding:sqlEncoding error:&readError]; - NSMutableString *sqlHTML = [[NSMutableString alloc] initWithCapacity:[sqlText length]]; - NSString *truncatedString = [[NSString alloc] init]; - - if(readError != nil) { - // cocoa tries to detect the encoding - sqlText = [[NSString alloc] initWithContentsOfFile:[myURL path] usedEncoding:&sqlEncoding error:&readError]; - // fall back to latin1 if no sqlText couldn't read - if(sqlText == nil) { - sqlEncoding = NSISOLatin1StringEncoding; - sqlText = [[NSString alloc] initWithContentsOfFile:[myURL path] encoding:sqlEncoding error:&readError]; - } + } + + // if nothing could be read print ... SQL ... + if(!sqlText) { + [sqlHTML appendString:@"... SQL ..."]; + } else { + + // truncate large files since Finder blocks + if([filesize unsignedLongValue] > kMaxSQLFileSize) { + NSString *truncatedSqlText = [[NSString alloc] initWithString:[sqlText substringToIndex:kMaxSQLFileSize-1]]; + [sqlText release]; + sqlText = [[NSString alloc] initWithString:truncatedSqlText]; + [truncatedSqlText release]; + [truncatedString release]; + truncatedString = [[NSString alloc] initWithString:@"\n ✂ ..."]; + } + + NSString *tokenColor; + size_t token; + NSRange tokenRange; + + // initialise flex + yyuoffset = 0; yyuleng = 0; + yy_switch_to_buffer(yy_scan_string([sqlText UTF8String])); + BOOL skipFontTag; + + // now loop through all the tokens + NSUInteger poolCount = 0; + NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init]; + while ((token=yylex())){ + skipFontTag = NO; + switch (token) { + case SPT_SINGLE_QUOTED_TEXT: + case SPT_DOUBLE_QUOTED_TEXT: + tokenColor = @"#A7221C"; + break; + case SPT_BACKTICK_QUOTED_TEXT: + tokenColor = @"#001892"; + break; + case SPT_RESERVED_WORD: + tokenColor = @"#0041F6"; + break; + case SPT_NUMERIC: + tokenColor = @"#67350F"; + break; + case SPT_COMMENT: + tokenColor = @"#265C10"; + break; + case SPT_VARIABLE: + tokenColor = @"#6C6C6C"; + break; + case SPT_WHITESPACE: + skipFontTag = YES; + break; + default: + skipFontTag = YES; } - - // if nothing could be read print ... SQL ... - if(!sqlText) { - [sqlHTML appendString:@"... SQL ..."]; - } else { - - // truncate large files since Finder blocks - if([filesize unsignedLongValue] > kMaxSQLFileSize) { - NSString *truncatedSqlText = [[NSString alloc] initWithString:[sqlText substringToIndex:kMaxSQLFileSize-1]]; - [sqlText release]; - sqlText = [[NSString alloc] initWithString:truncatedSqlText]; - [truncatedSqlText release]; - [truncatedString release]; - truncatedString = [[NSString alloc] initWithString:@"\n ✂ ..."]; - } - - NSString *tokenColor; - size_t token; - NSRange tokenRange; - - // initialise flex - yyuoffset = 0; yyuleng = 0; - yy_switch_to_buffer(yy_scan_string([sqlText UTF8String])); - BOOL skipFontTag; - - // now loop through all the tokens - NSUInteger poolCount = 0; - NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init]; - while ((token=yylex())){ - skipFontTag = NO; - switch (token) { - case SPT_SINGLE_QUOTED_TEXT: - case SPT_DOUBLE_QUOTED_TEXT: - tokenColor = @"#A7221C"; - break; - case SPT_BACKTICK_QUOTED_TEXT: - tokenColor = @"#001892"; - break; - case SPT_RESERVED_WORD: - tokenColor = @"#0041F6"; - break; - case SPT_NUMERIC: - tokenColor = @"#67350F"; - break; - case SPT_COMMENT: - tokenColor = @"#265C10"; - break; - case SPT_VARIABLE: - tokenColor = @"#6C6C6C"; - break; - case SPT_WHITESPACE: - skipFontTag = YES; - break; - default: - skipFontTag = YES; - } - - tokenRange = NSMakeRange(yyuoffset, yyuleng); - - if(skipFontTag) - [sqlHTML appendString:[[sqlText substringWithRange:tokenRange] HTMLEscapeString]]; - else - [sqlHTML appendFormat:@"%@", tokenColor, [[sqlText substringWithRange:tokenRange] HTMLEscapeString]]; - - if (QLPreviewRequestIsCancelled(preview)) { - if(sqlHTML) SPClear(sqlHTML); - if(truncatedString) [truncatedString release], sqlHTML = nil; - if(sqlText) [sqlText release], sqlHTML = nil; - if(pool) SPClear(pool); - [loopPool release]; - return noErr; - } - - poolCount++; - if (poolCount > 1000) { - poolCount = 0; - [loopPool release]; - loopPool = [[NSAutoreleasePool alloc] init]; - } - } + + tokenRange = NSMakeRange(yyuoffset, yyuleng); + + if(skipFontTag) + [sqlHTML appendString:[[sqlText substringWithRange:tokenRange] HTMLEscapeString]]; + else + [sqlHTML appendFormat:@"%@", tokenColor, [[sqlText substringWithRange:tokenRange] HTMLEscapeString]]; + + if (QLPreviewRequestIsCancelled(preview)) { + if(sqlHTML) SPClear(sqlHTML); + if(truncatedString) [truncatedString release], sqlHTML = nil; + if(sqlText) [sqlText release], sqlHTML = nil; [loopPool release]; - [sqlHTML appendString:truncatedString]; - [sqlText release]; - [truncatedString release]; - + return nil; + } + + poolCount++; + if (poolCount > 1000) { + poolCount = 0; + [loopPool release]; + loopPool = [[NSAutoreleasePool alloc] init]; } - - // Wrap lines, and replace tabs with spaces - [sqlHTML replaceOccurrencesOfString:@"\n" withString:@"
" options:NSLiteralSearch range:NSMakeRange(0, [sqlHTML length])]; - [sqlHTML replaceOccurrencesOfString:@"\t" withString:@"    " options:NSLiteralSearch range:NSMakeRange(0, [sqlHTML length])]; - - // Improve soft wrapping my making more characters wrap points - [sqlHTML replaceOccurrencesOfString:@"," withString:@",​" options:NSLiteralSearch range:NSMakeRange(0, [sqlHTML length])]; - - html = [NSString stringWithFormat:template, - [NSString stringForByteSize:[[fileAttributes objectForKey:NSFileSize] longLongValue]], - sqlHTML - ]; - previewHeight = 495; - [sqlHTML release]; - - } else { - - // No file attributes were read, bail for safety reasons - if(pool) SPClear(pool); - return noErr; - } - - } - - NSMutableDictionary *props,*imgProps; - NSData *image; - - 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]]; - -#warning Shouldn't that be "> 1"? - if([iconImages count] > 0) - iconImage = [iconImages objectAtIndex:1]; - else - iconImage = [iconImages objectAtIndex:0]; - - image = [iconImage TIFFRepresentation]; - - props = [[NSMutableDictionary alloc] initWithCapacity:6]; - imgProps = [[NSMutableDictionary alloc] initWithCapacity:2]; - - [props setObject:[NSNumber numberWithInteger: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]; + [loopPool release]; + [sqlHTML appendString:truncatedString]; + [sqlText release]; + [truncatedString 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; - -} - -void CancelPreviewGeneration(void* thisInterface, QLPreviewRequestRef preview) -{ - // Implement only if supported + + // Wrap lines, and replace tabs with spaces + [sqlHTML replaceOccurrencesOfString:@"\n" withString:@"
" options:NSLiteralSearch range:NSMakeRange(0, [sqlHTML length])]; + [sqlHTML replaceOccurrencesOfString:@"\t" withString:@"    " options:NSLiteralSearch range:NSMakeRange(0, [sqlHTML length])]; + + // Improve soft wrapping my making more characters wrap points + [sqlHTML replaceOccurrencesOfString:@"," withString:@",​" options:NSLiteralSearch range:NSMakeRange(0, [sqlHTML length])]; + + NSString *html = [NSString stringWithFormat:template, + [NSString stringForByteSize:[[fileAttributes objectForKey:NSFileSize] longLongValue]], + sqlHTML + ]; + if(previewHeight != NULL) *previewHeight = 495; + [sqlHTML release]; + + return html; } diff --git a/Source/SPConstants.h b/Source/SPConstants.h index 67d1a91f..ec190ba4 100644 --- a/Source/SPConstants.h +++ b/Source/SPConstants.h @@ -274,6 +274,12 @@ extern NSString *SPHTMLPrintTemplate; extern NSString *SPHTMLTableInfoPrintTemplate; extern NSString *SPHTMLHelpTemplate; +// SPF file types +extern NSString *SPFExportSettingsContentType; +extern NSString *SPFContentFiltersContentType; +extern NSString *SPFQueryFavoritesContentType; +extern NSString *SPFConnectionContentType; + // Folder names extern NSString *SPThemesSupportFolder; extern NSString *SPBundleSupportFolder; diff --git a/Source/SPConstants.m b/Source/SPConstants.m index 4098f062..1c62d37e 100644 --- a/Source/SPConstants.m +++ b/Source/SPConstants.m @@ -56,6 +56,12 @@ NSString *SPFileExtensionSQL = @"sql"; NSString *SPColorThemeFileExtension = @"spTheme"; NSString *SPUserBundleFileExtension = @"spBundle"; +// SPF File types +NSString *SPFExportSettingsContentType = @"export settings"; +NSString *SPFContentFiltersContentType = @"content filters"; +NSString *SPFQueryFavoritesContentType = @"query favorites"; +NSString *SPFConnectionContentType = @"connection"; + // File names NSString *SPFavoritesDataFile = @"Favorites.plist"; NSString *SPHTMLPrintTemplate = @"SPPrintTemplate"; diff --git a/Source/SPContentFilterManager.m b/Source/SPContentFilterManager.m index dca1fda8..3f59f198 100644 --- a/Source/SPContentFilterManager.m +++ b/Source/SPContentFilterManager.m @@ -931,7 +931,7 @@ static NSString *SPExportFilterAction = @"SPExportFilter"; [spfdata setObject:@1 forKey:SPFVersionKey]; - [spfdata setObject:@"content filters" forKey:SPFFormatKey]; + [spfdata setObject:SPFContentFiltersContentType forKey:SPFFormatKey]; [spfdata setObject:@NO forKey:@"encrypted"]; NSIndexSet *indexes = [contentFilterTableView selectedRowIndexes]; diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index 05bdc849..a5202f05 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -3350,7 +3350,7 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; } // For dispatching later - if(![[spf objectForKey:SPFFormatKey] isEqualToString:@"connection"]) { + if(![[spf objectForKey:SPFFormatKey] isEqualToString:SPFConnectionContentType]) { NSLog(@"SPF file format is not 'connection'."); [spf release]; return NO; @@ -3400,7 +3400,7 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; // Add basic details [spfStructure setObject:@1 forKey:SPFVersionKey]; - [spfStructure setObject:@"connection" forKey:SPFFormatKey]; + [spfStructure setObject:SPFConnectionContentType forKey:SPFFormatKey]; [spfStructure setObject:@"mysql" forKey:@"rdbms_type"]; if([self mySQLVersion]) [spfStructure setObject:[self mySQLVersion] forKey:@"rdbms_version"]; @@ -4874,7 +4874,7 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; } // If the .spf format is unhandled, error. - if (![[spf objectForKey:SPFFormatKey] isEqualToString:@"connection"]) { + if (![[spf objectForKey:SPFFormatKey] isEqualToString:SPFConnectionContentType]) { NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Unknown file format", @"warning")] defaultButton:NSLocalizedString(@"OK", @"OK button") alternateButton:nil diff --git a/Source/SPExportSettingsPersistence.m b/Source/SPExportSettingsPersistence.m index f1fe98a2..38a4ed84 100644 --- a/Source/SPExportSettingsPersistence.m +++ b/Source/SPExportSettingsPersistence.m @@ -43,8 +43,6 @@ static inline NSNumber *IsOn(id obj); */ static inline void SetOnOff(NSNumber *ref,id obj); -static const NSString *SPFExportSettingsContentType = @"export settings"; - @interface SPExportController (Private) - (void)_updateExportAdvancedOptionsLabel; @@ -412,7 +410,7 @@ static const NSString *SPFExportSettingsContentType = @"export settings"; NSDictionary *errInfo = @{ @"isVersion": @(version), NSLocalizedDescriptionKey: NSLocalizedString(@"Unsupported version for export settings!", @"export : import settings : file version error title"), - NSLocalizedRecoverySuggestionErrorKey: [NSString stringWithFormat:NSLocalizedString(@"The selected export settings were stored with version\u00A0%1$ld, but only settings with the following versions can be imported: %2$@.\n\nEither save the settings in a backwards compatible way or update your version of Sequel Pro.", @"export : import settings : file version error description ($1 = is version, $2 = list of supported versions)"),version,@"1"], + NSLocalizedRecoverySuggestionErrorKey: [NSString stringWithFormat:NSLocalizedString(@"The selected export settings were stored with version\u00A0%1$ld, but only settings with the following versions can be imported: %2$@.\n\nEither save the settings in a backwards compatible way or update your version of Sequel Pro.", @"export : import settings : file version error description ($1 = is version, $2 = list of supported versions); note: the u00A0 is a non-breaking space, do not add more whitespace."),version,@"1"], }; *err = [NSError errorWithDomain:SPErrorDomain code:SPErrorWrongContentVersion diff --git a/Source/SPQueryFavoriteManager.m b/Source/SPQueryFavoriteManager.m index b1ea9a49..5857b1b7 100644 --- a/Source/SPQueryFavoriteManager.m +++ b/Source/SPQueryFavoriteManager.m @@ -918,7 +918,7 @@ [spfdata setObject:@1 forKey:SPFVersionKey]; - [spfdata setObject:@"query favorites" forKey:SPFFormatKey]; + [spfdata setObject:SPFQueryFavoritesContentType forKey:SPFFormatKey]; [spfdata setObject:@NO forKey:@"encrypted"]; NSIndexSet *indexes = [favoritesTableView selectedRowIndexes]; diff --git a/Source/main.c b/Source/main.c index 2c497dbe..e40183e0 100644 --- a/Source/main.c +++ b/Source/main.c @@ -148,7 +148,7 @@ HRESULT QuickLookGeneratorQueryInterface(void *thisInstance,REFIID iid,LPVOID *p ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->CancelThumbnailGeneration = CancelThumbnailGeneration; ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->GeneratePreviewForURL = GeneratePreviewForURL; ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->CancelPreviewGeneration = CancelPreviewGeneration; - ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType*)thisInstance)->conduitInterface)->AddRef(thisInstance); + ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->AddRef(thisInstance); *ppv = thisInstance; CFRelease(interfaceID); return S_OK; diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index 7d3dc6a5..90a1e8ac 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -179,6 +179,15 @@ 4DECC3370EC2A170008D359E /* Growl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4DECC3340EC2A170008D359E /* Growl.framework */; }; 4DECC48F0EC2B436008D359E /* Sparkle.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 4DECC3320EC2A170008D359E /* Sparkle.framework */; }; 4DECC4910EC2B436008D359E /* Growl.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 4DECC3340EC2A170008D359E /* Growl.framework */; }; + 50082B3D1BF7CD2100746ECC /* ICUTemplateMatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 296DC8AC0F909194002A3258 /* ICUTemplateMatcher.m */; }; + 50082B3E1BF7CD2100746ECC /* NSArray_DeepMutableCopy.m in Sources */ = {isa = PBXBuildFile; fileRef = 296DC8AE0F909194002A3258 /* NSArray_DeepMutableCopy.m */; }; + 50082B3F1BF7CD2100746ECC /* NSDictionary_DeepMutableCopy.m in Sources */ = {isa = PBXBuildFile; fileRef = 296DC8B10F909194002A3258 /* NSDictionary_DeepMutableCopy.m */; }; + 50082B421BF7CD3C00746ECC /* ICUTemplateMatcher.h in Headers */ = {isa = PBXBuildFile; fileRef = 296DC8A90F909194002A3258 /* ICUTemplateMatcher.h */; }; + 50082B431BF7CD3C00746ECC /* NSArray_DeepMutableCopy.h in Headers */ = {isa = PBXBuildFile; fileRef = 296DC8AF0F909194002A3258 /* NSArray_DeepMutableCopy.h */; }; + 50082B441BF7CD3C00746ECC /* NSDictionary_DeepMutableCopy.h in Headers */ = {isa = PBXBuildFile; fileRef = 296DC8B20F909194002A3258 /* NSDictionary_DeepMutableCopy.h */; }; + 50082B451BF7D1C300746ECC /* RegexKitLite.m in Sources */ = {isa = PBXBuildFile; fileRef = 296DC8AB0F909194002A3258 /* RegexKitLite.m */; }; + 50082B461BF7D1CD00746ECC /* RegexKitLite.h in Headers */ = {isa = PBXBuildFile; fileRef = 296DC8B00F909194002A3258 /* RegexKitLite.h */; }; + 50082B471BF7D1F600746ECC /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 296DC8BE0F9091DF002A3258 /* libicucore.dylib */; }; 500DA4B71BEFF877000773FE /* SPComboBoxCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 500DA4B61BEFF877000773FE /* SPComboBoxCell.m */; }; 500DA4BC1BF0CD57000773FE /* SPScreenAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 500DA4BB1BF0CD57000773FE /* SPScreenAdditions.m */; }; 501B1D181728A3DA0017C92E /* SPCharsetCollationHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 501B1D171728A3DA0017C92E /* SPCharsetCollationHelper.m */; }; @@ -200,6 +209,15 @@ 507FF26A1BC8450100104523 /* SPExportSettingsPersistence.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF2691BC8450100104523 /* SPExportSettingsPersistence.m */; }; 507FF2A11BCD27A700104523 /* SPFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF1111BBCC57600104523 /* SPFunctions.m */; }; 507FF2A21BCD27AE00104523 /* SPOSInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 50EAB5B71A8FBB08008F627A /* SPOSInfo.m */; }; + 508022951BF7BA470052A9B2 /* SPQLPluginExportSettingsTemplate.html in Resources */ = {isa = PBXBuildFile; fileRef = 508022931BF7BA470052A9B2 /* SPQLPluginExportSettingsTemplate.html */; }; + 508022961BF7C0E90052A9B2 /* MGTemplateMarker.h in Headers */ = {isa = PBXBuildFile; fileRef = 296DC8A50F909194002A3258 /* MGTemplateMarker.h */; }; + 508022971BF7C0E90052A9B2 /* MGTemplateFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 296DC8A60F909194002A3258 /* MGTemplateFilter.h */; }; + 508022981BF7C0E90052A9B2 /* MGTemplateEngine.h in Headers */ = {isa = PBXBuildFile; fileRef = 296DC8A80F909194002A3258 /* MGTemplateEngine.h */; }; + 508022991BF7C0E90052A9B2 /* MGTemplateStandardMarkers.h in Headers */ = {isa = PBXBuildFile; fileRef = 296DC8B30F909194002A3258 /* MGTemplateStandardMarkers.h */; }; + 5080229A1BF7C0E90052A9B2 /* MGTemplateStandardFilters.h in Headers */ = {isa = PBXBuildFile; fileRef = 296DC8B50F909194002A3258 /* MGTemplateStandardFilters.h */; }; + 5080229B1BF7C0FE0052A9B2 /* MGTemplateEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 296DC8A70F909194002A3258 /* MGTemplateEngine.m */; }; + 5080229C1BF7C0FE0052A9B2 /* MGTemplateStandardMarkers.m in Sources */ = {isa = PBXBuildFile; fileRef = 296DC8AD0F909194002A3258 /* MGTemplateStandardMarkers.m */; }; + 5080229D1BF7C0FE0052A9B2 /* MGTemplateStandardFilters.m in Sources */ = {isa = PBXBuildFile; fileRef = 296DC8B40F909194002A3258 /* MGTemplateStandardFilters.m */; }; 50805B0D1BF2A068005F7A99 /* SPPopUpButtonCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 50805B0C1BF2A068005F7A99 /* SPPopUpButtonCell.m */; }; 5089B0271BE714E300E226CD /* SPIdMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 5089B0261BE714E300E226CD /* SPIdMenu.m */; }; 50A9F8B119EAD4B90053E571 /* SPGotoDatabaseController.m in Sources */ = {isa = PBXBuildFile; fileRef = 50A9F8B019EAD4B90053E571 /* SPGotoDatabaseController.m */; }; @@ -924,6 +942,7 @@ 507FF1111BBCC57600104523 /* SPFunctions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPFunctions.m; sourceTree = ""; }; 507FF2681BC8450100104523 /* SPExportSettingsPersistence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPExportSettingsPersistence.h; sourceTree = ""; }; 507FF2691BC8450100104523 /* SPExportSettingsPersistence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExportSettingsPersistence.m; sourceTree = ""; }; + 508022941BF7BA470052A9B2 /* English */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = English; path = English.lproj/SPQLPluginExportSettingsTemplate.html; sourceTree = ""; }; 50805B0B1BF2A068005F7A99 /* SPPopUpButtonCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPPopUpButtonCell.h; sourceTree = ""; }; 50805B0C1BF2A068005F7A99 /* SPPopUpButtonCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPPopUpButtonCell.m; sourceTree = ""; }; 5089B0251BE714E300E226CD /* SPIdMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPIdMenu.h; sourceTree = ""; }; @@ -1313,6 +1332,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 50082B471BF7D1F600746ECC /* libicucore.dylib in Frameworks */, 502D22161BA62FF5000D4CE7 /* Security.framework in Frameworks */, BC0E14A1120AAC2E00E52E25 /* libbz2.dylib in Frameworks */, 584D87C415141A5D00F24774 /* libz.dylib in Frameworks */, @@ -2453,6 +2473,7 @@ 584756FE120A1B290057631F /* SPQLPluginQueryFavoritesTemplate.html */, 58475700120A1B290057631F /* SPQLPluginSQLTemplate.html */, BCEC861F12115A30002561DA /* SPQLPluginConnectionBundleWindowTemplate.html */, + 508022931BF7BA470052A9B2 /* SPQLPluginExportSettingsTemplate.html */, ); name = "QuickLook Plugin"; sourceTree = ""; @@ -2687,6 +2708,15 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 50082B461BF7D1CD00746ECC /* RegexKitLite.h in Headers */, + 50082B421BF7CD3C00746ECC /* ICUTemplateMatcher.h in Headers */, + 50082B431BF7CD3C00746ECC /* NSArray_DeepMutableCopy.h in Headers */, + 50082B441BF7CD3C00746ECC /* NSDictionary_DeepMutableCopy.h in Headers */, + 508022961BF7C0E90052A9B2 /* MGTemplateMarker.h in Headers */, + 508022971BF7C0E90052A9B2 /* MGTemplateFilter.h in Headers */, + 508022981BF7C0E90052A9B2 /* MGTemplateEngine.h in Headers */, + 508022991BF7C0E90052A9B2 /* MGTemplateStandardMarkers.h in Headers */, + 5080229A1BF7C0E90052A9B2 /* MGTemplateStandardFilters.h in Headers */, BC6D709D120C4C97008027B5 /* SPEditorTokens.h in Headers */, BCD06FC7120AAACB00C73602 /* SPStringAdditions.h in Headers */, BCD06FC6120AAAC200C73602 /* SPDataAdditions.h in Headers */, @@ -2883,6 +2913,7 @@ buildActionMask = 2147483647; files = ( 58475702120A1B290057631F /* SPQLPluginConnectionTemplate.html in Resources */, + 508022951BF7BA470052A9B2 /* SPQLPluginExportSettingsTemplate.html in Resources */, 58475703120A1B290057631F /* SPQLPluginContentFiltersTemplate.html in Resources */, 58475704120A1B290057631F /* SPQLPluginQueryFavoritesTemplate.html in Resources */, 58475705120A1B290057631F /* SPQLPluginSQLTemplate.html in Resources */, @@ -3133,6 +3164,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 50082B451BF7D1C300746ECC /* RegexKitLite.m in Sources */, + 50082B3D1BF7CD2100746ECC /* ICUTemplateMatcher.m in Sources */, + 50082B3E1BF7CD2100746ECC /* NSArray_DeepMutableCopy.m in Sources */, + 50082B3F1BF7CD2100746ECC /* NSDictionary_DeepMutableCopy.m in Sources */, + 5080229B1BF7C0FE0052A9B2 /* MGTemplateEngine.m in Sources */, + 5080229C1BF7C0FE0052A9B2 /* MGTemplateStandardMarkers.m in Sources */, + 5080229D1BF7C0FE0052A9B2 /* MGTemplateStandardFilters.m in Sources */, 507FF2A21BCD27AE00104523 /* SPOSInfo.m in Sources */, 507FF2A11BCD27A700104523 /* SPFunctions.m in Sources */, 50D3C3541A7715E600B5429C /* SPParserUtils.c in Sources */, @@ -3519,6 +3557,14 @@ name = UserManagerView.xib; sourceTree = ""; }; + 508022931BF7BA470052A9B2 /* SPQLPluginExportSettingsTemplate.html */ = { + isa = PBXVariantGroup; + children = ( + 508022941BF7BA470052A9B2 /* English */, + ); + name = SPQLPluginExportSettingsTemplate.html; + sourceTree = ""; + }; 50D3C34B1A75B8A800B5429C /* GotoDatabaseDialog.xib */ = { isa = PBXVariantGroup; children = ( -- cgit v1.2.3 From a23e7b66395357ada37a1e6a8c386ce49b58a611 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 15 Nov 2015 18:08:40 +0100 Subject: Store the last export settings using the new "export settings" format --- Interfaces/English.lproj/ExportDialog.xib | 32 ------------ Resources/Plists/PreferenceDefaults.plist | 4 -- Source/SPConstants.h | 9 ++-- Source/SPConstants.m | 6 +-- Source/SPExportController.m | 83 +++++++++++-------------------- Source/SPExportSettingsPersistence.m | 3 +- 6 files changed, 33 insertions(+), 104 deletions(-) diff --git a/Interfaces/English.lproj/ExportDialog.xib b/Interfaces/English.lproj/ExportDialog.xib index 5275ec30..25d5b075 100644 --- a/Interfaces/English.lproj/ExportDialog.xib +++ b/Interfaces/English.lproj/ExportDialog.xib @@ -3808,38 +3808,6 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2
1238
- - - value: values.SPExportMemoryModeLastValue - - - - - - value: values.SPExportMemoryModeLastValue - value - values.SPExportMemoryModeLastValue - 2 - - - 1389 - - - - selectedIndex: values.SPExportCompressionLastValue - - - - - - selectedIndex: values.SPExportCompressionLastValue - selectedIndex - values.SPExportCompressionLastValue - 2 - - - 1387 - diff --git a/Resources/Plists/PreferenceDefaults.plist b/Resources/Plists/PreferenceDefaults.plist index 687b4b09..1d55e65a 100644 --- a/Resources/Plists/PreferenceDefaults.plist +++ b/Resources/Plists/PreferenceDefaults.plist @@ -149,8 +149,6 @@ 3 NewFieldsAllowNulls - NoBOMforSQLdumpFile - NullValue NULL PrintBackground @@ -189,8 +187,6 @@ SPFirstRun - SQLExportUseCompression - SSHMultiplexingEnabled TableInformationPanelCollapsed diff --git a/Source/SPConstants.h b/Source/SPConstants.h index ec190ba4..db51b462 100644 --- a/Source/SPConstants.h +++ b/Source/SPConstants.h @@ -67,7 +67,8 @@ typedef NS_ENUM(NSUInteger, SPExportType) { SPDotExport = 3, SPPDFExport = 4, SPHTMLExport = 5, - SPExcelExport = 6 + SPExcelExport = 6, + SPAnyExportType = NSUIntegerMax, // this is a transient type to indicate "no specific choice" }; // Export source constants @@ -402,11 +403,7 @@ extern NSString *SPCSVImportFieldEscapeCharacter; extern NSString *SPCSVImportFirstLineIsHeader; extern NSString *SPCSVFieldImportMappingAlignment; extern NSString *SPImportClipboardTempFileNamePrefix; -extern NSString *SPSQLExportUseCompression; -extern NSString *SPNoBOMforSQLdumpFile; -extern NSString *SPExportLastDirectory; -extern NSString *SPExportFilenameFormat; // legacy -extern NSString *SPExportFilenameFormatIntl; // new, user language independent version +extern NSString *SPLastExportSettings; // Export filename tokens extern NSString *SPFileNameDatabaseTokenName; diff --git a/Source/SPConstants.m b/Source/SPConstants.m index 1c62d37e..16f59f7a 100644 --- a/Source/SPConstants.m +++ b/Source/SPConstants.m @@ -202,11 +202,7 @@ NSString *SPCSVImportFirstLineIsHeader = @"CSVImportFirstLineIsHeader" NSString *SPCSVImportLineTerminator = @"CSVImportLineTerminator"; NSString *SPCSVFieldImportMappingAlignment = @"CSVFieldImportMappingAlignment"; NSString *SPImportClipboardTempFileNamePrefix = @"/tmp/_SP_ClipBoard_Import_File_"; -NSString *SPSQLExportUseCompression = @"SQLExportUseCompression"; -NSString *SPNoBOMforSQLdumpFile = @"NoBOMforSQLdumpFile"; -NSString *SPExportLastDirectory = @"SPExportLastDirectory"; -NSString *SPExportFilenameFormat = @"SPExportFilenameFormat"; -NSString *SPExportFilenameFormatIntl = @"CustomExportFilenameFormat"; +NSString *SPLastExportSettings = @"LastExportSettings"; // Export filename tokens NSString *SPFileNameDatabaseTokenName = @"database"; diff --git a/Source/SPExportController.m b/Source/SPExportController.m index 9113decc..2439dd86 100644 --- a/Source/SPExportController.m +++ b/Source/SPExportController.m @@ -42,6 +42,7 @@ #import "SPThreadAdditions.h" #import "SPCustomQuery.h" #import "SPExportController+SharedPrivateAPI.h" +#import "SPExportSettingsPersistence.h" #import @@ -63,7 +64,6 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; - (void)_displayExportTypeOptions:(BOOL)display; - (void)_updateExportFormatInformation; - (void)_updateExportAdvancedOptionsLabel; -- (void)_setPreviousExportFilenameAndPath; - (void)_toggleExportButton:(id)uiStateDict; - (void)_toggleExportButtonOnBackgroundThread; @@ -120,8 +120,6 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; heightOffset1 = 0; heightOffset2 = 0; - windowMinWidth = [[self window] minSize].width; - windowMinHeigth = [[self window] minSize].height; prefs = [NSUserDefaults standardUserDefaults]; @@ -150,13 +148,17 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; if (mainNibLoaded) return; mainNibLoaded = YES; + + windowMinWidth = [[self window] minSize].width; + windowMinHeigth = [[self window] minSize].height; // Select the 'selected tables' option [exportInputPopUpButton selectItemAtIndex:SPTableExport]; // Select the SQL tab [[exportTypeTabBar tabViewItemAtIndex:0] setView:exporterView]; - + [exportTypeTabBar selectTabViewItemAtIndex:0]; + // By default a new SQL INSERT statement should be created every 250KiB of data [exportSQLInsertNValueTextField setIntegerValue:250]; @@ -186,14 +188,28 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; * @param source The source of the export. See SPExportSource constants. */ - (void)exportTables:(NSArray *)exportTables asFormat:(SPExportType)format usingSource:(SPExportSource)source -{ - // Select the correct tab - [exportTypeTabBar selectTabViewItemAtIndex:format]; +{ + // set some defaults + [exportCSVNULLValuesAsTextField setStringValue:[prefs stringForKey:SPNullValue]]; + [exportXMLNULLValuesAsTextField setStringValue:[prefs stringForKey:SPNullValue]]; + if(![[exportPathField stringValue] length]) { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSAllDomainsMask, YES); + // If found the set the default path to the user's desktop, otherwise use their home directory + [exportPathField setStringValue:([paths count] > 0) ? [paths objectAtIndex:0] : NSHomeDirectory()]; + } - [self _setPreviousExportFilenameAndPath]; + // initially popuplate the tables list + [self refreshTableList:nil]; + + // overwrite defaults with user settings from last export + [self applySettingsFromDictionary:[prefs objectForKey:SPLastExportSettings] error:NULL]; + + // overwrite those with settings for the current export + + // Select the correct tab + if(format != SPAnyExportType) [exportTypeTabBar selectTabViewItemAtIndex:format]; [self updateDisplayedExportFilename]; - [self refreshTableList:nil]; [exporters removeAllObjects]; [exportFiles removeAllObjects]; @@ -281,7 +297,7 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; */ - (IBAction)export:(id)sender { - SPExportType selectedExportType = SPSQLExport; + SPExportType selectedExportType = SPAnyExportType; SPExportSource selectedExportSource = SPTableExport; NSArray *selectedTables = [tablesListInstance selectedTableItems]; @@ -454,7 +470,6 @@ set_input: userInfo:nil]; } [exportPathField setStringValue:path]; - [prefs setObject:path forKey:SPExportLastDirectory]; } }]; } @@ -703,24 +718,8 @@ set_input: { // Perform the export if (returnCode == NSOKButton) { - - // Check whether to save the export filename. Save it if it's not blank and contains at least one - // token - this suggests it's not a one-off filename - if ([[exportCustomFilenameTokenField stringValue] length] < 1) { - [prefs removeObjectForKey:SPExportFilenameFormatIntl]; - } - else { - BOOL saveFilename = NO; - - NSArray *representedObjects = [exportCustomFilenameTokenField objectValue]; - - for (id aToken in representedObjects) - { - if ([aToken isKindOfClass:[SPExportFileNameTokenObject class]]) saveFilename = YES; - } - - if (saveFilename) [prefs setObject:[NSKeyedArchiver archivedDataWithRootObject:representedObjects] forKey:SPExportFilenameFormatIntl]; - } + + [prefs setObject:[self currentSettingsAsDictionary] forKey:SPLastExportSettings]; // If we are about to perform a table export, cache the current number of tables within the list, // refresh the list and then compare the numbers to accommodate situations where new tables are @@ -843,9 +842,6 @@ set_input: [exportDotForceLowerTableNamesCheck setState:(serverLowerCaseTableNameValue == 0)?NSOffState:NSOnState]; } - - [exportCSVNULLValuesAsTextField setStringValue:[prefs stringForKey:SPNullValue]]; - [exportXMLNULLValuesAsTextField setStringValue:[prefs stringForKey:SPNullValue]]; [self _displayExportTypeOptions:(isSQL || isCSV || isXML || isDot)]; [self updateAvailableExportFilenameTokens]; @@ -972,29 +968,6 @@ set_input: [exportAdvancedOptionsViewLabelButton setTitle:[NSString stringWithFormat:@"%@ (%@)", NSLocalizedString(@"Advanced", @"Advanced options short title"), [optionsSummary componentsJoinedByString:@", "]]]; } -/** - * Sets the previous export filename and path if available. - */ -- (void)_setPreviousExportFilenameAndPath -{ - id o; - // Restore the export filename if it exists, and update the display - if ((o = [prefs objectForKey:SPExportFilenameFormatIntl])) { - [exportCustomFilenameTokenField setObjectValue:[NSKeyedUnarchiver unarchiveObjectWithData:o]]; - } - - // If a directory has previously been selected, reselect it - if ((o = [prefs objectForKey:SPExportLastDirectory])) { - [exportPathField setStringValue:o]; - } - else { - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSAllDomainsMask, YES); - - // If found the set the default path to the user's desktop, otherwise use their home directory - [exportPathField setStringValue:([paths count] > 0) ? [paths objectAtIndex:0] : NSHomeDirectory()]; - } -} - /** * Enables or disables the export button based on the state of various interface controls. * diff --git a/Source/SPExportSettingsPersistence.m b/Source/SPExportSettingsPersistence.m index 38a4ed84..29a981af 100644 --- a/Source/SPExportSettingsPersistence.m +++ b/Source/SPExportSettingsPersistence.m @@ -46,7 +46,6 @@ static inline void SetOnOff(NSNumber *ref,id obj); @interface SPExportController (Private) - (void)_updateExportAdvancedOptionsLabel; -- (void)_switchTab; @end @@ -463,7 +462,7 @@ static inline void SetOnOff(NSNumber *ref,id obj); // token pool is only valid once the schema object selection is done [self updateAvailableExportFilenameTokens]; - if((o = [dict objectForKey:@"customFilename"])) [self setCustomFilenameFromArray:o]; + if((o = [dict objectForKey:@"customFilename"]) && [o isKindOfClass:[NSArray class]]) [self setCustomFilenameFromArray:o]; return YES; } -- cgit v1.2.3 From 8d435dbfd1aaf02a91218aa9badd8c1f71fb2a41 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 15 Nov 2015 18:10:26 +0100 Subject: Add XML encoding header --- Source/SPExportFileUtilities.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/SPExportFileUtilities.m b/Source/SPExportFileUtilities.m index 0cecad91..b3a6cc3c 100644 --- a/Source/SPExportFileUtilities.m +++ b/Source/SPExportFileUtilities.m @@ -102,7 +102,7 @@ SPExportErrorChoice; { NSMutableString *header = [NSMutableString string]; - [header setString:@"\n\n"]; + [header setString:@"\n\n"]; [header appendString:@"