diff options
230 files changed, 11077 insertions, 4579 deletions
diff --git a/Frameworks/PostgresKit/PostgresKit.xcodeproj/project.pbxproj b/Frameworks/PostgresKit/PostgresKit.xcodeproj/project.pbxproj index 479474da..bf8e5545 100644 --- a/Frameworks/PostgresKit/PostgresKit.xcodeproj/project.pbxproj +++ b/Frameworks/PostgresKit/PostgresKit.xcodeproj/project.pbxproj @@ -81,7 +81,7 @@ 1724CA3B15F9EE7300AB2291 /* libpqtypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = libpqtypes.h; sourceTree = "<group>"; }; 1724CC9015FB4CC200AB2291 /* PGPostgresTimeTZ.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PGPostgresTimeTZ.h; sourceTree = "<group>"; }; 1724CC9115FB4CC200AB2291 /* PGPostgresTimeTZ.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PGPostgresTimeTZ.m; sourceTree = "<group>"; }; - 1724CD0415FB68E800AB2291 /* Tests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.octest; sourceTree = BUILT_PRODUCTS_DIR; }; + 1724CD0415FB68E800AB2291 /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 1724CD0515FB68E800AB2291 /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Tests-Info.plist"; path = "Resources/Tests-Info.plist"; sourceTree = "<group>"; }; 1724CD5715FB8A3300AB2291 /* PGPostgresTimeInterval.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PGPostgresTimeInterval.h; sourceTree = "<group>"; }; 1724CD5815FB8A3300AB2291 /* PGPostgresTimeInterval.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PGPostgresTimeInterval.m; sourceTree = "<group>"; }; @@ -167,7 +167,7 @@ isa = PBXGroup; children = ( 8DC2EF5B0486A6940098B216 /* PostgresKit.framework */, - 1724CD0415FB68E800AB2291 /* Tests.octest */, + 1724CD0415FB68E800AB2291 /* Tests.xctest */, ); name = Products; sourceTree = "<group>"; @@ -460,8 +460,8 @@ ); name = Tests; productName = Tests; - productReference = 1724CD0415FB68E800AB2291 /* Tests.octest */; - productType = "com.apple.product-type.bundle"; + productReference = 1724CD0415FB68E800AB2291 /* Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; }; 8DC2EF4F0486A6940098B216 /* PostgresKit */ = { isa = PBXNativeTarget; @@ -487,7 +487,7 @@ 0867D690FE84028FC02AAC07 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0500; + LastUpgradeCheck = 0720; }; buildConfigurationList = 1DEB91B108733DA50010E9CD /* Build configuration list for PBXProject "PostgresKit" */; compatibilityVersion = "Xcode 3.2"; @@ -565,7 +565,7 @@ CLANG_LINK_OBJC_RUNTIME = NO; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks"; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; @@ -576,12 +576,9 @@ OTHER_LDFLAGS = ( "-framework", Cocoa, - "-framework", - SenTestingKit, ); + PRODUCT_BUNDLE_IDENTIFIER = com.sequelpro.postgreskit.tests; PRODUCT_NAME = Tests; - TEST_AFTER_BUILD = YES; - WRAPPER_EXTENSION = octest; }; name = Debug; }; @@ -592,7 +589,7 @@ CLANG_LINK_OBJC_RUNTIME = NO; COMBINE_HIDPI_IMAGES = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks"; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; @@ -601,12 +598,9 @@ OTHER_LDFLAGS = ( "-framework", Cocoa, - "-framework", - SenTestingKit, ); + PRODUCT_BUNDLE_IDENTIFIER = com.sequelpro.postgreskit.tests; PRODUCT_NAME = Tests; - TEST_AFTER_BUILD = YES; - WRAPPER_EXTENSION = octest; ZERO_LINK = NO; }; name = Release; @@ -617,7 +611,7 @@ ALWAYS_SEARCH_USER_PATHS = NO; CLANG_LINK_OBJC_RUNTIME = NO; COMBINE_HIDPI_IMAGES = YES; - FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks"; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; @@ -626,12 +620,9 @@ OTHER_LDFLAGS = ( "-framework", Cocoa, - "-framework", - SenTestingKit, ); + PRODUCT_BUNDLE_IDENTIFIER = com.sequelpro.postgreskit.tests; PRODUCT_NAME = Tests; - TEST_AFTER_BUILD = YES; - WRAPPER_EXTENSION = octest; }; name = Distribution; }; @@ -645,8 +636,11 @@ CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Source/PostgresKit-Prefix.pch"; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; @@ -704,6 +698,7 @@ "$(inherited)", "\"$(SRCROOT)/Libs\"/**", ); + PRODUCT_BUNDLE_IDENTIFIER = com.sequelpro.postgreskit; PRODUCT_NAME = PostgresKit; WARNING_CFLAGS = "-Wmost"; WRAPPER_EXTENSION = framework; @@ -756,6 +751,7 @@ "$(inherited)", "\"$(SRCROOT)/Libs\"/**", ); + PRODUCT_BUNDLE_IDENTIFIER = com.sequelpro.postgreskit; PRODUCT_NAME = PostgresKit; WARNING_CFLAGS = "-Wmost"; WRAPPER_EXTENSION = framework; @@ -803,6 +799,7 @@ "$(inherited)", "\"$(SRCROOT)/Libs\"/**", ); + PRODUCT_BUNDLE_IDENTIFIER = com.sequelpro.postgreskit; PRODUCT_NAME = PostgresKit; WARNING_CFLAGS = "-Wmost"; WRAPPER_EXTENSION = framework; @@ -819,8 +816,12 @@ CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Source/PostgresKit-Prefix.pch"; @@ -849,8 +850,11 @@ CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Source/PostgresKit-Prefix.pch"; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; diff --git a/Frameworks/PostgresKit/PostgresKit.xcodeproj/xcshareddata/xcschemes/PostgresKit.xcscheme b/Frameworks/PostgresKit/PostgresKit.xcodeproj/xcshareddata/xcschemes/PostgresKit.xcscheme index ca74a70b..04dd4bea 100644 --- a/Frameworks/PostgresKit/PostgresKit.xcodeproj/xcshareddata/xcschemes/PostgresKit.xcscheme +++ b/Frameworks/PostgresKit/PostgresKit.xcodeproj/xcshareddata/xcschemes/PostgresKit.xcscheme @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <Scheme - LastUpgradeVersion = "0500" + LastUpgradeVersion = "0720" version = "1.3"> <BuildAction parallelizeBuildables = "YES" @@ -33,12 +33,14 @@ <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "1724CD0315FB68E800AB2291" - BuildableName = "Tests.octest" + BuildableName = "Tests.xctest" BlueprintName = "Tests" ReferencedContainer = "container:PostgresKit.xcodeproj"> </BuildableReference> </TestableReference> </Testables> + <AdditionalOptions> + </AdditionalOptions> </TestAction> <LaunchAction selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" @@ -48,7 +50,17 @@ buildConfiguration = "Debug" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" + debugServiceExtension = "internal" allowLocationSimulation = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "8DC2EF4F0486A6940098B216" + BuildableName = "PostgresKit.framework" + BlueprintName = "PostgresKit" + ReferencedContainer = "container:PostgresKit.xcodeproj"> + </BuildableReference> + </MacroExpansion> <AdditionalOptions> </AdditionalOptions> </LaunchAction> diff --git a/Frameworks/PostgresKit/Resources/Info.plist b/Frameworks/PostgresKit/Resources/Info.plist index b961331f..0da2c36b 100644 --- a/Frameworks/PostgresKit/Resources/Info.plist +++ b/Frameworks/PostgresKit/Resources/Info.plist @@ -7,7 +7,7 @@ <key>CFBundleExecutable</key> <string>PostgresKit</string> <key>CFBundleIdentifier</key> - <string>com.sequelpro.postgreskit</string> + <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundlePackageType</key> diff --git a/Frameworks/PostgresKit/Resources/Tests-Info.plist b/Frameworks/PostgresKit/Resources/Tests-Info.plist index b88d8638..ddbc4bf7 100644 --- a/Frameworks/PostgresKit/Resources/Tests-Info.plist +++ b/Frameworks/PostgresKit/Resources/Tests-Info.plist @@ -7,7 +7,7 @@ <key>CFBundleExecutable</key> <string>Tests</string> <key>CFBundleIdentifier</key> - <string>com.sequelpro.postgreskit.tests</string> + <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundlePackageType</key> diff --git a/Frameworks/PostgresKit/Tests/PGDataTypeTests.h b/Frameworks/PostgresKit/Tests/PGDataTypeTests.h index 8f6b54ad..7322823f 100644 --- a/Frameworks/PostgresKit/Tests/PGDataTypeTests.h +++ b/Frameworks/PostgresKit/Tests/PGDataTypeTests.h @@ -27,7 +27,7 @@ // OTHER DEALINGS IN THE SOFTWARE. #import <PostgresKit/PostgresKit.h> -#import <SenTestingKit/SenTestingKit.h> +#import <XCTest/XCTest.h> #import "PGPostgresIntegrationTestCase.h" diff --git a/Frameworks/PostgresKit/Tests/PGDataTypeTests.m b/Frameworks/PostgresKit/Tests/PGDataTypeTests.m index a2048ed6..8ffd8b47 100644 --- a/Frameworks/PostgresKit/Tests/PGDataTypeTests.m +++ b/Frameworks/PostgresKit/Tests/PGDataTypeTests.m @@ -33,7 +33,7 @@ + (void)_addTestForField:(NSString *)field withExpectedResult:(id)result connection:(PGPostgresConnection *)connection - toTestSuite:(SenTestSuite *)testSuite; + toTestSuite:(XCTestSuite *)testSuite; @end @@ -48,7 +48,7 @@ + (id)defaultTestSuite { - SenTestSuite *testSuite = [[SenTestSuite alloc] initWithName:[self className]]; + XCTestSuite *testSuite = [[XCTestSuite alloc] initWithName:[self className]]; PGPostgresConnection *connection = [[PGPostgresConnection alloc] init]; @@ -109,12 +109,12 @@ - (void)testResultValueIsNotNil { - STAssertNotNil(_result, nil); + XCTAssertNotNil(_result); } - (void)testResultIsOfCorrectTypeAndValue { - STAssertEqualObjects(_result, _expectedResult, nil); + XCTAssertEqualObjects(_result, _expectedResult); } #pragma mark - @@ -123,11 +123,11 @@ + (void)_addTestForField:(NSString *)field withExpectedResult:(id)result connection:(PGPostgresConnection *)connection - toTestSuite:(SenTestSuite *)testSuite + toTestSuite:(XCTestSuite *)testSuite { for (NSInvocation *invocation in [self testInvocations]) { - SenTestCase *test = [[[self class] alloc] initWithInvocation:invocation connection:connection expectedResult:result field:field]; + XCTestCase *test = [[[self class] alloc] initWithInvocation:invocation connection:connection expectedResult:result field:field]; [testSuite addTest:test]; diff --git a/Frameworks/PostgresKit/Tests/PGPostgresIntegrationTestCase.h b/Frameworks/PostgresKit/Tests/PGPostgresIntegrationTestCase.h index c6706156..7a654f5f 100644 --- a/Frameworks/PostgresKit/Tests/PGPostgresIntegrationTestCase.h +++ b/Frameworks/PostgresKit/Tests/PGPostgresIntegrationTestCase.h @@ -27,9 +27,9 @@ // OTHER DEALINGS IN THE SOFTWARE. #import <PostgresKit/PostgresKit.h> -#import <SenTestingKit/SenTestingKit.h> +#import <XCTest/XCTest.h> -@interface PGPostgresIntegrationTestCase : SenTestCase +@interface PGPostgresIntegrationTestCase : XCTestCase { PGPostgresConnection *_connection; } diff --git a/Frameworks/PostgresKit/Tests/PGPostgresIntegrationTestCase.m b/Frameworks/PostgresKit/Tests/PGPostgresIntegrationTestCase.m index e6c96505..231e1bfa 100644 --- a/Frameworks/PostgresKit/Tests/PGPostgresIntegrationTestCase.m +++ b/Frameworks/PostgresKit/Tests/PGPostgresIntegrationTestCase.m @@ -35,6 +35,8 @@ static NSString *PGTestDatabasePassword = @"pgkit"; static NSUInteger PGTestDatabasePort = 5432; +static double PGTestConnectionTimeout = 0.2; + @interface PGPostgresIntegrationTestCase () - (void)_establishConnection; @@ -67,13 +69,25 @@ static NSUInteger PGTestDatabasePort = 5432; [_connection setPassword:PGTestDatabasePassword]; if (![_connection connect]) { - STFail(@"Request to establish connection to local database failed."); + XCTFail(@"Request to establish connection to local database failed."); exit(1); } + NSDate *startDate = [NSDate date]; + do { sleep(0.1); + + if([[NSDate date] timeIntervalSinceDate:startDate] > PGTestConnectionTimeout) { + XCTFail(@"Failed to connect to database after %f seconds. Host:%@ Database:%@ User:%@ Password:%@", + PGTestConnectionTimeout, + PGTestDatabaseHost, + PGTestDatabaseName, + PGTestDatabaseUser, + PGTestDatabasePassword); + exit(1); + } } while (![_connection isConnected]); } diff --git a/Frameworks/PostgresKit/Tests/PGPostgresResultTests.h b/Frameworks/PostgresKit/Tests/PGPostgresResultTests.h index 93c36427..e01aedec 100644 --- a/Frameworks/PostgresKit/Tests/PGPostgresResultTests.h +++ b/Frameworks/PostgresKit/Tests/PGPostgresResultTests.h @@ -27,7 +27,7 @@ // OTHER DEALINGS IN THE SOFTWARE. #import <PostgresKit/PostgresKit.h> -#import <SenTestingKit/SenTestingKit.h> +#import <XCTest/XCTest.h> #import "PGPostgresIntegrationTestCase.h" diff --git a/Frameworks/PostgresKit/Tests/PGPostgresResultTests.m b/Frameworks/PostgresKit/Tests/PGPostgresResultTests.m index ef64ea53..8e27b163 100644 --- a/Frameworks/PostgresKit/Tests/PGPostgresResultTests.m +++ b/Frameworks/PostgresKit/Tests/PGPostgresResultTests.m @@ -63,7 +63,7 @@ "}"); // Compare the output after getting rid of newlines and spaces - STAssertTrue([[[[_result description] stringByReplacingOccurrencesOfString:@"\n" withString:@""] stringByReplacingOccurrencesOfString:@" " withString:@""] isEqualToString:@"{" + XCTAssertTrue([[[[_result description] stringByReplacingOccurrencesOfString:@"\n" withString:@""] stringByReplacingOccurrencesOfString:@" " withString:@""] isEqualToString:@"{" "\"bigint_field\" = 123456789;" "\"bool_field\" = 1;" "\"char_field\" = CHAR;" @@ -77,7 +77,7 @@ "\"timestamptz_field\" = \"8 Apr 1987 03:02:02 GMT+01:00\";" "\"timetz_field\" = \"02:02:02 GMT+10:00\";" "\"varchar_field\" = VARCHAR;" - "}"], nil); + "}"]); } #pragma mark - diff --git a/Frameworks/QueryKit/QueryKit.xcodeproj/project.pbxproj b/Frameworks/QueryKit/QueryKit.xcodeproj/project.pbxproj index 62059d07..52ef1774 100644 --- a/Frameworks/QueryKit/QueryKit.xcodeproj/project.pbxproj +++ b/Frameworks/QueryKit/QueryKit.xcodeproj/project.pbxproj @@ -87,7 +87,7 @@ 17E5951E14F301DF0054EE08 /* QueryKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QueryKit.h; sourceTree = "<group>"; }; 17E595F114F3058F0054EE08 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 17E5969814F307B70054EE08 /* QKSelectQueryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QKSelectQueryTests.m; sourceTree = "<group>"; }; - 17E5969E14F307CE0054EE08 /* Tests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.octest; sourceTree = BUILT_PRODUCTS_DIR; }; + 17E5969E14F307CE0054EE08 /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 17E5969F14F307CE0054EE08 /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Tests-Info.plist"; path = "Resources/Tests-Info.plist"; sourceTree = "<group>"; }; 17F48BA615B27F6400C6455B /* QKQueryOrderBy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QKQueryOrderBy.h; sourceTree = "<group>"; }; 17F48BA715B27F6400C6455B /* QKQueryOrderBy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QKQueryOrderBy.m; sourceTree = "<group>"; }; @@ -123,7 +123,7 @@ isa = PBXGroup; children = ( 8DC2EF5B0486A6940098B216 /* QueryKit.framework */, - 17E5969E14F307CE0054EE08 /* Tests.octest */, + 17E5969E14F307CE0054EE08 /* Tests.xctest */, ); name = Products; sourceTree = "<group>"; @@ -329,8 +329,8 @@ ); name = Tests; productName = Tests; - productReference = 17E5969E14F307CE0054EE08 /* Tests.octest */; - productType = "com.apple.product-type.bundle"; + productReference = 17E5969E14F307CE0054EE08 /* Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; }; 8DC2EF4F0486A6940098B216 /* QueryKit */ = { isa = PBXNativeTarget; @@ -357,7 +357,7 @@ 0867D690FE84028FC02AAC07 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0510; + LastUpgradeCheck = 0720; }; buildConfigurationList = 1DEB91B108733DA50010E9CD /* Build configuration list for PBXProject "QueryKit" */; compatibilityVersion = "Xcode 3.2"; @@ -436,7 +436,7 @@ 17E5952D14F302740054EE08 /* Distribution */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; CLANG_LINK_OBJC_RUNTIME = NO; CLANG_STATIC_ANALYZER_MODE = deep; CLANG_WARN_BOOL_CONVERSION = YES; @@ -444,8 +444,11 @@ CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Source/QueryKit-Prefix.pch"; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; @@ -498,6 +501,7 @@ GCC_WARN_UNUSED_VALUE = YES; INFOPLIST_FILE = Resources/Info.plist; INSTALL_PATH = "@executable_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.sequelpro.querykit; PRODUCT_NAME = QueryKit; SKIP_INSTALL = YES; WARNING_CFLAGS = "-Wmost"; @@ -511,7 +515,7 @@ ALWAYS_SEARCH_USER_PATHS = NO; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks"; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_MODEL_TUNING = G5; @@ -523,12 +527,9 @@ OTHER_LDFLAGS = ( "-framework", Cocoa, - "-framework", - SenTestingKit, ); + PRODUCT_BUNDLE_IDENTIFIER = com.sequelpro.querykit.tests; PRODUCT_NAME = Tests; - TEST_AFTER_BUILD = YES; - WRAPPER_EXTENSION = octest; }; name = Debug; }; @@ -539,7 +540,7 @@ COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks"; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; @@ -549,12 +550,9 @@ OTHER_LDFLAGS = ( "-framework", Cocoa, - "-framework", - SenTestingKit, ); + PRODUCT_BUNDLE_IDENTIFIER = com.sequelpro.querykit.tests; PRODUCT_NAME = Tests; - TEST_AFTER_BUILD = YES; - WRAPPER_EXTENSION = octest; ZERO_LINK = NO; }; name = Release; @@ -564,7 +562,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; COMBINE_HIDPI_IMAGES = YES; - FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks"; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; @@ -574,12 +572,9 @@ OTHER_LDFLAGS = ( "-framework", Cocoa, - "-framework", - SenTestingKit, ); + PRODUCT_BUNDLE_IDENTIFIER = com.sequelpro.querykit.tests; PRODUCT_NAME = Tests; - TEST_AFTER_BUILD = YES; - WRAPPER_EXTENSION = octest; }; name = Distribution; }; @@ -623,6 +618,7 @@ GENERATE_PKGINFO_FILE = YES; INFOPLIST_FILE = Resources/Info.plist; INSTALL_PATH = "@executable_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.sequelpro.querykit; PRODUCT_NAME = QueryKit; SKIP_INSTALL = YES; WARNING_CFLAGS = "-Wmost"; @@ -666,6 +662,7 @@ GCC_WARN_UNUSED_VALUE = YES; INFOPLIST_FILE = Resources/Info.plist; INSTALL_PATH = "@executable_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.sequelpro.querykit; PRODUCT_NAME = QueryKit; SKIP_INSTALL = YES; WARNING_CFLAGS = "-Wmost"; @@ -676,7 +673,7 @@ 1DEB91B208733DA50010E9CD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; CLANG_LINK_OBJC_RUNTIME = NO; CLANG_STATIC_ANALYZER_MODE = deep; CLANG_WARN_BOOL_CONVERSION = YES; @@ -684,8 +681,12 @@ CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Source/QueryKit-Prefix.pch"; @@ -707,7 +708,7 @@ 1DEB91B308733DA50010E9CD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; CLANG_LINK_OBJC_RUNTIME = NO; CLANG_STATIC_ANALYZER_MODE = deep; CLANG_WARN_BOOL_CONVERSION = YES; @@ -715,8 +716,11 @@ CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Source/QueryKit-Prefix.pch"; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; diff --git a/Frameworks/QueryKit/QueryKit.xcodeproj/xcshareddata/xcschemes/QueryKit.xcscheme b/Frameworks/QueryKit/QueryKit.xcodeproj/xcshareddata/xcschemes/QueryKit.xcscheme index fcff0831..b5c97922 100644 --- a/Frameworks/QueryKit/QueryKit.xcodeproj/xcshareddata/xcschemes/QueryKit.xcscheme +++ b/Frameworks/QueryKit/QueryKit.xcodeproj/xcshareddata/xcschemes/QueryKit.xcscheme @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <Scheme - LastUpgradeVersion = "0510" + LastUpgradeVersion = "0720" version = "1.3"> <BuildAction parallelizeBuildables = "YES" @@ -33,12 +33,14 @@ <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "17E5969D14F307CE0054EE08" - BuildableName = "Tests.octest" + BuildableName = "Tests.xctest" BlueprintName = "Tests" ReferencedContainer = "container:QueryKit.xcodeproj"> </BuildableReference> </TestableReference> </Testables> + <AdditionalOptions> + </AdditionalOptions> </TestAction> <LaunchAction selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" @@ -48,7 +50,17 @@ buildConfiguration = "Debug" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" + debugServiceExtension = "internal" allowLocationSimulation = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "8DC2EF4F0486A6940098B216" + BuildableName = "QueryKit.framework" + BlueprintName = "QueryKit" + ReferencedContainer = "container:QueryKit.xcodeproj"> + </BuildableReference> + </MacroExpansion> <AdditionalOptions> </AdditionalOptions> </LaunchAction> diff --git a/Frameworks/QueryKit/Resources/Info.plist b/Frameworks/QueryKit/Resources/Info.plist index 6c5911aa..5c5cdce8 100644 --- a/Frameworks/QueryKit/Resources/Info.plist +++ b/Frameworks/QueryKit/Resources/Info.plist @@ -7,7 +7,7 @@ <key>CFBundleExecutable</key> <string>QueryKit</string> <key>CFBundleIdentifier</key> - <string>com.sequelpro.querykit</string> + <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundlePackageType</key> diff --git a/Frameworks/QueryKit/Resources/Tests-Info.plist b/Frameworks/QueryKit/Resources/Tests-Info.plist index add26ae7..ddbc4bf7 100644 --- a/Frameworks/QueryKit/Resources/Tests-Info.plist +++ b/Frameworks/QueryKit/Resources/Tests-Info.plist @@ -7,7 +7,7 @@ <key>CFBundleExecutable</key> <string>Tests</string> <key>CFBundleIdentifier</key> - <string>com.sequelpro.querykit.tests</string> + <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundlePackageType</key> diff --git a/Frameworks/QueryKit/Tests/QKDeleteQueryTests.h b/Frameworks/QueryKit/Tests/QKDeleteQueryTests.h index ef0e26ca..379fdaf4 100644 --- a/Frameworks/QueryKit/Tests/QKDeleteQueryTests.h +++ b/Frameworks/QueryKit/Tests/QKDeleteQueryTests.h @@ -29,10 +29,10 @@ #import "QKTestCase.h" #import <QueryKit/QueryKit.h> -#import <SenTestingKit/SenTestingKit.h> +#import <XCTest/XCTest.h> @interface QKDeleteQueryTests : QKTestCase -+ (void)addTestForDatabase:(QKQueryDatabase)database withIdentifierQuote:(NSString *)quote toTestSuite:(SenTestSuite *)testSuite; ++ (void)addTestForDatabase:(QKQueryDatabase)database withIdentifierQuote:(NSString *)quote toTestSuite:(XCTestSuite *)testSuite; @end diff --git a/Frameworks/QueryKit/Tests/QKDeleteQueryTests.m b/Frameworks/QueryKit/Tests/QKDeleteQueryTests.m index 438f45dd..8862fee5 100644 --- a/Frameworks/QueryKit/Tests/QKDeleteQueryTests.m +++ b/Frameworks/QueryKit/Tests/QKDeleteQueryTests.m @@ -36,7 +36,7 @@ + (id)defaultTestSuite { - SenTestSuite *testSuite = [[SenTestSuite alloc] initWithName:NSStringFromClass(self)]; + XCTestSuite *testSuite = [[XCTestSuite alloc] initWithName:NSStringFromClass(self)]; [self addTestForDatabase:QKDatabaseUnknown withIdentifierQuote:EMPTY_STRING toTestSuite:testSuite]; [self addTestForDatabase:QKDatabaseMySQL withIdentifierQuote:QKMySQLIdentifierQuote toTestSuite:testSuite]; @@ -45,11 +45,11 @@ return [testSuite autorelease]; } -+ (void)addTestForDatabase:(QKQueryDatabase)database withIdentifierQuote:(NSString *)quote toTestSuite:(SenTestSuite *)testSuite ++ (void)addTestForDatabase:(QKQueryDatabase)database withIdentifierQuote:(NSString *)quote toTestSuite:(XCTestSuite *)testSuite { for (NSInvocation *invocation in [self testInvocations]) { - SenTestCase *test = [[QKDeleteQueryTests alloc] initWithInvocation:invocation database:database identifierQuote:quote]; + XCTestCase *test = [[QKDeleteQueryTests alloc] initWithInvocation:invocation database:database identifierQuote:quote]; [testSuite addTest:test]; @@ -77,14 +77,14 @@ - (void)testDeleteQueryTypeIsCorrect { - STAssertTrue([[[self query] query] hasPrefix:@"DELETE"], nil); + XCTAssertTrue([[[self query] query] hasPrefix:@"DELETE"]); } - (void)testDeleteQueryFromTableIsCorrect { NSString *query = [NSString stringWithFormat:@"DELETE FROM %1$@%2$@%1$@", [self identifierQuote], QKTestTableName]; - STAssertTrue([[[self query] query] isEqualToString:query], nil); + XCTAssertTrue([[[self query] query] isEqualToString:query]); } - (void)testDeleteQueryFromDatabaseTableIsCorrect @@ -93,7 +93,7 @@ NSString *query = [NSString stringWithFormat:@"DELETE FROM %1$@%2$@%1$@.%1$@%3$@%1$@", [self identifierQuote], QKTestDatabaseName, QKTestTableName]; - STAssertTrue([[[self query] query] isEqualToString:query] , nil); + XCTAssertTrue([[[self query] query] isEqualToString:query] ); } - (void)testDeleteQueryWithSingleConstraintIsCorrect @@ -102,7 +102,7 @@ NSString *query = [NSString stringWithFormat:@"DELETE FROM %1$@%2$@%1$@ WHERE %1$@%3$@%1$@ %4$@ %5$@", [self identifierQuote], QKTestTableName, QKTestFieldOne, [QKQueryUtilities stringRepresentationOfQueryOperator:QKEqualityOperator], [NSNumber numberWithUnsignedInteger:QKTestParameterOne]]; - STAssertTrue([[[self query] query] isEqualToString:query] , nil); + XCTAssertTrue([[[self query] query] isEqualToString:query] ); } - (void)testDeleteQueryWithMultipleConstraintsIsCorrect @@ -115,7 +115,7 @@ NSString *query = [NSString stringWithFormat:@"DELETE FROM %1$@%2$@%1$@ WHERE %1$@%3$@%1$@ %4$@ %5$@ AND %1$@%6$@%1$@ %7$@ '%8$@'", [self identifierQuote], QKTestTableName, QKTestFieldOne, opOne, [NSNumber numberWithUnsignedInteger:QKTestParameterOne], QKTestFieldTwo, opTwo, QKTestParameterTwo]; - STAssertTrue([[[self query] query] isEqualToString:query] , nil); + XCTAssertTrue([[[self query] query] isEqualToString:query] ); } @end diff --git a/Frameworks/QueryKit/Tests/QKQueryTests.m b/Frameworks/QueryKit/Tests/QKQueryTests.m index 0043e607..2064fad6 100644 --- a/Frameworks/QueryKit/Tests/QKQueryTests.m +++ b/Frameworks/QueryKit/Tests/QKQueryTests.m @@ -30,7 +30,7 @@ #import "QKTestCase.h" #import <QueryKit/QueryKit.h> -#import <SenTestingKit/SenTestingKit.h> +#import <XCTest/XCTest.h> @interface QKQueryTests : QKTestCase @end @@ -68,19 +68,19 @@ { [[self query] clear]; - STAssertNil([[self query] table], @"query table"); - STAssertNil([[self query] database], @"query database"); + XCTAssertNil([[self query] table], @"query table"); + XCTAssertNil([[self query] database], @"query database"); - STAssertTrue([[self query] useQuotedIdentifiers], @"query use quoted identifiers"); - STAssertTrue([[[self query] identifierQuote] isEqualToString:EMPTY_STRING], @"query identifier quote"); - STAssertTrue([[[self query] fields] count] == 0, @"query fields"); - STAssertTrue([[[self query] parameters] count] == 0, @"query parameters"); - STAssertTrue([[[self query] updateParameters] count] == 0, @"query update parameters"); - STAssertTrue([[[self query] groupByFields] count] == 0, @"query group by fields"); - STAssertTrue([[[self query] orderByFields] count] == 0, @"query order by fields"); + XCTAssertTrue([[self query] useQuotedIdentifiers], @"query use quoted identifiers"); + XCTAssertTrue([[[self query] identifierQuote] isEqualToString:EMPTY_STRING], @"query identifier quote"); + XCTAssertTrue([[[self query] fields] count] == 0, @"query fields"); + XCTAssertTrue([[[self query] parameters] count] == 0, @"query parameters"); + XCTAssertTrue([[[self query] updateParameters] count] == 0, @"query update parameters"); + XCTAssertTrue([[[self query] groupByFields] count] == 0, @"query group by fields"); + XCTAssertTrue([[[self query] orderByFields] count] == 0, @"query order by fields"); - STAssertEquals([[self query] queryType], QKUnknownQuery, @"query type"); - STAssertEquals([[self query] queryDatabase], QKDatabaseUnknown, @"query database"); + XCTAssertEqual([[self query] queryType], QKUnknownQuery, @"query type"); + XCTAssertEqual([[self query] queryDatabase], QKDatabaseUnknown, @"query database"); } @end diff --git a/Frameworks/QueryKit/Tests/QKSelectQueryGroupByTests.h b/Frameworks/QueryKit/Tests/QKSelectQueryGroupByTests.h index 9a1349d8..f7966a66 100644 --- a/Frameworks/QueryKit/Tests/QKSelectQueryGroupByTests.h +++ b/Frameworks/QueryKit/Tests/QKSelectQueryGroupByTests.h @@ -29,10 +29,10 @@ #import "QKTestCase.h" #import <QueryKit/QueryKit.h> -#import <SenTestingKit/SenTestingKit.h> +#import <XCTest/XCTest.h> @interface QKSelectQueryGroupByTests : QKTestCase -+ (void)addTestForDatabase:(QKQueryDatabase)database withIdentifierQuote:(NSString *)quote toTestSuite:(SenTestSuite *)testSuite; ++ (void)addTestForDatabase:(QKQueryDatabase)database withIdentifierQuote:(NSString *)quote toTestSuite:(XCTestSuite *)testSuite; @end diff --git a/Frameworks/QueryKit/Tests/QKSelectQueryGroupByTests.m b/Frameworks/QueryKit/Tests/QKSelectQueryGroupByTests.m index d8e627bc..76aa1a80 100644 --- a/Frameworks/QueryKit/Tests/QKSelectQueryGroupByTests.m +++ b/Frameworks/QueryKit/Tests/QKSelectQueryGroupByTests.m @@ -36,7 +36,7 @@ + (id)defaultTestSuite { - SenTestSuite *testSuite = [[SenTestSuite alloc] initWithName:NSStringFromClass(self)]; + XCTestSuite *testSuite = [[XCTestSuite alloc] initWithName:NSStringFromClass(self)]; [self addTestForDatabase:QKDatabaseUnknown withIdentifierQuote:EMPTY_STRING toTestSuite:testSuite]; [self addTestForDatabase:QKDatabaseMySQL withIdentifierQuote:QKMySQLIdentifierQuote toTestSuite:testSuite]; @@ -45,11 +45,11 @@ return [testSuite autorelease]; } -+ (void)addTestForDatabase:(QKQueryDatabase)database withIdentifierQuote:(NSString *)quote toTestSuite:(SenTestSuite *)testSuite ++ (void)addTestForDatabase:(QKQueryDatabase)database withIdentifierQuote:(NSString *)quote toTestSuite:(XCTestSuite *)testSuite { for (NSInvocation *invocation in [self testInvocations]) { - SenTestCase *test = [[QKSelectQueryGroupByTests alloc] initWithInvocation:invocation database:database identifierQuote:quote]; + XCTestCase *test = [[QKSelectQueryGroupByTests alloc] initWithInvocation:invocation database:database identifierQuote:quote]; [testSuite addTest:test]; @@ -78,7 +78,7 @@ - (void)testSelectQueryTypeIsCorrect { - STAssertTrue([[[self query] query] hasPrefix:@"SELECT"], nil); + XCTAssertTrue([[[self query] query] hasPrefix:@"SELECT"]); } - (void)testSelectQueryGroupByIsCorrect @@ -87,7 +87,7 @@ NSString *query = [NSString stringWithFormat:@"GROUP BY %1$@%2$@%1$@", [self identifierQuote], QKTestFieldOne]; - STAssertTrue([[[self query] query] hasSuffix:query], nil); + XCTAssertTrue([[[self query] query] hasSuffix:query]); } - (void)testSelectQueryGroupByMultipleFieldsIsCorrect @@ -96,7 +96,7 @@ NSString *query = [NSString stringWithFormat:@"GROUP BY %1$@%2$@%1$@, %1$@%3$@%1$@", [self identifierQuote], QKTestFieldOne, QKTestFieldTwo]; - STAssertTrue([[[self query] query] hasSuffix:query], nil); + XCTAssertTrue([[[self query] query] hasSuffix:query]); } @end diff --git a/Frameworks/QueryKit/Tests/QKSelectQueryOrderByTests.h b/Frameworks/QueryKit/Tests/QKSelectQueryOrderByTests.h index efd7eb2f..ffca6929 100644 --- a/Frameworks/QueryKit/Tests/QKSelectQueryOrderByTests.h +++ b/Frameworks/QueryKit/Tests/QKSelectQueryOrderByTests.h @@ -29,10 +29,10 @@ #import "QKTestCase.h" #import <QueryKit/QueryKit.h> -#import <SenTestingKit/SenTestingKit.h> +#import <XCTest/XCTest.h> @interface QKSelectQueryOrderByTests : QKTestCase -+ (void)addTestForDatabase:(QKQueryDatabase)database withIdentifierQuote:(NSString *)quote toTestSuite:(SenTestSuite *)testSuite; ++ (void)addTestForDatabase:(QKQueryDatabase)database withIdentifierQuote:(NSString *)quote toTestSuite:(XCTestSuite *)testSuite; @end diff --git a/Frameworks/QueryKit/Tests/QKSelectQueryOrderByTests.m b/Frameworks/QueryKit/Tests/QKSelectQueryOrderByTests.m index b6847d11..a1be1d0c 100644 --- a/Frameworks/QueryKit/Tests/QKSelectQueryOrderByTests.m +++ b/Frameworks/QueryKit/Tests/QKSelectQueryOrderByTests.m @@ -36,7 +36,7 @@ + (id)defaultTestSuite { - SenTestSuite *testSuite = [[SenTestSuite alloc] initWithName:NSStringFromClass(self)]; + XCTestSuite *testSuite = [[XCTestSuite alloc] initWithName:NSStringFromClass(self)]; [self addTestForDatabase:QKDatabaseUnknown withIdentifierQuote:EMPTY_STRING toTestSuite:testSuite]; [self addTestForDatabase:QKDatabaseMySQL withIdentifierQuote:QKMySQLIdentifierQuote toTestSuite:testSuite]; @@ -45,11 +45,11 @@ return [testSuite autorelease]; } -+ (void)addTestForDatabase:(QKQueryDatabase)database withIdentifierQuote:(NSString *)quote toTestSuite:(SenTestSuite *)testSuite ++ (void)addTestForDatabase:(QKQueryDatabase)database withIdentifierQuote:(NSString *)quote toTestSuite:(XCTestSuite *)testSuite { for (NSInvocation *invocation in [self testInvocations]) { - SenTestCase *test = [[QKSelectQueryOrderByTests alloc] initWithInvocation:invocation database:database identifierQuote:quote]; + XCTestCase *test = [[QKSelectQueryOrderByTests alloc] initWithInvocation:invocation database:database identifierQuote:quote]; [testSuite addTest:test]; @@ -78,7 +78,7 @@ - (void)testSelectQueryTypeIsCorrect { - STAssertTrue([[[self query] query] hasPrefix:@"SELECT"], nil); + XCTAssertTrue([[[self query] query] hasPrefix:@"SELECT"]); } - (void)testSelectQueryOrderByAscendingIsCorrect @@ -87,7 +87,7 @@ NSString *query = [NSString stringWithFormat:@"ORDER BY %1$@%2$@%1$@ ASC", [self identifierQuote], QKTestFieldOne]; - STAssertTrue([[[self query] query] hasSuffix:query], nil); + XCTAssertTrue([[[self query] query] hasSuffix:query]); } - (void)testSelectQueryOrderByMultipleFieldsAscendingIsCorrect @@ -97,7 +97,7 @@ NSString *query = [NSString stringWithFormat:@"ORDER BY %1$@%2$@%1$@ ASC, %1$@%3$@%1$@ ASC", [self identifierQuote], QKTestFieldOne, QKTestFieldTwo]; - STAssertTrue([[[self query] query] hasSuffix:query], nil); + XCTAssertTrue([[[self query] query] hasSuffix:query]); } - (void)testSelectQueryOrderByDescendingIsCorrect @@ -106,7 +106,7 @@ NSString *query = [NSString stringWithFormat:@"ORDER BY %1$@%2$@%1$@ DESC", [self identifierQuote], QKTestFieldOne]; - STAssertTrue([[[self query] query] hasSuffix:query], nil); + XCTAssertTrue([[[self query] query] hasSuffix:query]); } - (void)testSelectQueryOrderByMultipleFieldsDescendingIsCorrect @@ -116,7 +116,7 @@ NSString *query = [NSString stringWithFormat:@"ORDER BY %1$@%2$@%1$@ DESC, %1$@%3$@%1$@ DESC", [self identifierQuote], QKTestFieldOne, QKTestFieldTwo]; - STAssertTrue([[[self query] query] hasSuffix:query], nil); + XCTAssertTrue([[[self query] query] hasSuffix:query]); } @end diff --git a/Frameworks/QueryKit/Tests/QKSelectQueryTests.h b/Frameworks/QueryKit/Tests/QKSelectQueryTests.h index 5845ad06..43eda806 100644 --- a/Frameworks/QueryKit/Tests/QKSelectQueryTests.h +++ b/Frameworks/QueryKit/Tests/QKSelectQueryTests.h @@ -29,10 +29,10 @@ #import "QKTestCase.h" #import <QueryKit/QueryKit.h> -#import <SenTestingKit/SenTestingKit.h> +#import <XCTest/XCTest.h> @interface QKSelectQueryTests : QKTestCase -+ (void)addTestForDatabase:(QKQueryDatabase)database withIdentifierQuote:(NSString *)quote toTestSuite:(SenTestSuite *)testSuite; ++ (void)addTestForDatabase:(QKQueryDatabase)database withIdentifierQuote:(NSString *)quote toTestSuite:(XCTestSuite *)testSuite; @end diff --git a/Frameworks/QueryKit/Tests/QKSelectQueryTests.m b/Frameworks/QueryKit/Tests/QKSelectQueryTests.m index 0f1153c6..ec5328be 100644 --- a/Frameworks/QueryKit/Tests/QKSelectQueryTests.m +++ b/Frameworks/QueryKit/Tests/QKSelectQueryTests.m @@ -36,7 +36,7 @@ + (id)defaultTestSuite { - SenTestSuite *testSuite = [[SenTestSuite alloc] initWithName:NSStringFromClass(self)]; + XCTestSuite *testSuite = [[XCTestSuite alloc] initWithName:NSStringFromClass(self)]; [self addTestForDatabase:QKDatabaseUnknown withIdentifierQuote:EMPTY_STRING toTestSuite:testSuite]; [self addTestForDatabase:QKDatabaseMySQL withIdentifierQuote:QKMySQLIdentifierQuote toTestSuite:testSuite]; @@ -45,11 +45,11 @@ return [testSuite autorelease]; } -+ (void)addTestForDatabase:(QKQueryDatabase)database withIdentifierQuote:(NSString *)quote toTestSuite:(SenTestSuite *)testSuite ++ (void)addTestForDatabase:(QKQueryDatabase)database withIdentifierQuote:(NSString *)quote toTestSuite:(XCTestSuite *)testSuite { for (NSInvocation *invocation in [self testInvocations]) { - SenTestCase *test = [[QKSelectQueryTests alloc] initWithInvocation:invocation database:database identifierQuote:quote]; + XCTestCase *test = [[QKSelectQueryTests alloc] initWithInvocation:invocation database:database identifierQuote:quote]; [testSuite addTest:test]; @@ -82,14 +82,14 @@ - (void)testSelectQueryTypeIsCorrect { - STAssertTrue([[[self query] query] hasPrefix:@"SELECT"], nil); + XCTAssertTrue([[[self query] query] hasPrefix:@"SELECT"]); } - (void)testSelectQueryFieldIsCorrect { NSString *query = [NSString stringWithFormat:@"SELECT %1$@%2$@%1$@", [self identifierQuote], QKTestFieldOne]; - STAssertTrue([[[self query] query] hasPrefix:query], nil); + XCTAssertTrue([[[self query] query] hasPrefix:query]); } - (void)testSelectQueryFromDatabaseAndTableIsCorrect @@ -98,21 +98,21 @@ NSString *query = [NSString stringWithFormat:@"FROM %1$@%2$@%1$@.%1$@%3$@%1$@", [self identifierQuote], QKTestDatabaseName, QKTestTableName]; - STAssertTrue([[[self query] query] rangeOfString:query].location != NSNotFound, nil); + XCTAssertTrue([[[self query] query] rangeOfString:query].location != NSNotFound); } - (void)testSelectQueryMultipleFieldsAreCorrect { NSString *query = [NSString stringWithFormat:@"SELECT %1$@%2$@%1$@, %1$@%3$@%1$@, %1$@%4$@%1$@, %1$@%5$@%1$@", [self identifierQuote], QKTestFieldOne, QKTestFieldTwo, QKTestFieldThree, QKTestFieldFour]; - STAssertTrue([[[self query] query] hasPrefix:query], nil); + XCTAssertTrue([[[self query] query] hasPrefix:query]); } - (void)testSelectQueryConstraintsAreCorrect { NSString *query = [NSString stringWithFormat:@"WHERE %1$@%2$@%1$@ %3$@ %4$@", [self identifierQuote], QKTestFieldOne, [QKQueryUtilities stringRepresentationOfQueryOperator:QKEqualityOperator], [NSNumber numberWithUnsignedInteger:QKTestParameterOne]]; - STAssertTrue(([[[self query] query] rangeOfString:query].location != NSNotFound), nil); + XCTAssertTrue(([[[self query] query] rangeOfString:query].location != NSNotFound)); } @end diff --git a/Frameworks/QueryKit/Tests/QKTestCase.h b/Frameworks/QueryKit/Tests/QKTestCase.h index 47efec04..baa4f050 100644 --- a/Frameworks/QueryKit/Tests/QKTestCase.h +++ b/Frameworks/QueryKit/Tests/QKTestCase.h @@ -27,9 +27,9 @@ // OTHER DEALINGS IN THE SOFTWARE. #import <QueryKit/QueryKit.h> -#import <SenTestingKit/SenTestingKit.h> +#import <XCTest/XCTest.h> -@interface QKTestCase : SenTestCase +@interface QKTestCase : XCTestCase { @private QKQuery *_query; diff --git a/Frameworks/QueryKit/Tests/QKUpdateQueryTests.h b/Frameworks/QueryKit/Tests/QKUpdateQueryTests.h index aede8979..7c3ea7c5 100644 --- a/Frameworks/QueryKit/Tests/QKUpdateQueryTests.h +++ b/Frameworks/QueryKit/Tests/QKUpdateQueryTests.h @@ -29,10 +29,10 @@ #import "QKTestCase.h" #import <QueryKit/QueryKit.h> -#import <SenTestingKit/SenTestingKit.h> +#import <XCTest/XCTest.h> @interface QKUpdateQueryTests : QKTestCase -+ (void)addTestForDatabase:(QKQueryDatabase)database withIdentifierQuote:(NSString *)quote toTestSuite:(SenTestSuite *)testSuite; ++ (void)addTestForDatabase:(QKQueryDatabase)database withIdentifierQuote:(NSString *)quote toTestSuite:(XCTestSuite *)testSuite; @end diff --git a/Frameworks/QueryKit/Tests/QKUpdateQueryTests.m b/Frameworks/QueryKit/Tests/QKUpdateQueryTests.m index 1beff6d5..039840bb 100644 --- a/Frameworks/QueryKit/Tests/QKUpdateQueryTests.m +++ b/Frameworks/QueryKit/Tests/QKUpdateQueryTests.m @@ -36,7 +36,7 @@ + (id)defaultTestSuite { - SenTestSuite *testSuite = [[SenTestSuite alloc] initWithName:NSStringFromClass(self)]; + XCTestSuite *testSuite = [[XCTestSuite alloc] initWithName:NSStringFromClass(self)]; [self addTestForDatabase:QKDatabaseUnknown withIdentifierQuote:EMPTY_STRING toTestSuite:testSuite]; [self addTestForDatabase:QKDatabaseMySQL withIdentifierQuote:QKMySQLIdentifierQuote toTestSuite:testSuite]; @@ -45,11 +45,11 @@ return [testSuite autorelease]; } -+ (void)addTestForDatabase:(QKQueryDatabase)database withIdentifierQuote:(NSString *)quote toTestSuite:(SenTestSuite *)testSuite ++ (void)addTestForDatabase:(QKQueryDatabase)database withIdentifierQuote:(NSString *)quote toTestSuite:(XCTestSuite *)testSuite { for (NSInvocation *invocation in [self testInvocations]) { - SenTestCase *test = [[NSClassFromString(@"QKUpdateQueryTests") alloc] initWithInvocation:invocation database:database identifierQuote:quote]; + XCTestCase *test = [[NSClassFromString(@"QKUpdateQueryTests") alloc] initWithInvocation:invocation database:database identifierQuote:quote]; [testSuite addTest:test]; @@ -81,7 +81,7 @@ - (void)testUpdateQueryTypeIsCorrect { - STAssertTrue([[[self query] query] hasPrefix:@"UPDATE"], nil); + XCTAssertTrue([[[self query] query] hasPrefix:@"UPDATE"]); } - (void)testUpdateQueryUsingDatabaseAndTableIsCorrect @@ -90,21 +90,21 @@ NSString *query = [NSString stringWithFormat:@"UPDATE %1$@%2$@%1$@.%1$@%3$@%1$@", [self identifierQuote], QKTestDatabaseName, QKTestTableName]; - STAssertTrue([[[self query] query] hasPrefix:query], nil); + XCTAssertTrue([[[self query] query] hasPrefix:query]); } - (void)testUpdateQueryFieldsAreCorrect { NSString *query = [NSString stringWithFormat:@"UPDATE %1$@%2$@%1$@ SET %1$@%3$@%1$@ = '%4$@', %1$@%5$@%1$@ = '%6$@'", [self identifierQuote], QKTestTableName, QKTestFieldOne, QKTestUpdateValueOne, QKTestFieldTwo, QKTestUpdateValueTwo]; - STAssertTrue([[[self query] query] hasPrefix:query], nil); + XCTAssertTrue([[[self query] query] hasPrefix:query]); } - (void)testUpdateQueryConstraintIsCorrect { NSString *query = [NSString stringWithFormat:@"WHERE %1$@%2$@%1$@ %3$@ %4$@", [self identifierQuote], QKTestFieldOne, [QKQueryUtilities stringRepresentationOfQueryOperator:QKEqualityOperator], [NSNumber numberWithUnsignedInteger:QKTestParameterOne]]; - STAssertTrue(([[[self query] query] rangeOfString:query].location != NSNotFound), nil); + XCTAssertTrue(([[[self query] query] rangeOfString:query].location != NSNotFound)); } @end diff --git a/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/mysql_com.h b/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/mysql_com.h index f2345be6..8a8c019d 100644 --- a/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/mysql_com.h +++ b/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/mysql_com.h @@ -348,7 +348,11 @@ enum enum_field_types { MYSQL_TYPE_DECIMAL, MYSQL_TYPE_TINY, MYSQL_TYPE_DATETIME, MYSQL_TYPE_YEAR, MYSQL_TYPE_NEWDATE, MYSQL_TYPE_VARCHAR, MYSQL_TYPE_BIT, - MYSQL_TYPE_NEWDECIMAL=246, + MYSQL_TYPE_TIMESTAMP2, + MYSQL_TYPE_DATETIME2, + MYSQL_TYPE_TIME2, + MYSQL_TYPE_JSON=245, + MYSQL_TYPE_NEWDECIMAL=246, MYSQL_TYPE_ENUM=247, MYSQL_TYPE_SET=248, MYSQL_TYPE_TINY_BLOB=249, diff --git a/Frameworks/SPMySQLFramework/Resources/Info.plist b/Frameworks/SPMySQLFramework/Resources/Info.plist index 84b64d04..0932b287 100644 --- a/Frameworks/SPMySQLFramework/Resources/Info.plist +++ b/Frameworks/SPMySQLFramework/Resources/Info.plist @@ -7,7 +7,7 @@ <key>CFBundleExecutable</key> <string>SPMySQL</string> <key>CFBundleIdentifier</key> - <string>com.sequelpro.spmysql</string> + <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundlePackageType</key> 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..b8256a5c --- /dev/null +++ b/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/DataConversion_Tests.m @@ -0,0 +1,77 @@ +// +// 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 <https://github.com/sequelpro/sequelpro> + +#import <Cocoa/Cocoa.h> +#import <XCTest/XCTest.h> + +// 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; + +@end + +@implementation DataConversion_Tests + +- (void)test_bitStringWithBytes +{ + // BIT(1) + { + const char y = '\1'; + const char n = '\0'; + XCTAssertEqualObjects(_bitStringWithBytes(&y,sizeof(y),1), @"1"); + XCTAssertEqualObjects(_bitStringWithBytes(&n,sizeof(n),1), @"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/SPMySQL Unit Tests/Info.plist b/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/Info.plist new file mode 100644 index 00000000..ba72822e --- /dev/null +++ b/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/Info.plist @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>en</string> + <key>CFBundleExecutable</key> + <string>$(EXECUTABLE_NAME)</string> + <key>CFBundleIdentifier</key> + <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>$(PRODUCT_NAME)</string> + <key>CFBundlePackageType</key> + <string>BNDL</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1</string> +</dict> +</plist> 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 <Cocoa/Cocoa.h> +#import <XCTest/XCTest.h> +#import <SPMySQL/SPMySQL.h> + +@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/SPMySQLDataTypes.h b/Frameworks/SPMySQLFramework/SPMySQLDataTypes.h index def1e988..31acf6b8 100644 --- a/Frameworks/SPMySQLFramework/SPMySQLDataTypes.h +++ b/Frameworks/SPMySQLFramework/SPMySQLDataTypes.h @@ -72,3 +72,4 @@ extern NSString *SPMySQLMultiPointType; extern NSString *SPMySQLMultiLineStringType; extern NSString *SPMySQLMultiPolygonType; extern NSString *SPMySQLGeometryCollectionType; +extern NSString *SPMySQLJsonType; diff --git a/Frameworks/SPMySQLFramework/SPMySQLDataTypes.m b/Frameworks/SPMySQLFramework/SPMySQLDataTypes.m index 06708f1b..d7f54c0e 100644 --- a/Frameworks/SPMySQLFramework/SPMySQLDataTypes.m +++ b/Frameworks/SPMySQLFramework/SPMySQLDataTypes.m @@ -74,3 +74,4 @@ NSString *SPMySQLMultiPointType = @"MULTIPOINT"; NSString *SPMySQLMultiLineStringType = @"MULTILINESTRING"; NSString *SPMySQLMultiPolygonType = @"MULTIPOLYGON"; NSString *SPMySQLGeometryCollectionType = @"GEOMETRYCOLLECTION"; +NSString *SPMySQLJsonType = @"JSON"; diff --git a/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj b/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj index 9acf9417..778b97f9 100644 --- a/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj +++ b/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj @@ -9,6 +9,9 @@ /* 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 */; }; + 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, ); }; }; @@ -75,6 +78,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 = "<absolute>"; }; 0867D6A5FE840307C02AAC07 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; }; @@ -83,6 +96,10 @@ 17E3A5791885A286009CF372 /* SPMySQLDataTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPMySQLDataTypes.h; sourceTree = "<group>"; }; 17E3A57A1885A286009CF372 /* SPMySQLDataTypes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPMySQLDataTypes.m; sourceTree = "<group>"; }; 32DBCF5E0370ADEE00C91783 /* SPMySQLFramework_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLFramework_Prefix.pch; path = Source/SPMySQLFramework_Prefix.pch; sourceTree = "<group>"; }; + 507FF1811BC0C64100104523 /* DataConversion_Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DataConversion_Tests.m; sourceTree = "<group>"; }; + 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 = "<group>"; }; + 507FF23C1BC157B500104523 /* SPMySQLStringAdditions_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPMySQLStringAdditions_Tests.m; sourceTree = "<group>"; }; 580A331C14D75CF7000D6933 /* SPMySQLGeometryData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLGeometryData.h; path = Source/SPMySQLGeometryData.h; sourceTree = "<group>"; }; 580A331D14D75CF7000D6933 /* SPMySQLGeometryData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPMySQLGeometryData.m; path = Source/SPMySQLGeometryData.m; sourceTree = "<group>"; }; 583C734917A489CC0056B284 /* SPMySQLStreamingResultStoreDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLStreamingResultStoreDelegate.h; path = Source/SPMySQLStreamingResultStoreDelegate.h; sourceTree = "<group>"; }; @@ -153,6 +170,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 +196,7 @@ isa = PBXGroup; children = ( 8DC2EF5B0486A6940098B216 /* SPMySQL.framework */, + 507FF1D51BC0D7D300104523 /* SPMySQL Unit Tests.xctest */, ); name = Products; sourceTree = "<group>"; @@ -184,6 +210,7 @@ 08FB77AEFE84172EC02AAC07 /* Classes */, 58C009D214E31D1300AC489A /* Category Additions */, 32C88DFF0371C24200C91783 /* Other Sources */, + 507FF1801BC0C64100104523 /* Unit Tests */, 089C1665FE841158C02AAC07 /* Resources */, 58428DF514BA5A03000F8438 /* Scripts */, 0867D69AFE84028FC02AAC07 /* Linked Frameworks */, @@ -252,6 +279,17 @@ name = "Other Sources"; sourceTree = "<group>"; }; + 507FF1801BC0C64100104523 /* Unit Tests */ = { + isa = PBXGroup; + children = ( + 507FF1D81BC0D7D300104523 /* Info.plist */, + 507FF1811BC0C64100104523 /* DataConversion_Tests.m */, + 507FF23C1BC157B500104523 /* SPMySQLStringAdditions_Tests.m */, + ); + name = "Unit Tests"; + path = "SPMySQL Unit Tests"; + sourceTree = "<group>"; + }; 580A331B14D75CCF000D6933 /* Result types */ = { isa = PBXGroup; children = ( @@ -419,6 +457,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" */; @@ -444,7 +500,12 @@ 0867D690FE84028FC02AAC07 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0510; + LastUpgradeCheck = 0720; + TargetAttributes = { + 507FF1D41BC0D7D300104523 = { + CreatedOnToolsVersion = 6.2; + }; + }; }; buildConfigurationList = 1DEB91B108733DA50010E9CD /* Build configuration list for PBXProject "SPMySQLFramework" */; compatibilityVersion = "Xcode 3.2"; @@ -462,11 +523,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 +547,15 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 507FF1D11BC0D7D300104523 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 507FF23D1BC157B500104523 /* SPMySQLStringAdditions_Tests.m in Sources */, + 507FF1E51BC0D82300104523 /* DataConversion_Tests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8DC2EF540486A6940098B216 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -510,6 +588,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 +618,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; @@ -543,6 +628,7 @@ "$(inherited)", "\"$(SRCROOT)/MySQL Client Libraries/lib\"", ); + PRODUCT_BUNDLE_IDENTIFIER = com.sequelpro.spmysql; PRODUCT_NAME = SPMySQL; SKIP_INSTALL = YES; WRAPPER_EXTENSION = framework; @@ -559,7 +645,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; @@ -568,6 +653,7 @@ "$(inherited)", "\"$(SRCROOT)/MySQL Client Libraries/lib\"", ); + PRODUCT_BUNDLE_IDENTIFIER = com.sequelpro.spmysql; PRODUCT_NAME = SPMySQL; SKIP_INSTALL = YES; WRAPPER_EXTENSION = framework; @@ -585,6 +671,7 @@ CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_OPTIMIZATION_LEVEL = 0; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -644,6 +731,203 @@ }; 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 = NO; + 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_BUNDLE_IDENTIFIER = "com.sequelpro.spmysql-unittests"; + 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 = NO; + 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_BUNDLE_IDENTIFIER = "com.sequelpro.spmysql-unittests"; + 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 = NO; + 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_BUNDLE_IDENTIFIER = "com.sequelpro.spmysql-unittests"; + 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_BUNDLE_IDENTIFIER = com.sequelpro.spmysql; + 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 = NO; + 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_BUNDLE_IDENTIFIER = "com.sequelpro.spmysql-unittests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = "Unit Testing"; + }; 586AA55214F5D599007F82BF /* Distribution */ = { isa = XCBuildConfiguration; buildSettings = { @@ -688,7 +972,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; @@ -697,6 +980,7 @@ "$(inherited)", "\"$(SRCROOT)/MySQL Client Libraries/lib\"", ); + PRODUCT_BUNDLE_IDENTIFIER = com.sequelpro.spmysql; PRODUCT_NAME = SPMySQL; SKIP_INSTALL = YES; WRAPPER_EXTENSION = framework; @@ -710,6 +994,7 @@ isa = XCConfigurationList; buildConfigurations = ( 1DEB91AE08733DA50010E9CD /* Debug */, + 507FF2371BC0E0A800104523 /* Unit Testing */, 1DEB91AF08733DA50010E9CD /* Release */, 586AA55314F5D599007F82BF /* Distribution */, ); @@ -720,12 +1005,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 */; diff --git a/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/xcshareddata/xcschemes/SPMySQL.framework.xcscheme b/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/xcshareddata/xcschemes/SPMySQL.framework.xcscheme new file mode 100644 index 00000000..6b85f25b --- /dev/null +++ b/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/xcshareddata/xcschemes/SPMySQL.framework.xcscheme @@ -0,0 +1,99 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "0720" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "8DC2EF4F0486A6940098B216" + BuildableName = "SPMySQL.framework" + BlueprintName = "SPMySQL.framework" + ReferencedContainer = "container:SPMySQLFramework.xcodeproj"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + buildConfiguration = "Unit Testing" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "507FF1D41BC0D7D300104523" + BuildableName = "SPMySQL Unit Tests.xctest" + BlueprintName = "SPMySQL Unit Tests" + ReferencedContainer = "container:SPMySQLFramework.xcodeproj"> + </BuildableReference> + </TestableReference> + </Testables> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "8DC2EF4F0486A6940098B216" + BuildableName = "SPMySQL.framework" + BlueprintName = "SPMySQL.framework" + ReferencedContainer = "container:SPMySQLFramework.xcodeproj"> + </BuildableReference> + </MacroExpansion> + <AdditionalOptions> + </AdditionalOptions> + </TestAction> + <LaunchAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "8DC2EF4F0486A6940098B216" + BuildableName = "SPMySQL.framework" + BlueprintName = "SPMySQL.framework" + ReferencedContainer = "container:SPMySQLFramework.xcodeproj"> + </BuildableReference> + </MacroExpansion> + <AdditionalOptions> + </AdditionalOptions> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "8DC2EF4F0486A6940098B216" + BuildableName = "SPMySQL.framework" + BlueprintName = "SPMySQL.framework" + ReferencedContainer = "container:SPMySQLFramework.xcodeproj"> + </BuildableReference> + </MacroExpansion> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h b/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h index 2419d7a9..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; @@ -99,12 +100,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/SPMySQLConnection Categories/Encoding.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.m index fb949679..76f323bc 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.m @@ -213,7 +213,7 @@ if (!strcmp(mysqlCharset, "utf8")) { return NSUTF8StringEncoding; } else if (!strcmp(mysqlCharset, "latin1")) { - return NSISOLatin1StringEncoding; + return NSWindowsCP1252StringEncoding; // Warning: This is NOT the same as ISO-8859-1 (aka "ISO Latin 1") } else if (!strcmp(mysqlCharset, "ascii")) { return NSASCIIStringEncoding; @@ -289,7 +289,7 @@ } else if (!strcmp(mysqlCharset, "dos")) { return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingDOSLatin1); } else if (!strcmp(mysqlCharset, "german1")) { - return NSISOLatin1StringEncoding; + return NSWindowsCP1252StringEncoding; } else if (!strcmp(mysqlCharset, "usa7")) { return NSASCIIStringEncoding; } else if (!strcmp(mysqlCharset, "danish")) { @@ -313,7 +313,7 @@ } else if (!strcmp(mysqlCharset, "croat")) { return NSISOLatin2StringEncoding; } else if (!strcmp(mysqlCharset, "latin1_de")) { - return NSISOLatin1StringEncoding; + return NSWindowsCP1252StringEncoding; } /** diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h index cff8d43b..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) @@ -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 7443a67e..7940b483 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m @@ -80,8 +80,12 @@ */ - (void)_threadedKeepAlive { + if(keepAliveThread) { + NSLog(@"warning: overwriting existing keepAliveThread: %@, results may be unpredictable!",keepAliveThread); + } + keepAliveThread = [NSThread currentThread]; - [keepAliveThread setName:@"SPMySQL connection keepalive thread"]; + [keepAliveThread setName:@"SPMySQL connection keepalive monitor thread"]; // If the maximum number of ping failures has been reached, determine whether to reconnect. if (keepAliveLastPingBlocked || keepAlivePingFailures >= 3) { @@ -90,16 +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. - keepAliveThread = nil; - return; + goto end_cleanup; } // Otherwise, perform a background ping. @@ -109,6 +112,7 @@ } else { keepAlivePingFailures++; } +end_cleanup: keepAliveThread = nil; } @@ -135,8 +139,13 @@ // 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; + } - keepAliveLastPingSuccess = NO; + volatile BOOL keepAliveLastPingSuccess = NO; keepAliveLastPingBlocked = NO; keepAlivePingThreadActive = YES; @@ -148,12 +157,14 @@ 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 @@ -166,7 +177,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) { @@ -182,6 +193,9 @@ 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; @@ -202,6 +216,8 @@ */ void _backgroundPingTask(void *ptr) { + pthread_setname_np("SPMySQL _backgroundPingTask() worker thread"); + SPMySQLConnectionPingDetails *pingDetails = (SPMySQLConnectionPingDetails *)ptr; // Set up a cleanup routine @@ -236,7 +252,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(); @@ -248,24 +264,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]; + + // 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); + } - - // 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); + + return YES; } @end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m index 48f4fc1e..d96ebe52 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m @@ -58,6 +58,9 @@ * Take a string and escapes any special character for safe use within a query; correctly * escapes any characters within the string using the current connection encoding. * Allows control over whether to also wrap the string in single quotes. + * + * WARNING: This method may return nil if the current thread is cancelled! + * You MUST check the isCancelled flag before using the result! */ - (NSString *)escapeString:(NSString *)theString includingQuotes:(BOOL)includeQuotes { @@ -72,11 +75,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]; @@ -221,6 +225,9 @@ * the connection encoding. * The result type desired can be specified, supporting either standard or streaming * result sets. + * + * WARNING: This method may return nil if the current thread is cancelled! + * You MUST check the isCancelled flag before using the result! */ - (id)queryString:(NSString *)theQueryString usingEncoding:(NSStringEncoding)theEncoding withResultType:(SPMySQLResultType)theReturnType { @@ -288,7 +295,8 @@ // Lock the connection while it's actively in use [self _lockConnection]; - while (queryAttemptsAllowed > 0) { + unsigned long long theAffectedRowCount; + do { // While recording the overall execution time (including network lag!), run // the raw query @@ -296,6 +304,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) { @@ -313,7 +326,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; } } @@ -327,11 +340,10 @@ return nil; } [self _lockConnection]; + NSAssert(mySQLConnection != NULL, @"mySQLConnection has disappeared while checking it!"); - queryAttemptsAllowed--; - } + } while (--queryAttemptsAllowed > 0); - unsigned long long theAffectedRowCount = mysql_affected_rows(mySQLConnection); id theResult = nil; // On success, if there is a query result, retrieve the result data type @@ -660,6 +672,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 * error; if no error is supplied, the connection will be used to derive (or clear) diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h index 82607cdb..8ec6c9e0 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h @@ -45,4 +45,14 @@ - (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. + * + * WARNING: This method may return NO if the current thread is cancelled! + * You MUST check the isCancelled flag before using the result! + */ +- (BOOL)serverShutdown; + @end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m index dd684c78..db846929 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; + unsigned long myver = aMajorVersion * 10000 + aMinorVersion * 100 + aReleaseVersion; - NSUInteger serverMinorVersion = (NSUInteger)[[serverVersionParts objectAtIndex:1] integerValue]; - if (serverMinorVersion < aMinorVersion) return NO; - if (serverMinorVersion > aMinorVersion) return YES; - - NSString *serverReleasePart = [serverVersionParts objectAtIndex:2]; - NSUInteger serverReleaseVersion = (NSUInteger)[[[serverReleasePart componentsSeparatedByString:@"-"] objectAtIndex:0] integerValue]; - if (serverReleaseVersion < aReleaseVersion) return NO; - return YES; + return (serverVersionNumber >= myver); } #pragma mark - @@ -132,6 +98,9 @@ * the resulting process list defaults to the short form; run a manual SHOW FULL PROCESSLIST * to retrieve tasks in non-truncated form. * Returns nil on error. + * + * WARNING: This method may return nil if the current thread is cancelled! + * You MUST check the isCancelled flag before using the result! */ - (SPMySQLResult *)listProcesses { @@ -182,4 +151,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/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h index c65ec2fb..15b809f1 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h @@ -85,10 +85,8 @@ CGFloat keepAliveInterval; uint64_t lastKeepAliveTime; NSUInteger keepAlivePingFailures; - NSThread *keepAliveThread; - pthread_t keepAlivePingThread_t; - BOOL keepAlivePingThreadActive; - BOOL keepAliveLastPingSuccess; + volatile NSThread *keepAliveThread; + volatile BOOL keepAlivePingThreadActive; BOOL keepAliveLastPingBlocked; // Encoding details - and also a record of any previous encoding to allow @@ -101,6 +99,7 @@ // Server details NSString *serverVariableVersion; + unsigned long serverVersionNumber; // Error state for the last query or connection state NSUInteger queryErrorID; @@ -129,6 +128,8 @@ BOOL retryQueriesOnConnectionFailure; SPMySQLClientFlags clientFlags; + + NSString *_debugLastConnectedEvent; } #pragma mark - diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m index 40c95321..286296af 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 @@ -176,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; @@ -200,6 +199,8 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS // while running them retryQueriesOnConnectionFailure = YES; + _debugLastConnectedEvent = nil; + // Start the ping keepalive timer keepAliveTimer = [[SPMySQLKeepAliveTimer alloc] initWithInterval:10 target:self selector:@selector(_keepAlive)]; @@ -254,6 +255,8 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS if (querySqlstate) [querySqlstate release], querySqlstate = nil; [delegateDecisionLock release]; + [_debugLastConnectedEvent release]; + [NSObject cancelPreviousPerformRequestsWithTarget:self]; [super dealloc]; @@ -279,6 +282,9 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS * Error checks extensively - if this method fails, it will ask how to proceed and loop depending * on the status, not returning control until either a connection has been established or * the connection and document have been closed. + * + * WARNING: This method may exit early returning NO if the current thread is cancelled! + * You MUST check the isCancelled flag before using the result! */ - (BOOL)reconnect { @@ -327,10 +333,12 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS * Checks whether the connection to the server is still active. This verifies * the connection using a ping, and if the connection is found to be down attempts * to quickly restore it, including the previous state. + * + * WARNING: This method may return NO if the current thread is cancelled! + * You MUST check the isCancelled flag before using the result! */ - (BOOL)checkConnection { - // If the connection is not seen as active, don't proceed if (state != SPMySQLConnected) return NO; @@ -429,6 +437,14 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS const char *__crashreporter_info__ = NULL; asm(".desc ___crashreporter_info__, 0x10"); +static uint64_t _elapsedMicroSecondsSinceAbsoluteTime(uint64_t comparisonTime) +{ + uint64_t elapsedTime_t = mach_absolute_time() - comparisonTime; + Nanoseconds elapsedTime = AbsoluteToNanoseconds(*(AbsoluteTime *)&(elapsedTime_t)); + + return (UnsignedWideToUInt64(elapsedTime) / 1000ULL); +} + @implementation SPMySQLConnection (PrivateAPI) /** @@ -439,8 +455,11 @@ asm(".desc ___crashreporter_info__, 0x10"); // If a connection is already active in some form, throw an exception if (state != SPMySQLDisconnected && state != SPMySQLConnectionLostInBackground) { - asprintf(&__crashreporter_info__, "Attempted to connect a connection that is not disconnected (SPMySQLConnectionState=%d).", state); - __builtin_trap(); + @synchronized (self) { + uint64_t diff = _elapsedMicroSecondsSinceAbsoluteTime(initialConnectTime); + asprintf(&__crashreporter_info__, "Attempted to connect a connection that is not disconnected (SPMySQLConnectionState=%d).\nIf state==2: Previous connection made %lluµs ago from: %s", state, diff, [_debugLastConnectedEvent cStringUsingEncoding:NSUTF8StringEncoding]); + __builtin_trap(); + } [NSException raise:NSInternalInconsistencyException format:@"Attempted to connect a connection that is not disconnected (SPMySQLConnectionState=%d).", state]; return NO; } @@ -466,17 +485,36 @@ asm(".desc ___crashreporter_info__, 0x10"); // If the connection was cancelled, clean up and don't continue if (userTriggeredDisconnect) { mysql_close(mySQLConnection); - [self _unlockConnection]; mySQLConnection = NULL; + [self _unlockConnection]; return NO; } // Successfully connected - record connected state and reset tracking variables state = SPMySQLConnected; - initialConnectTime = mach_absolute_time(); + + @synchronized (self) { + initialConnectTime = mach_absolute_time(); + [_debugLastConnectedEvent release]; + _debugLastConnectedEvent = [[NSString alloc] initWithFormat:@"thread=%@ stack=%@",[NSThread currentThread],[NSThread callStackSymbols]]; + } + 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; @@ -491,9 +529,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]; @@ -526,10 +562,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. @@ -619,6 +652,9 @@ asm(".desc ___crashreporter_info__, 0x10"); * the connection and document have been closed. * Runs its own autorelease pool as sometimes called in a thread following proxy changes * (where the return code doesn't matter). + * + * WARNING: This method may exit early returning NO if the current thread is cancelled! + * You MUST check the isCancelled flag before using the result! */ - (BOOL)_reconnectAllowingRetries:(BOOL)canRetry { @@ -892,11 +928,15 @@ asm(".desc ___crashreporter_info__, 0x10"); uint64_t disconnectStartTime_t = mach_absolute_time(); while (![self _tryLockConnection]) { usleep(100000); - if (_elapsedSecondsSinceAbsoluteTime(disconnectStartTime_t) > 10) break; + if (_elapsedSecondsSinceAbsoluteTime(disconnectStartTime_t) > 10) { + NSLog(@"%s: Could not acquire connection lock within time limit (10s). Forcing unlock!",__PRETTY_FUNCTION__); + break; + } } [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. @@ -904,17 +944,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; } /** @@ -939,19 +978,32 @@ 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. NSString *retrievedEncoding = @"latin1"; + // character_set_results is the charset the strings received from the server will be in if ([variables objectForKey:@"character_set_results"]) { retrievedEncoding = [variables objectForKey:@"character_set_results"]; - } else if ([variables objectForKey:@"character_set"]) { + } + // not used in 4.1+ (?) + else if ([variables objectForKey:@"character_set"]) { retrievedEncoding = [variables objectForKey:@"character_set"]; } + // character_set_client is the charset the server expects strings transmitted by us to be in + else if ([variables objectForKey:@"character_set_client"]) { + retrievedEncoding = [variables objectForKey:@"character_set_client"]; // fallback for sphinxql + } + // character_set_connection is used internally by the server for comparisons. + // String literals (without a cast) will always be converted from character_set_client to character_set_connection first. + // As an example: + // * Use a client with "SET NAMES utf8" + // * Do a "set @@session.character_set_connection = 'latin1';" + // * Finally try a "SELECT '犬';" (also try "select _utf8'犬';" for completeness) + // * The result will just show a "?" + // So even though we told the server that the client uses utf8 and the results + // should be encoded in utf8, too, the character got lost. + // This happened because the server did a roundtrip of utf8 -> latin1 -> utf8. // Update instance variables if (encoding) [encoding release]; @@ -995,6 +1047,9 @@ asm(".desc ___crashreporter_info__, 0x10"); * each of which requires a round trip to the server - but handles most * network issues. * Returns whether the connection is considered still valid. + * + * WARNING: This method may return NO if the current thread is cancelled! + * You MUST check the isCancelled flag before using the result! */ - (BOOL)_checkConnectionIfNecessary { @@ -1018,21 +1073,22 @@ 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 { - // Check to see whether the handler has already been installed 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); // 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/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 639ff0b9..3b29fb5e 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; @@ -68,6 +78,7 @@ static NSStringEncoding NSFromCFStringEncodingGBK_95; fieldProcessingMap[MYSQL_TYPE_NEWDATE] = SPMySQLResultFieldAsString; fieldProcessingMap[MYSQL_TYPE_VARCHAR] = SPMySQLResultFieldAsString; fieldProcessingMap[MYSQL_TYPE_BIT] = SPMySQLResultFieldAsBit; + fieldProcessingMap[MYSQL_TYPE_JSON] = SPMySQLResultFieldAsString; fieldProcessingMap[MYSQL_TYPE_NEWDECIMAL] = SPMySQLResultFieldAsString; fieldProcessingMap[MYSQL_TYPE_ENUM] = SPMySQLResultFieldAsString; fieldProcessingMap[MYSQL_TYPE_SET] = SPMySQLResultFieldAsString; @@ -161,10 +172,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 +213,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; @@ -209,27 +222,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]; @@ -243,7 +255,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. @@ -414,5 +426,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..ec52e0e3 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 <https://github.com/sequelpro/sequelpro> #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[] = @@ -379,7 +372,7 @@ const SPMySQLResultCharset SPMySQLCharsetMap[] = switch (type) { - case FIELD_TYPE_BIT: + case MYSQL_TYPE_BIT: return @"BIT"; case MYSQL_TYPE_DECIMAL: @@ -482,6 +475,9 @@ const SPMySQLResultCharset SPMySQLCharsetMap[] = case MYSQL_TYPE_GEOMETRY: return @"GEOMETRY"; + + case MYSQL_TYPE_JSON: + return @"JSON"; default: return @"UNKNOWN"; 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:] diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResultStore.h b/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResultStore.h index d66dfd81..5fa6f406 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResultStore.h +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResultStore.h @@ -79,7 +79,7 @@ static inline unsigned long long SPMySQLResultStoreGetRowCount(SPMySQLStreamingR { typedef unsigned long long (*SPMSRSRowCountMethodPtr)(SPMySQLStreamingResultStore*, SEL); static SPMSRSRowCountMethodPtr SPMSRSRowCount; - if (!SPMSRSRowCount) SPMSRSRowCount = (SPMSRSRowCountMethodPtr)[self methodForSelector:@selector(numberOfRows)]; + if (!SPMSRSRowCount) SPMSRSRowCount = (SPMSRSRowCountMethodPtr)[SPMySQLStreamingResultStore instanceMethodForSelector:@selector(numberOfRows)]; return SPMSRSRowCount(self, @selector(numberOfRows)); } @@ -87,7 +87,7 @@ static inline id SPMySQLResultStoreGetRow(SPMySQLStreamingResultStore* self, NSU { typedef id (*SPMSRSRowFetchMethodPtr)(SPMySQLStreamingResultStore*, SEL, NSUInteger); static SPMSRSRowFetchMethodPtr SPMSRSRowFetch; - if (!SPMSRSRowFetch) SPMSRSRowFetch = (SPMSRSRowFetchMethodPtr)[self methodForSelector:@selector(rowContentsAtIndex:)]; + if (!SPMSRSRowFetch) SPMSRSRowFetch = (SPMSRSRowFetchMethodPtr)[SPMySQLStreamingResultStore instanceMethodForSelector:@selector(rowContentsAtIndex:)]; return SPMSRSRowFetch(self, @selector(rowContentsAtIndex:), rowIndex); } @@ -95,7 +95,7 @@ static inline id SPMySQLResultStoreObjectAtRowAndColumn(SPMySQLStreamingResultSt { typedef id (*SPMSRSObjectFetchMethodPtr)(SPMySQLStreamingResultStore*, SEL, NSUInteger, NSUInteger); static SPMSRSObjectFetchMethodPtr SPMSRSObjectFetch; - if (!SPMSRSObjectFetch) SPMSRSObjectFetch = (SPMSRSObjectFetchMethodPtr)[self methodForSelector:@selector(cellDataAtRow:column:)]; + if (!SPMSRSObjectFetch) SPMSRSObjectFetch = (SPMSRSObjectFetchMethodPtr)[SPMySQLStreamingResultStore instanceMethodForSelector:@selector(cellDataAtRow:column:)]; return SPMSRSObjectFetch(self, @selector(cellDataAtRow:column:), rowIndex, colIndex); } @@ -103,6 +103,6 @@ static inline id SPMySQLResultStorePreviewAtRowAndColumn(SPMySQLStreamingResultS { typedef id (*SPMSRSObjectPreviewMethodPtr)(SPMySQLStreamingResultStore*, SEL, NSUInteger, NSUInteger, NSUInteger); static SPMSRSObjectPreviewMethodPtr SPMSRSObjectPreview; - if (!SPMSRSObjectPreview) SPMSRSObjectPreview = (SPMSRSObjectPreviewMethodPtr)[self methodForSelector:@selector(cellPreviewAtRow:column:previewLength:)]; + if (!SPMSRSObjectPreview) SPMSRSObjectPreview = (SPMSRSObjectPreviewMethodPtr)[SPMySQLStreamingResultStore instanceMethodForSelector:@selector(cellPreviewAtRow:column:previewLength:)]; return SPMSRSObjectPreview(self, @selector(cellPreviewAtRow:column:previewLength:), rowIndex, colIndex, previewLength); } 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]; 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 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="748268806"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272761856</int> + <int key="NSCellFlags2">272761920</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <string key="NSPlaceholderString">[sub menu category]</string> @@ -1216,7 +1216,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="43525095"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272761856</int> + <int key="NSCellFlags2">272761920</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <string key="NSPlaceholderString">[menu label tooltip]</string> @@ -1256,7 +1256,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="1025501303"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272761856</int> + <int key="NSCellFlags2">272761920</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <string key="NSPlaceholderString">[menu label name]</string> @@ -1605,7 +1605,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="464624197"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272761856</int> + <int key="NSCellFlags2">272761920</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="623620101"/> @@ -1642,7 +1642,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="561460655"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272761856</int> + <int key="NSCellFlags2">272761920</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="448539706"/> diff --git a/Interfaces/English.lproj/ConnectionView.xib b/Interfaces/English.lproj/ConnectionView.xib index ee6687b8..0baa710e 100644 --- a/Interfaces/English.lproj/ConnectionView.xib +++ b/Interfaces/English.lproj/ConnectionView.xib @@ -753,7 +753,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="859982751"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="807120225"/> <reference key="NSControlView" ref="644973446"/> @@ -813,7 +813,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="1040289660"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="807120225"/> <string key="NSPlaceholderString">optional</string> @@ -853,7 +853,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="925313159"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="807120225"/> <reference key="NSControlView" ref="978463433"/> @@ -892,7 +892,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="646391991"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="807120225"/> <reference key="NSControlView" ref="211629203"/> @@ -931,7 +931,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="925102591"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="807120225"/> <string key="NSPlaceholderString">optional</string> @@ -971,7 +971,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="1003825843"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="807120225"/> <string key="NSPlaceholderString">3306</string> @@ -1286,7 +1286,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="279442436"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="807120225"/> <reference key="NSControlView" ref="68541783"/> @@ -1325,7 +1325,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="251554254"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="807120225"/> <reference key="NSControlView" ref="512253301"/> @@ -1364,7 +1364,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="185246669"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="807120225"/> <string key="NSPlaceholderString">optional</string> @@ -1404,7 +1404,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="299457759"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="807120225"/> <string key="NSPlaceholderString">optional</string> @@ -1444,7 +1444,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="684614893"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="807120225"/> <string key="NSPlaceholderString">optional</string> @@ -1755,7 +1755,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="933547668"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="807120225"/> <reference key="NSControlView" ref="1030226140"/> @@ -1796,7 +1796,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="869330413"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="807120225"/> <reference key="NSControlView" ref="358903996"/> @@ -1837,7 +1837,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="737530250"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="807120225"/> <reference key="NSControlView" ref="187191991"/> @@ -1878,7 +1878,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="289965133"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="807120225"/> <string key="NSPlaceholderString">optional</string> @@ -1920,7 +1920,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="615536496"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="807120225"/> <string key="NSPlaceholderString">3306</string> @@ -1962,7 +1962,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="505035309"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="807120225"/> <reference key="NSControlView" ref="844385120"/> @@ -2003,7 +2003,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="347555313"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="807120225"/> <reference key="NSControlView" ref="790983747"/> @@ -2044,7 +2044,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="944931136"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="807120225"/> <reference key="NSControlView" ref="21480939"/> @@ -2085,7 +2085,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="850057181"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="807120225"/> <string key="NSPlaceholderString">optional</string> @@ -2127,7 +2127,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="833542503"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="807120225"/> <string key="NSPlaceholderString">optional</string> @@ -2214,7 +2214,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="13940444"> <int key="NSCellFlags">-2076180416</int> - <int key="NSCellFlags2">272631296</int> + <int key="NSCellFlags2">272631360</int> <string key="NSContents"/> <reference key="NSSupport" ref="807120225"/> <reference key="NSControlView" ref="180145997"/> 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 @@ <bool key="NSEnabled">YES</bool> <object class="NSSearchFieldCell" key="NSCell" id="220914667"> <int key="NSCellFlags">342884416</int> - <int key="NSCellFlags2">268567552</int> + <int key="NSCellFlags2">268567616</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <string key="NSPlaceholderString">Filter</string> 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 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="474077606"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272761856</int> + <int key="NSCellFlags2">272761920</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <string key="NSPlaceholderString">[no selection]</string> 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 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="254835918"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">-2076048384</int> + <int key="NSCellFlags2">-2076048320</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <object class="NSNumberFormatter" key="NSFormatter" id="582659696"> @@ -374,7 +374,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="929555576"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">67240960</int> + <int key="NSCellFlags2">67241024</int> <integer value="1" key="NSContents"/> <reference key="NSSupport" ref="26"/> <object class="NSNumberFormatter" key="NSFormatter" id="20983387"> diff --git a/Interfaces/English.lproj/DBView.xib b/Interfaces/English.lproj/DBView.xib index 289a9eef..7ffa8fc8 100644 --- a/Interfaces/English.lproj/DBView.xib +++ b/Interfaces/English.lproj/DBView.xib @@ -68,7 +68,7 @@ <string key="NSClassName">NSApplication</string> </object> <object class="NSCustomView" id="173438986"> - <reference key="NSNextResponder"/> + <nil key="NSNextResponder"/> <int key="NSvFlags">274</int> <array class="NSMutableArray" key="NSSubviews"> <object class="NSSplitView" id="310278688"> @@ -218,7 +218,6 @@ </array> <string key="NSFrame">{{1, 1}, {218, 38}}</string> <reference key="NSSuperview" ref="45537637"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="778694607"/> <reference key="NSDocView" ref="778694607"/> <int key="NScvFlags">2</int> @@ -228,7 +227,6 @@ <int key="NSvFlags">-2147483392</int> <string key="NSFrame">{{-100, -100}, {15, 8}}</string> <reference key="NSSuperview" ref="45537637"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="510684108"/> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <reference key="NSTarget" ref="45537637"/> @@ -239,7 +237,6 @@ <int key="NSvFlags">-2147483392</int> <string key="NSFrame">{{-100, -100}, {223, 15}}</string> <reference key="NSSuperview" ref="45537637"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="828528612"/> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <int key="NSsFlags">1</int> @@ -249,7 +246,6 @@ </array> <string key="NSFrame">{{-1, -13}, {220, 40}}</string> <reference key="NSSuperview" ref="695889527"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="36449649"/> <int key="NSsFlags">133122</int> <reference key="NSVScroller" ref="36449649"/> @@ -271,7 +267,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSSearchFieldCell" key="NSCell" id="249281227"> <int key="NSCellFlags">342884417</int> - <int key="NSCellFlags2">268567552</int> + <int key="NSCellFlags2">268567616</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="440427720"/> @@ -414,7 +410,6 @@ </array> <string key="NSFrameSize">{214, 334}</string> <reference key="NSSuperview" ref="737277163"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="720354614"/> <reference key="NSDocView" ref="720354614"/> </object> @@ -423,7 +418,6 @@ <int key="NSvFlags">-2147483392</int> <string key="NSFrame">{{197, 0}, {15, 292}}</string> <reference key="NSSuperview" ref="737277163"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="1061162984"/> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <reference key="NSTarget" ref="737277163"/> @@ -435,7 +429,6 @@ <int key="NSvFlags">-2147483392</int> <string key="NSFrame">{{-100, -100}, {141, 11}}</string> <reference key="NSSuperview" ref="737277163"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="385210161"/> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <int key="NSsFlags">257</int> @@ -445,7 +438,6 @@ </array> <string key="NSFrameSize">{214, 334}</string> <reference key="NSSuperview" ref="217761928"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="769186993"/> <int key="NSsFlags">133648</int> <reference key="NSVScroller" ref="710239831"/> @@ -459,21 +451,18 @@ </array> <string key="NSFrame">{{0, 25}, {214, 334}}</string> <reference key="NSSuperview" ref="152099238"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="737277163"/> <bool key="NSDoNotTranslateAutoresizingMask">YES</bool> </object> </array> <string key="NSFrameSize">{214, 359}</string> <reference key="NSSuperview" ref="315394535"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="695889527"/> <int key="NSDividerStyle">2</int> </object> </array> <string key="NSFrameSize">{214, 359}</string> <reference key="NSSuperview" ref="220052404"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="152099238"/> <bool key="NSDoNotTranslateAutoresizingMask">YES</bool> </object> @@ -560,7 +549,6 @@ </array> <string key="NSFrameSize">{216, 166}</string> <reference key="NSSuperview" ref="883675819"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="828624540"/> <reference key="NSDocView" ref="828624540"/> <int key="NScvFlags">2</int> @@ -570,7 +558,6 @@ <int key="NSvFlags">-2147483392</int> <string key="NSFrame">{{-100, -100}, {15, 20}}</string> <reference key="NSSuperview" ref="883675819"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="321115393"/> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <reference key="NSTarget" ref="883675819"/> @@ -581,7 +568,6 @@ <int key="NSvFlags">-2147483392</int> <string key="NSFrame">{{-100, -100}, {141, 11}}</string> <reference key="NSSuperview" ref="883675819"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="5721858"/> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <int key="NSsFlags">257</int> @@ -591,7 +577,6 @@ </array> <string key="NSFrame">{{-1, 0}, {216, 166}}</string> <reference key="NSSuperview" ref="1061162984"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="29986399"/> <int key="NSsFlags">133648</int> <reference key="NSVScroller" ref="29986399"/> @@ -681,7 +666,6 @@ </array> <string key="NSFrameSize">{214, 166}</string> <reference key="NSSuperview" ref="980722778"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="366652030"/> <reference key="NSDocView" ref="366652030"/> <int key="NScvFlags">2</int> @@ -691,7 +675,6 @@ <int key="NSvFlags">-2147483392</int> <string key="NSFrame">{{-100, -100}, {15, 20}}</string> <reference key="NSSuperview" ref="980722778"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="562900810"/> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <reference key="NSTarget" ref="980722778"/> @@ -703,7 +686,6 @@ <int key="NSvFlags">-2147483392</int> <string key="NSFrame">{{-100, -100}, {141, 11}}</string> <reference key="NSSuperview" ref="980722778"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="725874926"/> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <int key="NSsFlags">257</int> @@ -713,7 +695,6 @@ </array> <string key="NSFrameSize">{214, 166}</string> <reference key="NSSuperview" ref="1061162984"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="735507568"/> <int key="NSsFlags">133632</int> <reference key="NSVScroller" ref="735507568"/> @@ -727,14 +708,12 @@ </array> <string key="NSFrame">{{0, 360}, {214, 166}}</string> <reference key="NSSuperview" ref="220052404"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="883675819"/> <bool key="NSDoNotTranslateAutoresizingMask">YES</bool> </object> </array> <string key="NSFrame">{{0, 23}, {214, 526}}</string> <reference key="NSSuperview" ref="841205379"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="315394535"/> <int key="NSDividerStyle">2</int> </object> @@ -1127,7 +1106,6 @@ </array> <string key="NSFrameSize">{214, 549}</string> <reference key="NSSuperview" ref="310278688"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="99671504"/> <bool key="NSDoNotTranslateAutoresizingMask">YES</bool> </object> @@ -1140,13 +1118,12 @@ <int key="NSvFlags">274</int> <string key="NSFrame">{{-7, -10}, {741, 564}}</string> <reference key="NSSuperview" ref="630926746"/> - <reference key="NSWindow"/> - <reference key="NSNextKeyView" ref="278510490"/> + <reference key="NSNextKeyView" ref="113573997"/> <array class="NSMutableArray" key="NSTabViewItems"> <object class="NSTabViewItem" id="449203689"> <string key="NSIdentifier">source</string> <object class="NSView" key="NSView" id="113573997"> - <nil key="NSNextResponder"/> + <reference key="NSNextResponder" ref="280187924"/> <int key="NSvFlags">274</int> <array class="NSMutableArray" key="NSSubviews"> <object class="NSSplitView" id="204281121"> @@ -1196,7 +1173,7 @@ <int key="NSvFlags">4352</int> <string key="NSFrameSize">{694, 288}</string> <reference key="NSSuperview" ref="971612446"/> - <reference key="NSNextKeyView" ref="829483012"/> + <reference key="NSNextKeyView" ref="1057730379"/> <bool key="NSEnabled">YES</bool> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <bool key="NSControlAllowsExpansionToolTips">YES</bool> @@ -1820,7 +1797,7 @@ </array> <string key="NSFrame">{{-1, 22}, {696, 306}}</string> <reference key="NSSuperview" ref="981492694"/> - <reference key="NSNextKeyView" ref="971612446"/> + <reference key="NSNextKeyView" ref="829483012"/> <int key="NSsFlags">133682</int> <reference key="NSVScroller" ref="346829497"/> <reference key="NSHScroller" ref="1057730379"/> @@ -2112,7 +2089,7 @@ <object class="NSTextField" id="82207312"> <reference key="NSNextResponder" ref="955470299"/> <int key="NSvFlags">268</int> - <string key="NSFrame">{{3, 3}, {58, 14}}</string> + <string key="NSFrame">{{3, 3}, {155, 14}}</string> <reference key="NSSuperview" ref="955470299"/> <reference key="NSNextKeyView" ref="362266093"/> <bool key="NSEnabled">YES</bool> @@ -2186,7 +2163,7 @@ <int key="NSvFlags">4352</int> <string key="NSFrameSize">{694, 141}</string> <reference key="NSSuperview" ref="472300297"/> - <reference key="NSNextKeyView" ref="542930762"/> + <reference key="NSNextKeyView" ref="673560219"/> <bool key="NSEnabled">YES</bool> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <bool key="NSControlAllowsExpansionToolTips">YES</bool> @@ -2506,7 +2483,7 @@ </array> <string key="NSFrame">{{-1, 22}, {696, 159}}</string> <reference key="NSSuperview" ref="162304252"/> - <reference key="NSNextKeyView" ref="472300297"/> + <reference key="NSNextKeyView" ref="542930762"/> <int key="NSsFlags">133682</int> <reference key="NSVScroller" ref="673560219"/> <reference key="NSHScroller" ref="230182748"/> @@ -2600,7 +2577,6 @@ </set> <string key="NSFrame">{{93, 0}, {602, 23}}</string> <reference key="NSSuperview" ref="162304252"/> - <reference key="NSNextKeyView" ref="280187924"/> <bool key="NSEnabled">YES</bool> <object class="NSImageCell" key="NSCell" id="153461887"> <int key="NSCellFlags">0</int> @@ -2629,6 +2605,7 @@ </object> </array> <string key="NSFrame">{{10, 7}, {706, 544}}</string> + <reference key="NSSuperview" ref="280187924"/> <reference key="NSNextKeyView" ref="204281121"/> </object> <string key="NSLabel">Structure</string> @@ -3293,7 +3270,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSSearchFieldCell" key="NSCell" id="837725116"> <int key="NSCellFlags">879755329</int> - <int key="NSCellFlags2">268567552</int> + <int key="NSCellFlags2">268567616</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="426625670"/> @@ -3732,7 +3709,7 @@ <int key="NSvFlags">6418</int> <string key="NSFrameSize">{694, 141}</string> <reference key="NSSuperview" ref="814502469"/> - <reference key="NSNextKeyView" ref="1058351455"/> + <reference key="NSNextKeyView" ref="103288676"/> <object class="NSTextContainer" key="NSTextContainer" id="104432314"> <object class="NSLayoutManager" key="NSLayoutManager"> <object class="NSTextStorage" key="NSTextStorage"> @@ -3824,7 +3801,7 @@ </array> <string key="NSFrame">{{0, 1}, {696, 143}}</string> <reference key="NSSuperview" ref="154150014"/> - <reference key="NSNextKeyView" ref="103288676"/> + <reference key="NSNextKeyView" ref="814502469"/> <int key="NSsFlags">133650</int> <reference key="NSVScroller" ref="1058351455"/> <reference key="NSHScroller" ref="103288676"/> @@ -3950,7 +3927,7 @@ <int key="NSvFlags">4352</int> <string key="NSFrameSize">{694, 197}</string> <reference key="NSSuperview" ref="620500575"/> - <reference key="NSNextKeyView" ref="1031686395"/> + <reference key="NSNextKeyView" ref="621197929"/> <bool key="NSEnabled">YES</bool> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <bool key="NSControlAllowsExpansionToolTips">YES</bool> @@ -4061,13 +4038,12 @@ </array> <string key="NSFrame">{{0, -1}, {696, 215}}</string> <reference key="NSSuperview" ref="1045056406"/> - <reference key="NSNextKeyView" ref="621197929"/> + <reference key="NSNextKeyView" ref="620500575"/> <int key="NSsFlags">133682</int> <reference key="NSVScroller" ref="556042083"/> <reference key="NSHScroller" ref="1031686395"/> <reference key="NSContentView" ref="620500575"/> <reference key="NSHeaderClipView" ref="730166708"/> - <reference key="NSCornerView" ref="621197929"/> <bytes key="NSScrollAmts">QSAAAEEgAABBkAAAQZAAAA</bytes> <double key="NSMinMagnification">0.25</double> <double key="NSMaxMagnification">4</double> @@ -4683,7 +4659,7 @@ <int key="NSvFlags">2322</int> <string key="NSFrameSize">{672, 72}</string> <reference key="NSSuperview" ref="724030266"/> - <reference key="NSNextKeyView" ref="622087604"/> + <reference key="NSNextKeyView" ref="865629592"/> <object class="NSTextContainer" key="NSTextContainer" id="545876225"> <object class="NSLayoutManager" key="NSLayoutManager"> <object class="NSTextStorage" key="NSTextStorage"> @@ -4759,7 +4735,7 @@ </array> <string key="NSFrame">{{12, 12}, {672, 72}}</string> <reference key="NSSuperview" ref="105597532"/> - <reference key="NSNextKeyView" ref="865629592"/> + <reference key="NSNextKeyView" ref="724030266"/> <int key="NSsFlags">133648</int> <reference key="NSVScroller" ref="622087604"/> <reference key="NSHScroller" ref="865629592"/> @@ -4810,7 +4786,7 @@ <object class="NSTabViewItem" id="618922025"> <string key="NSIdentifier">status</string> <object class="NSView" key="NSView" id="278510490"> - <reference key="NSNextResponder" ref="280187924"/> + <nil key="NSNextResponder"/> <int key="NSvFlags">274</int> <array class="NSMutableArray" key="NSSubviews"> <object class="NSSplitView" id="420718918"> @@ -4899,7 +4875,6 @@ </array> <string key="NSFrame">{{1, 1}, {541, 70}}</string> <reference key="NSSuperview" ref="834551573"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="902567272"/> <reference key="NSDocView" ref="902567272"/> <reference key="NSBGColor" ref="457906098"/> @@ -4911,7 +4886,6 @@ <int key="NSvFlags">-2147483392</int> <string key="NSFrame">{{-100, -100}, {87, 18}}</string> <reference key="NSSuperview" ref="834551573"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="378663488"/> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <int key="NSsFlags">1</int> @@ -4924,7 +4898,6 @@ <int key="NSvFlags">256</int> <string key="NSFrame">{{542, 1}, {11, 70}}</string> <reference key="NSSuperview" ref="834551573"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="807082629"/> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <int key="NSsFlags">256</int> @@ -4934,7 +4907,6 @@ </array> <string key="NSFrame">{{109, 0}, {554, 72}}</string> <reference key="NSSuperview" ref="697228900"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="301562599"/> <int key="NSsFlags">133138</int> <reference key="NSVScroller" ref="582113763"/> @@ -4949,7 +4921,6 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{4, 58}, {100, 14}}</string> <reference key="NSSuperview" ref="697228900"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="834551573"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="191751479"> @@ -5035,7 +5006,6 @@ </array> <string key="NSFrame">{{1, 1}, {541, 197}}</string> <reference key="NSSuperview" ref="866412611"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="804444600"/> <reference key="NSDocView" ref="804444600"/> <reference key="NSBGColor" ref="457906098"/> @@ -5047,7 +5017,6 @@ <int key="NSvFlags">-2147483392</int> <string key="NSFrame">{{-100, -100}, {87, 18}}</string> <reference key="NSSuperview" ref="866412611"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="121595321"/> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <int key="NSsFlags">1</int> @@ -5060,8 +5029,6 @@ <int key="NSvFlags">256</int> <string key="NSFrame">{{542, 1}, {11, 197}}</string> <reference key="NSSuperview" ref="866412611"/> - <reference key="NSWindow"/> - <reference key="NSNextKeyView"/> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <int key="NSsFlags">256</int> <reference key="NSTarget" ref="866412611"/> @@ -5070,7 +5037,6 @@ </array> <string key="NSFrame">{{109, 0}, {554, 199}}</string> <reference key="NSSuperview" ref="807082629"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="492861350"/> <int key="NSsFlags">133138</int> <reference key="NSVScroller" ref="486433207"/> @@ -5085,7 +5051,6 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{3, 185}, {101, 14}}</string> <reference key="NSSuperview" ref="807082629"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="866412611"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="456332788"> @@ -5103,7 +5068,6 @@ </array> <string key="NSFrame">{{0, 81}, {663, 199}}</string> <reference key="NSSuperview" ref="420718918"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="473337782"/> <bool key="NSDoNotTranslateAutoresizingMask">YES</bool> <string key="NSClassName">NSView</string> @@ -5111,7 +5075,6 @@ </array> <string key="NSFrame">{{12, 30}, {663, 280}}</string> <reference key="NSSuperview" ref="278510490"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="697228900"/> <string key="NSAutosaveName">TableInfoSplitter</string> </object> @@ -5120,7 +5083,6 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{22, 501}, {91, 14}}</string> <reference key="NSSuperview" ref="278510490"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="830707723"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="46798413"> @@ -5441,79 +5403,6 @@ <int key="NSCellFlags">68157504</int> <int key="NSCellFlags2">272761856</int> <reference key="NSSupport" ref="26"/> - <object class="NSNumberFormatter" key="NSFormatter" id="597232718"> - <dictionary class="NSMutableDictionary" key="NS.attributes"> - <boolean value="YES" key="allowsFloats"/> - <boolean value="NO" key="alwaysShowsDecimalSeparator"/> - <string key="currencyDecimalSeparator">.</string> - <string key="decimalSeparator">.</string> - <string key="exponentSymbol">E</string> - <integer value="0" key="formatWidth"/> - <integer value="1040" key="formatterBehavior"/> - <boolean value="NO" key="generatesDecimalNumbers"/> - <string key="groupingSeparator">,</string> - <integer value="3" key="groupingSize"/> - <boolean value="YES" key="lenient"/> - <integer value="3" key="maximumFractionDigits"/> - <integer value="309" key="maximumIntegerDigits"/> - <real value="1" key="minimum"/> - <integer value="0" key="minimumFractionDigits"/> - <integer value="1" key="minimumIntegerDigits"/> - <string key="minusSign">-</string> - <string key="negativeFormat">#,##0.###</string> - <string key="negativeInfinitySymbol">-∞</string> - <string key="negativePrefix">-</string> - <string key="negativeSuffix"/> - <string key="nilSymbol"/> - <string key="notANumberSymbol">NaN</string> - <integer value="1" key="numberStyle"/> - <string key="paddingCharacter">*</string> - <integer value="0" key="paddingPosition"/> - <string key="perMillSymbol">‰</string> - <string key="percentSymbol">%</string> - <string key="plusSign">+</string> - <string key="positiveFormat">#,##0.###</string> - <string key="positiveInfinitySymbol">+∞</string> - <string key="positivePrefix"/> - <string key="positiveSuffix"/> - <real value="0.0" key="roundingIncrement"/> - <integer value="4" key="roundingMode"/> - <integer value="0" key="secondaryGroupingSize"/> - <boolean value="YES" key="usesGroupingSeparator"/> - </dictionary> - <string key="NS.positiveformat">#,##0.###</string> - <string key="NS.negativeformat">#,##0.###</string> - <nil key="NS.positiveattrs"/> - <nil key="NS.negativeattrs"/> - <nil key="NS.zero"/> - <object class="NSAttributedString" key="NS.nil"> - <string key="NSString"/> - </object> - <object class="NSAttributedString" key="NS.nan"> - <string key="NSString">NaN</string> - <dictionary key="NSAttributes" id="10305650"/> - </object> - <real value="1" key="NS.min"/> - <object class="NSDecimalNumberPlaceholder" key="NS.max" id="581212392"> - <int key="NS.exponent">0</int> - <int key="NS.length">0</int> - <bool key="NS.negative">YES</bool> - <bool key="NS.compact">NO</bool> - <int key="NS.mantissa.bo">1</int> - <bytes key="NS.mantissa">AAAAAAAAAAAAAAAAAAAAAA</bytes> - </object> - <object class="NSDecimalNumberHandler" key="NS.rounding"> - <int key="NS.roundingmode">3</int> - <bool key="NS.raise.overflow">YES</bool> - <bool key="NS.raise.underflow">YES</bool> - <bool key="NS.raise.dividebyzero">YES</bool> - </object> - <string key="NS.decimal">.</string> - <string key="NS.thousand">,</string> - <bool key="NS.hasthousands">YES</bool> - <bool key="NS.localized">YES</bool> - <bool key="NS.allowsfloats">YES</bool> - </object> <reference key="NSControlView" ref="735808692"/> <reference key="NSBackgroundColor" ref="647098198"/> <reference key="NSTextColor" ref="667308713"/> @@ -5878,8 +5767,6 @@ </object> </array> <string key="NSFrame">{{10, 7}, {706, 544}}</string> - <reference key="NSSuperview" ref="280187924"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="592029739"/> </object> <string key="NSLabel">Status</string> @@ -6734,26 +6621,24 @@ <reference key="NSTabView" ref="280187924"/> </object> </array> - <reference key="NSSelectedTabViewItem" ref="618922025"/> + <reference key="NSSelectedTabViewItem" ref="449203689"/> <reference key="NSFont" ref="26"/> <int key="NSTvFlags">134217731</int> <bool key="NSAllowTruncatedLabels">YES</bool> <bool key="NSDrawsBackground">YES</bool> <array class="NSMutableArray" key="NSSubviews"> - <reference ref="278510490"/> + <reference ref="113573997"/> </array> </object> </array> <string key="NSFrame">{{215, 0}, {728, 549}}</string> <reference key="NSSuperview" ref="310278688"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="280187924"/> <bool key="NSDoNotTranslateAutoresizingMask">YES</bool> </object> </array> <string key="NSFrameSize">{943, 549}</string> <reference key="NSSuperview" ref="173438986"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="841205379"/> <bool key="NSIsVertical">YES</bool> <int key="NSDividerStyle">2</int> @@ -6761,8 +6646,6 @@ </object> </array> <string key="NSFrameSize">{943, 549}</string> - <reference key="NSSuperview"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="310278688"/> <string key="NSClassName">NSView</string> </object> @@ -6963,7 +6846,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="329948835"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272761856</int> + <int key="NSCellFlags2">272761920</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <string key="NSCellIdentifier">_NS:9</string> @@ -7209,7 +7092,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="1027522412"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">4326400</int> + <int key="NSCellFlags2">4326464</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="677823009"/> @@ -7402,7 +7285,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="492882069"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">4326400</int> + <int key="NSCellFlags2">4326464</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="910445056"/> @@ -7474,14 +7357,14 @@ <object class="NSWindowTemplate" id="971880172"> <int key="NSWindowStyleMask">9</int> <int key="NSWindowBacking">2</int> - <string key="NSWindowRect">{{343, 433}, {384, 162}}</string> + <string key="NSWindowRect">{{343, 433}, {425, 162}}</string> <int key="NSWTFlags">1886912512</int> <string key="NSWindowTitle">New Table</string> <string key="NSWindowClass">NSWindow</string> <nil key="NSViewClass"/> <nil key="NSUserInterfaceItemIdentifier"/> <string key="NSWindowContentMaxSize">{600, 162}</string> - <string key="NSWindowContentMinSize">{384, 142}</string> + <string key="NSWindowContentMinSize">{425, 162}</string> <object class="NSView" key="NSWindowView" id="381160134"> <nil key="NSNextResponder"/> <int key="NSvFlags">256</int> @@ -7527,13 +7410,13 @@ <object class="NSTextField" id="374259664"> <reference key="NSNextResponder" ref="381160134"/> <int key="NSvFlags">258</int> - <string key="NSFrame">{{138, 124}, {226, 18}}</string> + <string key="NSFrame">{{138, 124}, {267, 18}}</string> <reference key="NSSuperview" ref="381160134"/> <reference key="NSNextKeyView" ref="513414376"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="476637769"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">4326400</int> + <int key="NSCellFlags2">4326464</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="374259664"/> @@ -7547,7 +7430,7 @@ <object class="NSButton" id="671615720"> <reference key="NSNextResponder" ref="381160134"/> <int key="NSvFlags">257</int> - <string key="NSFrame">{{291, 13}, {78, 28}}</string> + <string key="NSFrame">{{332, 13}, {78, 28}}</string> <reference key="NSSuperview" ref="381160134"/> <string key="NSHuggingPriority">{250, 750}</string> <int key="NSTag">1</int> @@ -7572,7 +7455,7 @@ <object class="NSPopUpButton" id="1050266816"> <reference key="NSNextResponder" ref="381160134"/> <int key="NSvFlags">258</int> - <string key="NSFrame">{{135, 95}, {232, 22}}</string> + <string key="NSFrame">{{135, 95}, {273, 22}}</string> <reference key="NSSuperview" ref="381160134"/> <reference key="NSNextKeyView" ref="289339603"/> <bool key="NSEnabled">YES</bool> @@ -7624,7 +7507,7 @@ <object class="NSPopUpButton" id="467430961"> <reference key="NSNextResponder" ref="381160134"/> <int key="NSvFlags">258</int> - <string key="NSFrame">{{135, 70}, {232, 22}}</string> + <string key="NSFrame">{{135, 70}, {273, 22}}</string> <reference key="NSSuperview" ref="381160134"/> <reference key="NSNextKeyView" ref="725803993"/> <bool key="NSEnabled">YES</bool> @@ -7676,7 +7559,7 @@ <object class="NSPopUpButton" id="189903457"> <reference key="NSNextResponder" ref="381160134"/> <int key="NSvFlags">258</int> - <string key="NSFrame">{{135, 45}, {232, 22}}</string> + <string key="NSFrame">{{135, 45}, {273, 22}}</string> <reference key="NSSuperview" ref="381160134"/> <reference key="NSNextKeyView" ref="635170907"/> <bool key="NSEnabled">YES</bool> @@ -7709,7 +7592,7 @@ <object class="NSButton" id="635170907"> <reference key="NSNextResponder" ref="381160134"/> <int key="NSvFlags">257</int> - <string key="NSFrame">{{209, 13}, {84, 28}}</string> + <string key="NSFrame">{{250, 13}, {84, 28}}</string> <reference key="NSSuperview" ref="381160134"/> <reference key="NSNextKeyView" ref="671615720"/> <string key="NSHuggingPriority">{250, 750}</string> @@ -7731,7 +7614,7 @@ <bool key="NSAllowsLogicalLayoutDirection">NO</bool> </object> </array> - <string key="NSFrameSize">{384, 162}</string> + <string key="NSFrameSize">{425, 162}</string> <reference key="NSNextKeyView" ref="1005413998"/> </object> <string key="NSScreenRect">{{0, 0}, {1920, 1178}}</string> @@ -7856,7 +7739,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="658680247"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272761856</int> + <int key="NSCellFlags2">272761920</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <string key="NSCellIdentifier">_NS:9</string> @@ -8128,7 +8011,7 @@ IGRvIHlvdSB3YW50IHRvIGFkZCBmb3IgdGhpcyBmaWVsZD8</string> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="725373559"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">4326400</int> + <int key="NSCellFlags2">4326464</int> <reference key="NSSupport" ref="26"/> <object class="NSNumberFormatter" key="NSFormatter" id="354814181"> <dictionary class="NSMutableDictionary" key="NS.attributes"> @@ -8180,10 +8063,17 @@ IGRvIHlvdSB3YW50IHRvIGFkZCBmb3IgdGhpcyBmaWVsZD8</string> </object> <object class="NSAttributedString" key="NS.nan"> <string key="NSString">NaN</string> - <reference key="NSAttributes" ref="10305650"/> + <dictionary key="NSAttributes"/> </object> <real value="1" key="NS.min"/> - <reference key="NS.max" ref="581212392"/> + <object class="NSDecimalNumberPlaceholder" key="NS.max"> + <int key="NS.exponent">0</int> + <int key="NS.length">0</int> + <bool key="NS.negative">YES</bool> + <bool key="NS.compact">NO</bool> + <int key="NS.mantissa.bo">1</int> + <bytes key="NS.mantissa">AAAAAAAAAAAAAAAAAAAAAA</bytes> + </object> <object class="NSDecimalNumberHandler" key="NS.rounding"> <int key="NS.roundingmode">3</int> <bool key="NS.raise.overflow">YES</bool> @@ -8412,7 +8302,7 @@ IGRvIHlvdSB3YW50IHRvIGFkZCBmb3IgdGhpcyBmaWVsZD8</string> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="697349750"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272761856</int> + <int key="NSCellFlags2">272761920</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <string key="NSPlaceholderString">Generate one</string> @@ -9062,7 +8952,7 @@ IGRvIHlvdSB3YW50IHRvIGFkZCBmb3IgdGhpcyBmaWVsZD8</string> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="118322854"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272761856</int> + <int key="NSCellFlags2">272761920</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="7465336"/> @@ -9118,6 +9008,7 @@ IGRvIHlvdSB3YW50IHRvIGFkZCBmb3IgdGhpcyBmaWVsZD8</string> <reference key="NSOnImage" ref="803427867"/> <reference key="NSMixedImage" ref="245717585"/> <string key="NSAction">_popUpItemAction:</string> + <int key="NSTag">1</int> <reference key="NSTarget" ref="556116646"/> </object> </array> @@ -9174,6 +9065,7 @@ IGRvIHlvdSB3YW50IHRvIGFkZCBmb3IgdGhpcyBmaWVsZD8</string> <reference key="NSOnImage" ref="803427867"/> <reference key="NSMixedImage" ref="245717585"/> <string key="NSAction">_popUpItemAction:</string> + <int key="NSTag">1</int> <reference key="NSTarget" ref="468443756"/> </object> <object class="NSMenuItem" id="766167700"> @@ -9185,6 +9077,7 @@ IGRvIHlvdSB3YW50IHRvIGFkZCBmb3IgdGhpcyBmaWVsZD8</string> <reference key="NSOnImage" ref="803427867"/> <reference key="NSMixedImage" ref="245717585"/> <string key="NSAction">_popUpItemAction:</string> + <int key="NSTag">2</int> <reference key="NSTarget" ref="468443756"/> </object> </array> @@ -10183,7 +10076,7 @@ IGRvIHlvdSB3YW50IHRvIGFkZCBmb3IgdGhpcyBmaWVsZD8</string> <bool key="NSEnabled">YES</bool> <object class="NSSearchFieldCell" key="NSCell" id="643565822"> <int key="NSCellFlags">341835841</int> - <int key="NSCellFlags2">268568576</int> + <int key="NSCellFlags2">268568640</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <string key="NSPlaceholderString">Search</string> @@ -11311,7 +11204,7 @@ IGRvIHlvdSB3YW50IHRvIGFkZCBmb3IgdGhpcyBmaWVsZD8</string> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="685985166"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">4326400</int> + <int key="NSCellFlags2">4326464</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="852380709"/> @@ -11474,7 +11367,7 @@ IGRvIHlvdSB3YW50IHRvIGFkZCBmb3IgdGhpcyBmaWVsZD8</string> <bool key="NSEnabled">YES</bool> <object class="NSSecureTextFieldCell" key="NSCell" id="529004113"> <int key="NSCellFlags">342884417</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="71602814"/> <reference key="NSControlView" ref="205502925"/> @@ -11962,7 +11855,7 @@ IGRvIHlvdSB3YW50IHRvIGFkZCBmb3IgdGhpcyBmaWVsZD8</string> <bool key="NSEnabled">YES</bool> <object class="NSSearchFieldCell" key="NSCell" id="502741268"> <int key="NSCellFlags">342884417</int> - <int key="NSCellFlags2">272794624</int> + <int key="NSCellFlags2">272794688</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <string key="NSPlaceholderString">Filter</string> @@ -12025,7 +11918,7 @@ IGRvIHlvdSB3YW50IHRvIGFkZCBmb3IgdGhpcyBmaWVsZD8</string> <bool key="NSEnabled">YES</bool> <object class="NSSearchFieldCell" key="NSCell" id="3613839"> <int key="NSCellFlags">342884417</int> - <int key="NSCellFlags2">272794624</int> + <int key="NSCellFlags2">272794688</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <string key="NSPlaceholderString">Filter</string> @@ -12594,7 +12487,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> </object> </array> </array> - <object class="NSColor" key="NSColor"> + <object class="NSColor" key="NSColor" id="114175458"> <int key="NSColorSpace">3</int> <bytes key="NSWhite">MCAwAA</bytes> </object> @@ -12669,6 +12562,161 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <string key="NSReuseIdentifierKey">_NS:9</string> <string key="NSClassName">NSView</string> </object> + <object class="NSWindowTemplate" id="746008569"> + <int key="NSWindowStyleMask">145</int> + <int key="NSWindowBacking">2</int> + <string key="NSWindowRect">{{196, 132}, {410, 165}}</string> + <int key="NSWTFlags">-461896704</int> + <string key="NSWindowTitle">Type Info</string> + <string key="NSWindowClass">NSPanel</string> + <nil key="NSViewClass"/> + <nil key="NSUserInterfaceItemIdentifier"/> + <object class="NSView" key="NSWindowView" id="343760123"> + <nil key="NSNextResponder"/> + <int key="NSvFlags">256</int> + <array class="NSMutableArray" key="NSSubviews"> + <object class="NSScrollView" id="417749005"> + <reference key="NSNextResponder" ref="343760123"/> + <int key="NSvFlags">274</int> + <array class="NSMutableArray" key="NSSubviews"> + <object class="NSClipView" id="863539615"> + <reference key="NSNextResponder" ref="417749005"/> + <int key="NSvFlags">2322</int> + <array class="NSMutableArray" key="NSSubviews"> + <object class="NSTextView" id="81696073"> + <reference key="NSNextResponder" ref="863539615"/> + <int key="NSvFlags">2322</int> + <string key="NSFrameSize">{384, 152}</string> + <reference key="NSSuperview" ref="863539615"/> + <reference key="NSNextKeyView" ref="21184185"/> + <string key="NSReuseIdentifierKey">_NS:13</string> + <object class="NSTextContainer" key="NSTextContainer" id="453813367"> + <object class="NSLayoutManager" key="NSLayoutManager"> + <object class="NSTextStorage" key="NSTextStorage"> + <object class="NSMutableString" key="NSString"> + <characters key="NS.bytes">Help dummy text</characters> + </object> + <dictionary key="NSAttributes"> + <object class="NSFont" key="NSFont"> + <string key="NSName">Helvetica</string> + <double key="NSSize">12</double> + <int key="NSfFlags">16</int> + </object> + <object class="NSMutableParagraphStyle" key="NSParagraphStyle"> + <nil key="NSTabStops"/> + </object> + </dictionary> + <nil key="NSDelegate"/> + </object> + <array class="NSMutableArray" key="NSTextContainers"> + <reference ref="453813367"/> + </array> + <int key="NSLMFlags">166</int> + <nil key="NSDelegate"/> + </object> + <reference key="NSTextView" ref="81696073"/> + <double key="NSWidth">384</double> + <int key="NSTCFlags">1</int> + </object> + <object class="NSTextViewSharedData" key="NSSharedData"> + <int key="NSFlags">10273</int> + <int key="NSTextCheckingTypes">0</int> + <nil key="NSMarkedAttributes"/> + <reference key="NSBackgroundColor" ref="457906098"/> + <dictionary key="NSSelectedAttributes"> + <reference key="NSBackgroundColor" ref="695342849"/> + <reference key="NSColor" ref="294465151"/> + </dictionary> + <reference key="NSInsertionColor" ref="667308713"/> + <dictionary key="NSLinkAttributes"> + <reference key="NSColor" ref="618433734"/> + <reference key="NSCursor" ref="113216199"/> + <integer value="1" key="NSUnderline"/> + </dictionary> + <nil key="NSDefaultParagraphStyle"/> + <nil key="NSTextFinder"/> + <int key="NSPreferredTextFinderStyle">1</int> + </object> + <int key="NSTVFlags">6</int> + <string key="NSMaxSize">{463, 10000000}</string> + <nil key="NSDelegate"/> + </object> + </array> + <string key="NSFrameSize">{384, 152}</string> + <reference key="NSSuperview" ref="417749005"/> + <reference key="NSNextKeyView" ref="81696073"/> + <string key="NSReuseIdentifierKey">_NS:11</string> + <reference key="NSDocView" ref="81696073"/> + <reference key="NSBGColor" ref="457906098"/> + <object class="NSCursor" key="NSCursor"> + <string key="NSHotSpot">{4, 5}</string> + <object class="NSImage" key="NSImage"> + <int key="NSImageFlags">12582912</int> + <array class="NSMutableArray" key="NSReps"> + <array> + <integer value="0"/> + <object class="NSBitmapImageRep"> + <object class="NSData" key="NSTIFFRepresentation"> + <bytes key="NS.bytes">TU0AKgAAAHCAFUqgBVKsAAAAwdVQUqwaEQeIRGJRGFlYqwWLQ+JxuOQpVRmEx2RROKwOQyOUQSPyaUym +SxqWyKXyeYxyZzWbSuJTScRCbz2Nz+gRKhUOfTqeUai0OSxiWTiBQSHSGFquGwekxyAgAAAOAQAAAwAA +AAEAEAAAAQEAAwAAAAEAEAAAAQIAAwAAAAIACAAIAQMAAwAAAAEABQAAAQYAAwAAAAEAAQAAAREABAAA +AAEAAAAIARIAAwAAAAEAAQAAARUAAwAAAAEAAgAAARYAAwAAAAEAEAAAARcABAAAAAEAAABnARwAAwAA +AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> + </object> + </object> + </array> + </array> + <reference key="NSColor" ref="114175458"/> + </object> + </object> + <int key="NScvFlags">2</int> + </object> + <object class="NSScroller" id="1022049165"> + <reference key="NSNextResponder" ref="417749005"/> + <int key="NSvFlags">-2147483392</int> + <string key="NSFrame">{{-100, -100}, {15, 133}}</string> + <reference key="NSSuperview" ref="417749005"/> + <string key="NSReuseIdentifierKey">_NS:85</string> + <bool key="NSAllowsLogicalLayoutDirection">NO</bool> + <reference key="NSTarget" ref="417749005"/> + <string key="NSAction">_doScroller:</string> + <double key="NSCurValue">1</double> + <double key="NSPercent">0.85256409645080566</double> + </object> + <object class="NSScroller" id="21184185"> + <reference key="NSNextResponder" ref="417749005"/> + <int key="NSvFlags">-2147483392</int> + <string key="NSFrame">{{-100, -100}, {87, 18}}</string> + <reference key="NSSuperview" ref="417749005"/> + <reference key="NSNextKeyView" ref="863539615"/> + <string key="NSReuseIdentifierKey">_NS:33</string> + <bool key="NSAllowsLogicalLayoutDirection">NO</bool> + <int key="NSsFlags">1</int> + <reference key="NSTarget" ref="417749005"/> + <string key="NSAction">_doScroller:</string> + <double key="NSCurValue">1</double> + <double key="NSPercent">0.94565218687057495</double> + </object> + </array> + <string key="NSFrame">{{13, 6}, {384, 152}}</string> + <reference key="NSSuperview" ref="343760123"/> + <reference key="NSNextKeyView" ref="863539615"/> + <string key="NSReuseIdentifierKey">_NS:9</string> + <int key="NSsFlags">153600</int> + <reference key="NSVScroller" ref="1022049165"/> + <reference key="NSHScroller" ref="21184185"/> + <reference key="NSContentView" ref="863539615"/> + <double key="NSMinMagnification">0.25</double> + <double key="NSMaxMagnification">4</double> + <double key="NSMagnification">1</double> + </object> + </array> + <string key="NSFrameSize">{410, 165}</string> + <string key="NSReuseIdentifierKey">_NS:21</string> + </object> + <string key="NSScreenRect">{{0, 0}, {1920, 1178}}</string> + <bool key="NSWindowIsRestorable">YES</bool> + </object> </array> <object class="IBObjectContainer" key="IBDocument.Objects"> <bool key="usesAutoincrementingIDs">NO</bool> @@ -13314,6 +13362,14 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <string key="id">LR5-fT-2qb</string> </object> <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">tableInfoSplitView</string> + <reference key="source" ref="800783608"/> + <reference key="destination" ref="420718918"/> + </object> + <string key="id">n3i-dz-XZx</string> + </object> + <object class="IBConnectionRecord"> <object class="IBActionConnection" key="connection"> <string key="label">addDatabase:</string> <reference key="source" ref="463834704"/> @@ -14858,6 +14914,22 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <string key="id">7087</string> </object> <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">structureHelpPanel</string> + <reference key="source" ref="239767357"/> + <reference key="destination" ref="746008569"/> + </object> + <string key="id">bLb-Jh-Jm1</string> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">structureHelpText</string> + <reference key="source" ref="239767357"/> + <reference key="destination" ref="81696073"/> + </object> + <string key="id">0cG-kD-PJR</string> + </object> + <object class="IBConnectionRecord"> <object class="IBActionConnection" key="connection"> <string key="label">gearMenuItemSelected:</string> <reference key="source" ref="227061690"/> @@ -16003,6 +16075,14 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> </object> <object class="IBConnectionRecord"> <object class="IBOutletConnection" key="connection"> + <string key="label">spDelegate</string> + <reference key="source" ref="381357793"/> + <reference key="destination" ref="239767357"/> + </object> + <string key="id">bLE-2M-Nym</string> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> <string key="label">menu</string> <reference key="source" ref="594274975"/> <reference key="destination" ref="142860202"/> @@ -17342,6 +17422,22 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <object class="IBConnectionRecord"> <object class="IBOutletConnection" key="connection"> <string key="label">delegate</string> + <reference key="source" ref="856601787"/> + <reference key="destination" ref="239767357"/> + </object> + <string key="id">Jbs-NM-Lzb</string> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">delegate</string> + <reference key="source" ref="65801928"/> + <reference key="destination" ref="239767357"/> + </object> + <string key="id">Dcc-ay-7AU</string> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">delegate</string> <reference key="source" ref="828624540"/> <reference key="destination" ref="188482031"/> </object> @@ -19828,17 +19924,10 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <object class="IBObjectRecord"> <string key="id">5782</string> <reference key="object" ref="105876720"/> - <array class="NSMutableArray" key="children"> - <reference ref="597232718"/> - </array> + <array class="NSMutableArray" key="children"/> <reference key="parent" ref="735808692"/> </object> <object class="IBObjectRecord"> - <string key="id">6879</string> - <reference key="object" ref="597232718"/> - <reference key="parent" ref="105876720"/> - </object> - <object class="IBObjectRecord"> <string key="id">5793</string> <reference key="object" ref="503851323"/> <array class="NSMutableArray" key="children"> @@ -24261,6 +24350,48 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <reference key="object" ref="120319413"/> <reference key="parent" ref="999783710"/> </object> + <object class="IBObjectRecord"> + <string key="id">NkY-EN-Fxn</string> + <reference key="object" ref="746008569"/> + <array class="NSMutableArray" key="children"> + <reference ref="343760123"/> + </array> + <reference key="parent" ref="0"/> + <string key="objectName">Structure Type Help</string> + </object> + <object class="IBObjectRecord"> + <string key="id">h2r-1x-8ZD</string> + <reference key="object" ref="343760123"/> + <array class="NSMutableArray" key="children"> + <reference ref="417749005"/> + </array> + <reference key="parent" ref="746008569"/> + </object> + <object class="IBObjectRecord"> + <string key="id">VTe-1x-Phd</string> + <reference key="object" ref="417749005"/> + <array class="NSMutableArray" key="children"> + <reference ref="81696073"/> + <reference ref="21184185"/> + <reference ref="1022049165"/> + </array> + <reference key="parent" ref="343760123"/> + </object> + <object class="IBObjectRecord"> + <string key="id">Bfr-0R-dqh</string> + <reference key="object" ref="81696073"/> + <reference key="parent" ref="417749005"/> + </object> + <object class="IBObjectRecord"> + <string key="id">mBC-nL-nLZ</string> + <reference key="object" ref="21184185"/> + <reference key="parent" ref="417749005"/> + </object> + <object class="IBObjectRecord"> + <string key="id">INC-ms-oeO</string> + <reference key="object" ref="1022049165"/> + <reference key="parent" ref="417749005"/> + </object> </array> </object> <dictionary class="NSMutableDictionary" key="flattenedProperties"> @@ -24286,6 +24417,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <string key="1246.userInterfaceItemIdentifier">StatusTabView</string> <string key="1277.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <boolean value="NO" key="1277.showNotes"/> + <string key="1304.CustomClassName">SPComboBoxCell</string> <string key="1304.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <boolean value="NO" key="1304.showNotes"/> <string key="1309.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> @@ -24357,6 +24489,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <string key="22.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <reference key="22.IBUserGuides" ref="0"/> <boolean value="NO" key="22.showNotes"/> + <string key="22.userInterfaceItemIdentifier">TablesListTableView</string> <string key="23.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <boolean value="NO" key="23.showNotes"/> <string key="231.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> @@ -24366,6 +24499,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <string key="232.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <reference key="232.IBUserGuides" ref="0"/> <boolean value="NO" key="232.showNotes"/> + <string key="232.userInterfaceItemIdentifier">TableStructureColumnsTableView</string> <string key="233.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <boolean value="NO" key="233.showNotes"/> <string key="245.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> @@ -24438,6 +24572,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <string key="36.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <reference key="36.IBUserGuides" ref="0"/> <boolean value="NO" key="36.showNotes"/> + <string key="36.userInterfaceItemIdentifier">TableContentTableView</string> <string key="362.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <boolean value="NO" key="362.showNotes"/> <string key="3754.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> @@ -24941,6 +25076,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <string key="5548.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <reference key="5548.IBUserGuides" ref="0"/> <boolean value="NO" key="5548.showNotes"/> + <string key="5548.userInterfaceItemIdentifier">TableRelationsTableView</string> <string key="5549.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <boolean value="NO" key="5549.showNotes"/> <string key="5550.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> @@ -25276,6 +25412,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <boolean value="NO" key="5828.showNotes"/> <string key="5829.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <boolean value="NO" key="5829.showNotes"/> + <string key="5904.CustomClassName">SPSplitView</string> <string key="5904.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <reference key="5904.IBUserGuides" ref="0"/> <boolean value="NO" key="5904.showNotes"/> @@ -25706,6 +25843,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <string key="6701.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <reference key="6701.IBUserGuides" ref="0"/> <boolean value="NO" key="6701.showNotes"/> + <string key="6701.userInterfaceItemIdentifier">TableTriggersTableView</string> <string key="6702.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <reference key="6702.IBUserGuides" ref="0"/> <boolean value="NO" key="6702.showNotes"/> @@ -25923,10 +26061,6 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <boolean value="NO" key="6877.showNotes"/> <string key="6878.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <boolean value="NO" key="6878.showNotes"/> - <integer value="1041" key="6879.IBNumberFormatterBehaviorMetadataKey"/> - <boolean value="YES" key="6879.IBNumberFormatterLocalizesFormatMetadataKey"/> - <string key="6879.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> - <boolean value="NO" key="6879.showNotes"/> <string key="6885.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <reference key="6885.IBUserGuides" ref="0"/> <boolean value="NO" key="6885.showNotes"/> @@ -26111,6 +26245,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <string key="7224.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <reference key="7224.IBUserGuides" ref="0"/> <boolean value="NO" key="7224.showNotes"/> + <string key="7224.userInterfaceItemIdentifier">CustomQueryResultsTableView</string> <string key="7225.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <reference key="7225.IBUserGuides" ref="0"/> <boolean value="NO" key="7225.showNotes"/> @@ -26351,10 +26486,41 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <boolean value="NO" key="749.showNotes"/> <string key="7490.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <boolean value="NO" key="7490.showNotes"/> + <string key="7491.CustomClassName">SPIdMenu</string> + <object class="NSMutableDictionary" key="7491.IBAttributePlaceholdersKey"> + <string key="NS.key.0">IBUserDefinedRuntimeAttributesPlaceholderName</string> + <object class="IBUserDefinedRuntimeAttributesPlaceholder" key="NS.object.0"> + <string key="name">IBUserDefinedRuntimeAttributesPlaceholderName</string> + <reference key="object" ref="856601787"/> + <array key="userDefinedRuntimeAttributes"> + <object class="IBUserDefinedRuntimeAttribute"> + <string key="typeIdentifier">com.apple.InterfaceBuilder.userDefinedRuntimeAttributeType.string</string> + <string key="keyPath">menuId</string> + <string key="value">encodingPopupMenu</string> + </object> + </array> + </object> + </object> <string key="7491.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <boolean value="NO" key="7491.showNotes"/> + <string key="7493.CustomClassName">SPPopUpButtonCell</string> <string key="7493.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <boolean value="NO" key="7493.showNotes"/> + <string key="7494.CustomClassName">SPIdMenu</string> + <object class="NSMutableDictionary" key="7494.IBAttributePlaceholdersKey"> + <string key="NS.key.0">IBUserDefinedRuntimeAttributesPlaceholderName</string> + <object class="IBUserDefinedRuntimeAttributesPlaceholder" key="NS.object.0"> + <string key="name">IBUserDefinedRuntimeAttributesPlaceholderName</string> + <reference key="object" ref="65801928"/> + <array key="userDefinedRuntimeAttributes"> + <object class="IBUserDefinedRuntimeAttribute"> + <string key="typeIdentifier">com.apple.InterfaceBuilder.userDefinedRuntimeAttributeType.string</string> + <string key="keyPath">menuId</string> + <string key="value">collationPopupMenu</string> + </object> + </array> + </object> + </object> <string key="7494.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <boolean value="NO" key="7494.showNotes"/> <string key="7499.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> @@ -26806,6 +26972,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <string key="8144.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <reference key="8144.IBUserGuides" ref="0"/> <boolean value="NO" key="8144.showNotes"/> + <string key="8144.userInterfaceItemIdentifier">AdvancedFilterTableView</string> <string key="8145.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <reference key="8145.IBUserGuides" ref="0"/> <boolean value="NO" key="8145.showNotes"/> @@ -27045,6 +27212,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <string key="AjX-OA-b5u.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <reference key="AjX-OA-b5u.IBUserGuides" ref="0"/> <boolean value="NO" key="AjX-OA-b5u.showNotes"/> + <string key="Bfr-0R-dqh.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <string key="CIQ-tc-1Fn.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <reference key="CIQ-tc-1Fn.IBUserGuides" ref="0"/> <boolean value="NO" key="CIQ-tc-1Fn.showNotes"/> @@ -27052,7 +27220,11 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <reference key="FF9-z2-9od.IBUserGuides" ref="0"/> <boolean value="NO" key="FF9-z2-9od.showNotes"/> <string key="GVI-VY-JuM.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="INC-ms-oeO.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <string key="KYo-IU-iIw.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="NkY-EN-Fxn.IBPersistedLastKnownCanvasPosition">{98, 659.5}</string> + <string key="NkY-EN-Fxn.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="NO" key="NkY-EN-Fxn.NSWindowTemplate.visibleAtLaunch"/> <string key="SPm-RJ-YgI.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <string key="TRD-dk-r1Q.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <string key="Ts3-iA-OpO.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> @@ -27060,6 +27232,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <reference key="VCn-3k-MdI.IBNSViewMetadataGestureRecognizers" ref="0"/> <string key="VCn-3k-MdI.IBPersistedLastKnownCanvasPosition">{-318.5, -215.5}</string> <string key="VCn-3k-MdI.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="VTe-1x-Phd.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <string key="dIO-Od-K6g.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <reference key="dIO-Od-K6g.IBUserGuides" ref="0"/> <boolean value="NO" key="dIO-Od-K6g.showNotes"/> @@ -27069,11 +27242,13 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <string key="gTZ-sd-Ef7.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <reference key="gTZ-sd-Ef7.IBUserGuides" ref="0"/> <boolean value="NO" key="gTZ-sd-Ef7.showNotes"/> + <string key="h2r-1x-8ZD.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <string key="j3N-dd-SyM.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <string key="khN-PI-iEA.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <boolean value="NO" key="khN-PI-iEA.showNotes"/> <string key="ki9-Po-bdr.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <boolean value="NO" key="ki9-Po-bdr.showNotes"/> + <string key="mBC-nL-nLZ.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <string key="mRm-ts-hRV.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <string key="n3P-gU-pX8.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <string key="n5n-Ef-ROd.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> @@ -27103,6 +27278,24 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> </object> </object> <object class="IBPartialClassDescription"> + <string key="className">NSComboBoxCell</string> + <object class="NSMutableDictionary" key="actions"> + <string key="NS.key.0">popUp:</string> + <string key="NS.object.0">id</string> + </object> + <object class="NSMutableDictionary" key="actionInfosByName"> + <string key="NS.key.0">popUp:</string> + <object class="IBActionInfo" key="NS.object.0"> + <string key="name">popUp:</string> + <string key="candidateClassName">id</string> + </object> + </object> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBProjectSource</string> + <string key="minorKey">../Source/SPComboBoxCell.m</string> + </object> + </object> + <object class="IBPartialClassDescription"> <string key="className">NSTextView</string> <dictionary class="NSMutableDictionary" key="actions"> <string key="doDecomposedStringWithCanonicalMapping:">id</string> @@ -27285,6 +27478,25 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> </object> </object> <object class="IBPartialClassDescription"> + <string key="className">SPComboBoxCell</string> + <string key="superclassName">NSComboBoxCell</string> + <object class="NSMutableDictionary" key="outlets"> + <string key="NS.key.0">spDelegate</string> + <string key="NS.object.0">id</string> + </object> + <object class="NSMutableDictionary" key="toOneOutletInfosByName"> + <string key="NS.key.0">spDelegate</string> + <object class="IBToOneOutletInfo" key="NS.object.0"> + <string key="name">spDelegate</string> + <string key="candidateClassName">id</string> + </object> + </object> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBProjectSource</string> + <string key="minorKey">../Source/SPComboBoxCell.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> <string key="className">SPComboPopupButton</string> <string key="superclassName">NSPopUpButton</string> <object class="IBClassDescriptionSource" key="sourceIdentifier" id="514582401"> @@ -28428,6 +28640,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <string key="tableDataInstance">SPTableData</string> <string key="tableDumpInstance">id</string> <string key="tableInfoScrollView">NSScrollView</string> + <string key="tableInfoSplitView">SPSplitView</string> <string key="tableInfoTable">NSTableView</string> <string key="tableRelationsInstance">id</string> <string key="tableSourceInstance">SPTableStructure</string> @@ -28667,6 +28880,10 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <string key="name">tableInfoScrollView</string> <string key="candidateClassName">NSScrollView</string> </object> + <object class="IBToOneOutletInfo" key="tableInfoSplitView"> + <string key="name">tableInfoSplitView</string> + <string key="candidateClassName">SPSplitView</string> + </object> <object class="IBToOneOutletInfo" key="tableInfoTable"> <string key="name">tableInfoTable</string> <string key="candidateClassName">NSTableView</string> @@ -29158,7 +29375,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <string key="exportCSVLinesTerminatedField">NSComboBox</string> <string key="exportCSVNULLValuesAsTextField">NSTextField</string> <string key="exportCustomFilenameTokenField">NSTokenField</string> - <string key="exportCustomFilenameTokensField">NSTokenField</string> + <string key="exportCustomFilenameTokenPool">NSTokenField</string> <string key="exportCustomFilenameView">NSView</string> <string key="exportCustomFilenameViewButton">NSButton</string> <string key="exportCustomFilenameViewLabelButton">NSButton</string> @@ -29258,8 +29475,8 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <string key="name">exportCustomFilenameTokenField</string> <string key="candidateClassName">NSTokenField</string> </object> - <object class="IBToOneOutletInfo" key="exportCustomFilenameTokensField"> - <string key="name">exportCustomFilenameTokensField</string> + <object class="IBToOneOutletInfo" key="exportCustomFilenameTokenPool"> + <string key="name">exportCustomFilenameTokenPool</string> <string key="candidateClassName">NSTokenField</string> </object> <object class="IBToOneOutletInfo" key="exportCustomFilenameView"> @@ -29524,6 +29741,48 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> </object> </object> <object class="IBPartialClassDescription"> + <string key="className">SPExportController</string> + <dictionary class="NSMutableDictionary" key="actions"> + <string key="exportCurrentSettings:">id</string> + <string key="importCurrentSettings:">id</string> + </dictionary> + <dictionary class="NSMutableDictionary" key="actionInfosByName"> + <object class="IBActionInfo" key="exportCurrentSettings:"> + <string key="name">exportCurrentSettings:</string> + <string key="candidateClassName">id</string> + </object> + <object class="IBActionInfo" key="importCurrentSettings:"> + <string key="name">importCurrentSettings:</string> + <string key="candidateClassName">id</string> + </object> + </dictionary> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBProjectSource</string> + <string key="minorKey">../Source/SPExportSettingsPersistence.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">SPExportController</string> + <dictionary class="NSMutableDictionary" key="actions"> + <string key="exportCurrentSettings:">id</string> + <string key="importCurrentSettings:">id</string> + </dictionary> + <dictionary class="NSMutableDictionary" key="actionInfosByName"> + <object class="IBActionInfo" key="exportCurrentSettings:"> + <string key="name">exportCurrentSettings:</string> + <string key="candidateClassName">id</string> + </object> + <object class="IBActionInfo" key="importCurrentSettings:"> + <string key="name">importCurrentSettings:</string> + <string key="candidateClassName">id</string> + </object> + </dictionary> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBProjectSource</string> + <string key="minorKey">../Source/SPExportSettingsPersistence.m</string> + </object> + </object> + <object class="IBPartialClassDescription"> <string key="className">SPExtendedTableInfo</string> <string key="superclassName">NSObject</string> <dictionary class="NSMutableDictionary" key="actions"> @@ -29767,6 +30026,14 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> </object> </object> <object class="IBPartialClassDescription"> + <string key="className">SPIdMenu</string> + <string key="superclassName">NSMenu</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBProjectSource</string> + <string key="minorKey">../Source/SPIdMenu.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> <string key="className">SPIndexesController</string> <string key="superclassName">NSWindowController</string> <dictionary class="NSMutableDictionary" key="actions"> @@ -29979,6 +30246,14 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> </object> </object> <object class="IBPartialClassDescription"> + <string key="className">SPPopUpButtonCell</string> + <string key="superclassName">NSPopUpButtonCell</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBProjectSource</string> + <string key="minorKey">../Source/SPPopUpButtonCell.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> <string key="className">SPProcessListController</string> <string key="superclassName">NSWindowController</string> <dictionary class="NSMutableDictionary" key="actions"> @@ -30971,7 +31246,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <string key="superclassName">NSObject</string> <dictionary class="NSMutableDictionary" key="outlets"> <string key="activitiesTable">NSTableView</string> - <string key="infoTable">id</string> + <string key="infoTable">NSTableView</string> <string key="tableDataInstance">id</string> <string key="tableDocumentInstance">id</string> <string key="tableInfoScrollView">NSScrollView</string> @@ -30985,7 +31260,7 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> </object> <object class="IBToOneOutletInfo" key="infoTable"> <string key="name">infoTable</string> - <string key="candidateClassName">id</string> + <string key="candidateClassName">NSTableView</string> </object> <object class="IBToOneOutletInfo" key="tableDataInstance"> <string key="name">tableDataInstance</string> @@ -31298,6 +31573,8 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <string key="resetAutoIncrementSheet">id</string> <string key="resetAutoIncrementValue">id</string> <string key="structureGrabber">id</string> + <string key="structureHelpPanel">NSPanel</string> + <string key="structureHelpText">NSTextView</string> <string key="tableDataInstance">SPTableData</string> <string key="tableDocumentInstance">SPDatabaseDocument</string> <string key="tableInfoInstance">SPTableInfo</string> @@ -31387,6 +31664,14 @@ AAEAAQAAAT0AAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAIAAQABAAAAAA</bytes> <string key="name">structureGrabber</string> <string key="candidateClassName">id</string> </object> + <object class="IBToOneOutletInfo" key="structureHelpPanel"> + <string key="name">structureHelpPanel</string> + <string key="candidateClassName">NSPanel</string> + </object> + <object class="IBToOneOutletInfo" key="structureHelpText"> + <string key="name">structureHelpText</string> + <string key="candidateClassName">NSTextView</string> + </object> <object class="IBToOneOutletInfo" key="tableDataInstance"> <string key="name">tableDataInstance</string> <string key="candidateClassName">SPTableData</string> 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 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="752604777"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272761856</int> + <int key="NSCellFlags2">272761920</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="806234307"/> 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 @@ <bool key="NSEnabled">YES</bool> <object class="NSSearchFieldCell" key="NSCell" id="92933437"> <int key="NSCellFlags">342884416</int> - <int key="NSCellFlags2">268567552</int> + <int key="NSCellFlags2">268567616</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <string key="NSPlaceholderString">Filter</string> @@ -1019,7 +1019,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="437242737"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <reference key="NSSupport" ref="421083426"/> <object class="NSNumberFormatter" key="NSFormatter" id="59013685"> <dictionary class="NSMutableDictionary" key="NS.attributes"> 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 @@ <bool key="NSEnabled">YES</bool> <object class="NSSearchFieldCell" key="NSCell" id="623709875"> <int key="NSCellFlags">342884416</int> - <int key="NSCellFlags2">268567552</int> + <int key="NSCellFlags2">268567616</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <string key="NSPlaceholderString">Filter</string> diff --git a/Interfaces/English.lproj/ExportDialog.xib b/Interfaces/English.lproj/ExportDialog.xib index 3c3d6a2f..25d5b075 100644 --- a/Interfaces/English.lproj/ExportDialog.xib +++ b/Interfaces/English.lproj/ExportDialog.xib @@ -61,13 +61,13 @@ <object class="NSWindowTemplate" id="834889278"> <int key="NSWindowStyleMask">4111</int> <int key="NSWindowBacking">2</int> - <string key="NSWindowRect">{{610, 273}, {517, 498}}</string> + <string key="NSWindowRect">{{610, 273}, {750, 498}}</string> <int key="NSWTFlags">611845120</int> <string key="NSWindowTitle">Export Window</string> <string key="NSWindowClass">NSWindow</string> <nil key="NSViewClass"/> <nil key="NSUserInterfaceItemIdentifier"/> - <string key="NSWindowContentMinSize">{517, 498}</string> + <string key="NSWindowContentMinSize">{730, 498}</string> <object class="NSView" key="NSWindowView" id="13817034"> <reference key="NSNextResponder"/> <int key="NSvFlags">256</int> @@ -75,7 +75,7 @@ <object class="NSTabView" id="109227463"> <reference key="NSNextResponder" ref="13817034"/> <int key="NSvFlags">274</int> - <string key="NSFrame">{{-8, 69}, {533, 423}}</string> + <string key="NSFrame">{{-8, 69}, {766, 423}}</string> <reference key="NSSuperview" ref="13817034"/> <reference key="NSWindow"/> <array class="NSMutableArray" key="NSTabViewItems"> @@ -84,7 +84,7 @@ <object class="NSView" key="NSView" id="254508059"> <reference key="NSNextResponder" ref="109227463"/> <int key="NSvFlags">274</int> - <string key="NSFrame">{{10, 33}, {513, 377}}</string> + <string key="NSFrame">{{10, 33}, {746, 377}}</string> <reference key="NSSuperview" ref="109227463"/> <reference key="NSWindow"/> </object> @@ -149,7 +149,7 @@ <object class="NSButton" id="842573327"> <reference key="NSNextResponder" ref="13817034"/> <int key="NSvFlags">289</int> - <string key="NSFrame">{{397, 12}, {104, 32}}</string> + <string key="NSFrame">{{630, 12}, {104, 32}}</string> <reference key="NSSuperview" ref="13817034"/> <reference key="NSWindow"/> <int key="NSTag">1</int> @@ -172,7 +172,7 @@ <object class="NSButton" id="840756827"> <reference key="NSNextResponder" ref="13817034"/> <int key="NSvFlags">289</int> - <string key="NSFrame">{{293, 12}, {104, 32}}</string> + <string key="NSFrame">{{526, 12}, {104, 32}}</string> <reference key="NSSuperview" ref="13817034"/> <reference key="NSWindow"/> <bool key="NSEnabled">YES</bool> @@ -339,12 +339,12 @@ <bool key="NSAllowsLogicalLayoutDirection">NO</bool> </object> </array> - <string key="NSFrame">{{1, 1}, {478, 65}}</string> + <string key="NSFrame">{{1, 1}, {711, 65}}</string> <reference key="NSSuperview" ref="75303935"/> <reference key="NSWindow"/> </object> </array> - <string key="NSFrame">{{-3, -4}, {480, 67}}</string> + <string key="NSFrame">{{-3, -4}, {713, 67}}</string> <reference key="NSSuperview" ref="184955131"/> <reference key="NSWindow"/> <string key="NSOffsets">{0, 0}</string> @@ -398,7 +398,7 @@ <bool key="NSAllowsLogicalLayoutDirection">NO</bool> </object> </array> - <string key="NSFrame">{{21, -16}, {474, 61}}</string> + <string key="NSFrame">{{21, -16}, {707, 61}}</string> <reference key="NSSuperview" ref="13817034"/> <reference key="NSWindow"/> <string key="NSClassName">NSView</string> @@ -456,7 +456,7 @@ <object class="NSTextField" id="912734518"> <reference key="NSNextResponder" ref="13817034"/> <int key="NSvFlags">290</int> - <string key="NSFrame">{{17, 22}, {274, 14}}</string> + <string key="NSFrame">{{17, 22}, {507, 14}}</string> <reference key="NSSuperview" ref="13817034"/> <reference key="NSWindow"/> <bool key="NSEnabled">YES</bool> @@ -476,7 +476,7 @@ <int key="NSTextFieldAlignmentRectInsetsVersion">1</int> </object> </array> - <string key="NSFrameSize">{517, 498}</string> + <string key="NSFrameSize">{750, 498}</string> <reference key="NSSuperview"/> <reference key="NSWindow"/> </object> @@ -839,6 +839,82 @@ <nil key="NSNextResponder"/> <int key="NSvFlags">268</int> <array class="NSMutableArray" key="NSSubviews"> + <object class="NSPopUpButton" id="745374830"> + <reference key="NSNextResponder" ref="1039688935"/> + <int key="NSvFlags">265</int> + <string key="NSFrame">{{674, 347}, {41, 22}}</string> + <reference key="NSSuperview" ref="1039688935"/> + <reference key="NSNextKeyView" ref="585337816"/> + <string key="NSReuseIdentifierKey">_NS:9</string> + <bool key="NSEnabled">YES</bool> + <object class="NSPopUpButtonCell" key="NSCell" id="949231914"> + <int key="NSCellFlags">71303232</int> + <int key="NSCellFlags2">133120</int> + <reference key="NSSupport" ref="26"/> + <string key="NSCellIdentifier">_NS:9</string> + <reference key="NSControlView" ref="745374830"/> + <int key="NSButtonFlags">109199360</int> + <int key="NSButtonFlags2">129</int> + <object class="NSFont" key="NSAlternateImage"> + <string key="NSName">.LucidaGrandeUI</string> + <double key="NSSize">11</double> + <int key="NSfFlags">16</int> + </object> + <string key="NSAlternateContents"/> + <string key="NSKeyEquivalent"/> + <int key="NSPeriodicDelay">400</int> + <int key="NSPeriodicInterval">75</int> + <object class="NSMenuItem" key="NSMenuItem" id="630979950"> + <reference key="NSMenu" ref="136150039"/> + <bool key="NSIsHidden">YES</bool> + <string key="NSTitle">S</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <int key="NSState">1</int> + <reference key="NSOnImage" ref="626013002"/> + <reference key="NSMixedImage" ref="428837307"/> + <string key="NSAction">_popUpItemAction:</string> + <reference key="NSTarget" ref="949231914"/> + </object> + <bool key="NSMenuItemRespectAlignment">YES</bool> + <object class="NSMenu" key="NSMenu" id="136150039"> + <string key="NSTitle"/> + <array class="NSMutableArray" key="NSMenuItems"> + <reference ref="630979950"/> + <object class="NSMenuItem" id="821214266"> + <reference key="NSMenu" ref="136150039"/> + <string key="NSTitle">Apply saved settings…</string> + <string key="NSKeyEquiv">o</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="626013002"/> + <reference key="NSMixedImage" ref="428837307"/> + <string key="NSAction">_popUpItemAction:</string> + <reference key="NSTarget" ref="949231914"/> + </object> + <object class="NSMenuItem" id="1043233379"> + <reference key="NSMenu" ref="136150039"/> + <string key="NSTitle">Save current settings…</string> + <string key="NSKeyEquiv">s</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="626013002"/> + <reference key="NSMixedImage" ref="428837307"/> + <string key="NSAction">_popUpItemAction:</string> + <reference key="NSTarget" ref="949231914"/> + </object> + </array> + </object> + <int key="NSSelectedIndex">-1</int> + <bool key="NSPullDown">YES</bool> + <int key="NSPreferredEdge">1</int> + <bool key="NSUsesItemFromMenu">YES</bool> + <bool key="NSAltersState">YES</bool> + <int key="NSArrowPosition">2</int> + </object> + <bool key="NSAllowsLogicalLayoutDirection">NO</bool> + </object> <object class="NSBox" id="414783283"> <reference key="NSNextResponder" ref="1039688935"/> <int key="NSvFlags">34</int> @@ -858,8 +934,9 @@ <string>NeXT Encapsulated PostScript v1.2 pasteboard type</string> <string>NeXT TIFF v4.0 pasteboard type</string> </set> - <string key="NSFrameSize">{200, 22}</string> + <string key="NSFrameSize">{413, 22}</string> <reference key="NSSuperview" ref="627515769"/> + <reference key="NSNextKeyView" ref="465113538"/> <bool key="NSEnabled">YES</bool> <object class="NSImageCell" key="NSCell" id="653271187"> <int key="NSCellFlags">134217728</int> @@ -881,6 +958,7 @@ <int key="NSvFlags">292</int> <string key="NSFrame">{{-1, -1}, {32, 25}}</string> <reference key="NSSuperview" ref="627515769"/> + <reference key="NSNextKeyView" ref="843467514"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="904794169"> <int key="NSCellFlags">67108864</int> @@ -904,8 +982,9 @@ <object class="NSButton" id="534662798"> <reference key="NSNextResponder" ref="627515769"/> <int key="NSvFlags">289</int> - <string key="NSFrame">{{168, -2}, {32, 25}}</string> + <string key="NSFrame">{{381, -2}, {32, 25}}</string> <reference key="NSSuperview" ref="627515769"/> + <reference key="NSNextKeyView" ref="683006936"/> <int key="NSTag">1</int> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="87312096"> @@ -930,8 +1009,9 @@ <object class="NSButton" id="465113538"> <reference key="NSNextResponder" ref="627515769"/> <int key="NSvFlags">289</int> - <string key="NSFrame">{{137, -2}, {32, 25}}</string> + <string key="NSFrame">{{350, -2}, {32, 25}}</string> <reference key="NSSuperview" ref="627515769"/> + <reference key="NSNextKeyView" ref="534662798"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="944500450"> <int key="NSCellFlags">67108864</int> @@ -953,12 +1033,14 @@ <bool key="NSAllowsLogicalLayoutDirection">NO</bool> </object> </array> - <string key="NSFrame">{{1, 1}, {199, 21}}</string> + <string key="NSFrame">{{1, 1}, {412, 21}}</string> <reference key="NSSuperview" ref="414783283"/> + <reference key="NSNextKeyView" ref="638088233"/> </object> </array> - <string key="NSFrame">{{21, 20}, {201, 23}}</string> + <string key="NSFrame">{{21, 20}, {414, 23}}</string> <reference key="NSSuperview" ref="1039688935"/> + <reference key="NSNextKeyView" ref="627515769"/> <string key="NSOffsets">{0, 0}</string> <object class="NSTextFieldCell" key="NSTitleCell"> <int key="NSCellFlags">67108864</int> @@ -987,6 +1069,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{17, 285}, {207, 22}}</string> <reference key="NSSuperview" ref="1039688935"/> + <reference key="NSNextKeyView" ref="238794017"/> <bool key="NSEnabled">YES</bool> <object class="NSPopUpButtonCell" key="NSCell" id="176341375"> <int key="NSCellFlags">-2076180416</int> @@ -999,9 +1082,9 @@ <string key="NSKeyEquivalent"/> <int key="NSPeriodicDelay">400</int> <int key="NSPeriodicInterval">75</int> - <object class="NSMenuItem" key="NSMenuItem" id="713988214"> + <object class="NSMenuItem" key="NSMenuItem" id="564853714"> <reference key="NSMenu" ref="516760020"/> - <string key="NSTitle">Tables</string> + <string key="NSTitle">Filtered Results</string> <string key="NSKeyEquiv"/> <int key="NSKeyEquivModMask">1048576</int> <int key="NSMnemonicLoc">2147483647</int> @@ -1015,9 +1098,10 @@ <object class="NSMenu" key="NSMenu" id="516760020"> <string key="NSTitle">OtherViews</string> <array class="NSMutableArray" key="NSMenuItems"> - <object class="NSMenuItem" id="564853714"> + <reference ref="564853714"/> + <object class="NSMenuItem" id="1029240286"> <reference key="NSMenu" ref="516760020"/> - <string key="NSTitle">Filtered Results</string> + <string key="NSTitle">Query Results</string> <string key="NSKeyEquiv"/> <int key="NSKeyEquivModMask">1048576</int> <int key="NSMnemonicLoc">2147483647</int> @@ -1026,9 +1110,9 @@ <string key="NSAction">_popUpItemAction:</string> <reference key="NSTarget" ref="176341375"/> </object> - <object class="NSMenuItem" id="1029240286"> + <object class="NSMenuItem" id="713988214"> <reference key="NSMenu" ref="516760020"/> - <string key="NSTitle">Query Results</string> + <string key="NSTitle">Tables</string> <string key="NSKeyEquiv"/> <int key="NSKeyEquivModMask">1048576</int> <int key="NSMnemonicLoc">2147483647</int> @@ -1037,12 +1121,11 @@ <string key="NSAction">_popUpItemAction:</string> <reference key="NSTarget" ref="176341375"/> </object> - <reference ref="713988214"/> </array> <bool key="NSNoAutoenable">YES</bool> <reference key="NSMenuFont" ref="695505032"/> </object> - <int key="NSSelectedIndex">2</int> + <int key="NSSelectedIndex">-1</int> <int key="NSPreferredEdge">1</int> <bool key="NSUsesItemFromMenu">YES</bool> <bool key="NSAltersState">YES</bool> @@ -1065,12 +1148,13 @@ <object class="NSTextField" id="369138387"> <reference key="NSNextResponder" ref="814282894"/> <int key="NSvFlags">266</int> - <string key="NSFrame">{{29, 59}, {482, 19}}</string> + <string key="NSFrame">{{29, 59}, {699, 19}}</string> <reference key="NSSuperview" ref="814282894"/> + <reference key="NSNextKeyView" ref="134032144"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="1053336717"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272761856</int> + <int key="NSCellFlags2">272761920</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="369138387"/> @@ -1089,8 +1173,9 @@ <object class="NSTextField" id="642404234"> <reference key="NSNextResponder" ref="814282894"/> <int key="NSvFlags">266</int> - <string key="NSFrame">{{29, 86}, {485, 14}}</string> + <string key="NSFrame">{{29, 86}, {702, 14}}</string> <reference key="NSSuperview" ref="814282894"/> + <reference key="NSNextKeyView" ref="369138387"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="136682379"> <int key="NSCellFlags">68157504</int> @@ -1115,8 +1200,9 @@ <object class="NSTextField" id="844630128"> <reference key="NSNextResponder" ref="197574806"/> <int key="NSvFlags">258</int> - <string key="NSFrame">{{21, 4}, {450, 22}}</string> + <string key="NSFrame">{{21, 4}, {667, 22}}</string> <reference key="NSSuperview" ref="197574806"/> + <reference key="NSNextKeyView" ref="429080228"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="872070930"> <int key="NSCellFlags">-2077228991</int> @@ -1131,12 +1217,14 @@ <int key="NSTextFieldAlignmentRectInsetsVersion">1</int> </object> </array> - <string key="NSFrame">{{1, 1}, {486, 38}}</string> + <string key="NSFrame">{{1, 1}, {703, 38}}</string> <reference key="NSSuperview" ref="134032144"/> + <reference key="NSNextKeyView" ref="844630128"/> </object> </array> - <string key="NSFrame">{{26, 10}, {488, 40}}</string> + <string key="NSFrame">{{26, 10}, {705, 40}}</string> <reference key="NSSuperview" ref="814282894"/> + <reference key="NSNextKeyView" ref="197574806"/> <string key="NSOffsets">{0, 0}</string> <object class="NSTextFieldCell" key="NSTitleCell"> <int key="NSCellFlags">67108864</int> @@ -1156,12 +1244,14 @@ <bool key="NSTransparent">NO</bool> </object> </array> - <string key="NSFrame">{{1, 1}, {529, 112}}</string> + <string key="NSFrame">{{1, 1}, {746, 112}}</string> <reference key="NSSuperview" ref="593000452"/> + <reference key="NSNextKeyView" ref="642404234"/> </object> </array> - <string key="NSFrame">{{-11, -4}, {531, 114}}</string> + <string key="NSFrame">{{-11, -4}, {748, 114}}</string> <reference key="NSSuperview" ref="185191090"/> + <reference key="NSNextKeyView" ref="814282894"/> <string key="NSOffsets">{0, 0}</string> <object class="NSTextFieldCell" key="NSTitleCell"> <int key="NSCellFlags">67108864</int> @@ -1181,15 +1271,17 @@ <bool key="NSTransparent">NO</bool> </object> </array> - <string key="NSFrame">{{0, 207}, {517, 108}}</string> + <string key="NSFrame">{{0, 207}, {730, 108}}</string> <reference key="NSSuperview" ref="1039688935"/> + <reference key="NSNextKeyView" ref="593000452"/> <string key="NSClassName">NSView</string> </object> <object class="NSButton" id="93580453"> <reference key="NSNextResponder" ref="1039688935"/> <int key="NSvFlags">266</int> - <string key="NSFrame">{{32, 316}, {467, 28}}</string> + <string key="NSFrame">{{32, 316}, {680, 28}}</string> <reference key="NSSuperview" ref="1039688935"/> + <reference key="NSNextKeyView" ref="185191090"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="704057245"> <int key="NSCellFlags">-2080374720</int> @@ -1212,6 +1304,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{9, 318}, {29, 26}}</string> <reference key="NSSuperview" ref="1039688935"/> + <reference key="NSNextKeyView" ref="212414565"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="240404499"> <int key="NSCellFlags">67108864</int> @@ -1232,8 +1325,9 @@ <object class="NSButton" id="1058029781"> <reference key="NSNextResponder" ref="1039688935"/> <int key="NSvFlags">265</int> - <string key="NSFrame">{{408, 344}, {94, 28}}</string> + <string key="NSFrame">{{580, 344}, {94, 28}}</string> <reference key="NSSuperview" ref="1039688935"/> + <reference key="NSNextKeyView" ref="745374830"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="808873518"> <int key="NSCellFlags">67108864</int> @@ -1253,8 +1347,9 @@ <object class="NSBox" id="212414565"> <reference key="NSNextResponder" ref="1039688935"/> <int key="NSvFlags">10</int> - <string key="NSFrame">{{20, 312}, {477, 5}}</string> + <string key="NSFrame">{{20, 312}, {690, 5}}</string> <reference key="NSSuperview" ref="1039688935"/> + <reference key="NSNextKeyView" ref="93580453"/> <string key="NSOffsets">{0, 0}</string> <object class="NSTextFieldCell" key="NSTitleCell"> <int key="NSCellFlags">67108864</int> @@ -1275,8 +1370,9 @@ <object class="NSTextField" id="412690578"> <reference key="NSNextResponder" ref="1039688935"/> <int key="NSvFlags">266</int> - <string key="NSFrame">{{127, 349}, {278, 19}}</string> + <string key="NSFrame">{{127, 349}, {450, 19}}</string> <reference key="NSSuperview" ref="1039688935"/> + <reference key="NSNextKeyView" ref="1058029781"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="291144691"> <int key="NSCellFlags">-2073034687</int> @@ -1296,6 +1392,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{17, 351}, {105, 14}}</string> <reference key="NSSuperview" ref="1039688935"/> + <reference key="NSNextKeyView" ref="412690578"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="869459931"> <int key="NSCellFlags">68157504</int> @@ -1314,6 +1411,7 @@ <int key="NSvFlags">-2147483380</int> <string key="NSFrame">{{226, 288}, {273, 18}}</string> <reference key="NSSuperview" ref="1039688935"/> + <reference key="NSNextKeyView" ref="505274803"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="328323922"> <int key="NSCellFlags">-2080374784</int> @@ -1334,21 +1432,23 @@ </object> <object class="NSTabView" id="683006936"> <reference key="NSNextResponder" ref="1039688935"/> - <int key="NSvFlags">17</int> - <string key="NSFrame">{{225, 16}, {276, 268}}</string> + <int key="NSvFlags">273</int> + <string key="NSFrame">{{438, 16}, {276, 268}}</string> <reference key="NSSuperview" ref="1039688935"/> + <reference key="NSNextKeyView" ref="855848511"/> <array class="NSMutableArray" key="NSTabViewItems"> <object class="NSTabViewItem" id="286086449"> <string key="NSIdentifier">sql</string> <object class="NSView" key="NSView" id="311280472"> - <reference key="NSNextResponder" ref="683006936"/> - <int key="NSvFlags">256</int> + <nil key="NSNextResponder"/> + <int key="NSvFlags">274</int> <array class="NSMutableArray" key="NSSubviews"> <object class="NSButton" id="896448830"> <reference key="NSNextResponder" ref="311280472"/> <int key="NSvFlags">268</int> <string key="NSFrame">{{11, 195}, {233, 18}}</string> <reference key="NSSuperview" ref="311280472"/> + <reference key="NSNextKeyView" ref="855467986"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="1070796296"> <int key="NSCellFlags">-2080374784</int> @@ -1372,6 +1472,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{12, 219}, {233, 14}}</string> <reference key="NSSuperview" ref="311280472"/> + <reference key="NSNextKeyView" ref="896448830"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="882864517"> <int key="NSCellFlags">68157504</int> @@ -1390,6 +1491,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{12, 95}, {233, 14}}</string> <reference key="NSSuperview" ref="311280472"/> + <reference key="NSNextKeyView" ref="771021044"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="460496284"> <int key="NSCellFlags">68157504</int> @@ -1408,6 +1510,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{12, 115}, {232, 18}}</string> <reference key="NSSuperview" ref="311280472"/> + <reference key="NSNextKeyView" ref="904279924"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="902213604"> <int key="NSCellFlags">67108864</int> @@ -1431,6 +1534,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{12, 135}, {232, 18}}</string> <reference key="NSSuperview" ref="311280472"/> + <reference key="NSNextKeyView" ref="163591590"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="619630097"> <int key="NSCellFlags">-2080374784</int> @@ -1454,6 +1558,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{12, 71}, {232, 18}}</string> <reference key="NSSuperview" ref="311280472"/> + <reference key="NSNextKeyView" ref="243826641"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="302595793"> <int key="NSCellFlags">67108864</int> @@ -1477,6 +1582,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{12, 51}, {232, 18}}</string> <reference key="NSSuperview" ref="311280472"/> + <reference key="NSNextKeyView" ref="864598499"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="595529721"> <int key="NSCellFlags">-2080374784</int> @@ -1500,6 +1606,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{12, 31}, {233, 14}}</string> <reference key="NSSuperview" ref="311280472"/> + <reference key="NSNextKeyView" ref="946500215"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="618129698"> <int key="NSCellFlags">68157504</int> @@ -1518,10 +1625,11 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{15, 4}, {60, 19}}</string> <reference key="NSSuperview" ref="311280472"/> + <reference key="NSNextKeyView" ref="137273930"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="754103523"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">-1874721792</int> + <int key="NSCellFlags2">-1874721728</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <object class="NSNumberFormatter" key="NSFormatter" id="301208633"> @@ -1638,6 +1746,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{29, 175}, {215, 18}}</string> <reference key="NSSuperview" ref="311280472"/> + <reference key="NSNextKeyView" ref="68865676"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="368147897"> <int key="NSCellFlags">67108864</int> @@ -1661,6 +1770,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{29, 156}, {215, 18}}</string> <reference key="NSSuperview" ref="311280472"/> + <reference key="NSNextKeyView" ref="472940336"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="703689900"> <int key="NSCellFlags">-2080374784</int> @@ -1681,7 +1791,7 @@ </object> </array> <string key="NSFrame">{{10, 7}, {256, 248}}</string> - <reference key="NSSuperview" ref="683006936"/> + <reference key="NSNextKeyView" ref="868197026"/> </object> <string key="NSLabel">SQL</string> <reference key="NSColor" ref="683790803"/> @@ -2065,7 +2175,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <bool key="NSEnabled">YES</bool> <object class="NSComboBoxCell" key="NSCell" id="24725207"> <int key="NSCellFlags">342884417</int> - <int key="NSCellFlags2">132096</int> + <int key="NSCellFlags2">132160</int> <string key="NSContents">,</string> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="160760073"/> @@ -2168,7 +2278,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <bool key="NSEnabled">YES</bool> <object class="NSComboBoxCell" key="NSCell" id="802916595"> <int key="NSCellFlags">342884417</int> - <int key="NSCellFlags2">132096</int> + <int key="NSCellFlags2">132160</int> <string key="NSContents">"</string> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="43051200"/> @@ -2253,7 +2363,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <bool key="NSEnabled">YES</bool> <object class="NSComboBoxCell" key="NSCell" id="590352278"> <int key="NSCellFlags">342884417</int> - <int key="NSCellFlags2">132096</int> + <int key="NSCellFlags2">132160</int> <string key="NSContents">\</string> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="1056509825"/> @@ -2356,7 +2466,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <bool key="NSEnabled">YES</bool> <object class="NSComboBoxCell" key="NSCell" id="755941142"> <int key="NSCellFlags">342884417</int> - <int key="NSCellFlags2">132096</int> + <int key="NSCellFlags2">132160</int> <string key="NSContents">\n</string> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="216687791"/> @@ -2500,7 +2610,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="95838642"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272761856</int> + <int key="NSCellFlags2">272761920</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="445644168"/> @@ -2532,14 +2642,15 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <object class="NSTabViewItem" id="317037302"> <string key="NSIdentifier">xml</string> <object class="NSView" key="NSView" id="855848511"> - <nil key="NSNextResponder"/> - <int key="NSvFlags">256</int> + <reference key="NSNextResponder" ref="683006936"/> + <int key="NSvFlags">274</int> <array class="NSMutableArray" key="NSSubviews"> <object class="NSTextField" id="65089502"> <reference key="NSNextResponder" ref="855848511"/> <int key="NSvFlags">268</int> <string key="NSFrame">{{11, 131}, {72, 14}}</string> <reference key="NSSuperview" ref="855848511"/> + <reference key="NSNextKeyView" ref="700877238"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="649072289"> <int key="NSCellFlags">68157504</int> @@ -2561,7 +2672,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="120501725"> <int key="NSCellFlags">-1267728319</int> - <int key="NSCellFlags2">272761856</int> + <int key="NSCellFlags2">272761920</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="700877238"/> @@ -2577,6 +2688,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <int key="NSvFlags">268</int> <string key="NSFrame">{{11, 171}, {179, 18}}</string> <reference key="NSSuperview" ref="855848511"/> + <reference key="NSNextKeyView" ref="542859898"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="628367768"> <int key="NSCellFlags">-2080374784</int> @@ -2600,6 +2712,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <int key="NSvFlags">268</int> <string key="NSFrame">{{12, 195}, {180, 14}}</string> <reference key="NSSuperview" ref="855848511"/> + <reference key="NSNextKeyView" ref="68485429"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="442957493"> <int key="NSCellFlags">68157504</int> @@ -2618,6 +2731,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <int key="NSvFlags">268</int> <string key="NSFrame">{{11, 151}, {179, 18}}</string> <reference key="NSSuperview" ref="855848511"/> + <reference key="NSNextKeyView" ref="65089502"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="588899969"> <int key="NSCellFlags">-2080374784</int> @@ -2641,6 +2755,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <int key="NSvFlags">268</int> <string key="NSFrame">{{59, 212}, {119, 22}}</string> <reference key="NSSuperview" ref="855848511"/> + <reference key="NSNextKeyView" ref="283307909"/> <bool key="NSEnabled">YES</bool> <object class="NSPopUpButtonCell" key="NSCell" id="732046199"> <int key="NSCellFlags">-2076180416</int> @@ -2696,6 +2811,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <int key="NSvFlags">268</int> <string key="NSFrame">{{11, 217}, {46, 14}}</string> <reference key="NSSuperview" ref="855848511"/> + <reference key="NSNextKeyView" ref="132499812"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="713176599"> <int key="NSCellFlags">68157504</int> @@ -2711,6 +2827,8 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 </object> </array> <string key="NSFrame">{{10, 7}, {256, 248}}</string> + <reference key="NSSuperview" ref="683006936"/> + <reference key="NSNextKeyView" ref="875960206"/> </object> <string key="NSLabel">XML</string> <reference key="NSColor" ref="683790803"/> @@ -2763,13 +2881,13 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <reference key="NSTabView" ref="683006936"/> </object> </array> - <reference key="NSSelectedTabViewItem" ref="286086449"/> + <reference key="NSSelectedTabViewItem" ref="317037302"/> <reference key="NSFont" ref="695505032"/> <int key="NSTvFlags">4</int> <bool key="NSAllowTruncatedLabels">YES</bool> <bool key="NSDrawsBackground">YES</bool> <array class="NSMutableArray" key="NSSubviews"> - <reference ref="311280472"/> + <reference ref="855848511"/> </array> </object> <object class="NSScrollView" id="505274803"> @@ -2778,21 +2896,23 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <array class="NSMutableArray" key="NSSubviews"> <object class="NSClipView" id="475696168"> <reference key="NSNextResponder" ref="505274803"/> - <int key="NSvFlags">2304</int> + <int key="NSvFlags">2322</int> <array class="NSMutableArray" key="NSSubviews"> <object class="NSTableView" id="324452213"> <reference key="NSNextResponder" ref="475696168"/> <int key="NSvFlags">4352</int> - <string key="NSFrameSize">{199, 221}</string> + <string key="NSFrameSize">{412, 221}</string> <reference key="NSSuperview" ref="475696168"/> + <reference key="NSNextKeyView" ref="695806109"/> <bool key="NSEnabled">YES</bool> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <bool key="NSControlAllowsExpansionToolTips">YES</bool> <object class="NSTableHeaderView" key="NSHeaderView" id="181185916"> <reference key="NSNextResponder" ref="452013144"/> <int key="NSvFlags">256</int> - <string key="NSFrameSize">{199, 17}</string> + <string key="NSFrameSize">{412, 17}</string> <reference key="NSSuperview" ref="452013144"/> + <reference key="NSNextKeyView" ref="450730105"/> <reference key="NSTableView" ref="324452213"/> </object> <object class="_NSCornerView" key="NSCornerView" id="450730105"> @@ -2800,11 +2920,12 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <int key="NSvFlags">-2147483392</int> <string key="NSFrame">{{144, 0}, {16, 17}}</string> <reference key="NSSuperview" ref="505274803"/> + <reference key="NSNextKeyView" ref="475696168"/> </object> <array class="NSMutableArray" key="NSTableColumns"> <object class="NSTableColumn" id="730805233"> <string key="NSIdentifier">tables</string> - <double key="NSWidth">142</double> + <double key="NSWidth">355</double> <double key="NSMinWidth">10</double> <double key="NSMaxWidth">1000</double> <object class="NSTableHeaderCell" key="NSHeaderCell"> @@ -2959,7 +3080,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <int key="NSTableViewGroupRowStyle">1</int> </object> </array> - <string key="NSFrame">{{1, 17}, {199, 221}}</string> + <string key="NSFrame">{{1, 17}, {412, 221}}</string> <reference key="NSSuperview" ref="505274803"/> <reference key="NSNextKeyView" ref="324452213"/> <reference key="NSDocView" ref="324452213"/> @@ -2971,6 +3092,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <int key="NSvFlags">-2147483392</int> <string key="NSFrame">{{148, 1}, {11, 228}}</string> <reference key="NSSuperview" ref="505274803"/> + <reference key="NSNextKeyView" ref="414783283"/> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <int key="NSsFlags">256</int> <reference key="NSTarget" ref="505274803"/> @@ -2982,6 +3104,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <int key="NSvFlags">-2147483392</int> <string key="NSFrame">{{-100, -100}, {191, 15}}</string> <reference key="NSSuperview" ref="505274803"/> + <reference key="NSNextKeyView" ref="452013144"/> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <int key="NSsFlags">1</int> <reference key="NSTarget" ref="505274803"/> @@ -2990,11 +3113,11 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 </object> <object class="NSClipView" id="452013144"> <reference key="NSNextResponder" ref="505274803"/> - <int key="NSvFlags">2304</int> + <int key="NSvFlags">2338</int> <array class="NSMutableArray" key="NSSubviews"> <reference ref="181185916"/> </array> - <string key="NSFrame">{{1, 0}, {199, 17}}</string> + <string key="NSFrame">{{1, 0}, {412, 17}}</string> <reference key="NSSuperview" ref="505274803"/> <reference key="NSNextKeyView" ref="181185916"/> <reference key="NSDocView" ref="181185916"/> @@ -3003,21 +3126,23 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 </object> <reference ref="450730105"/> </array> - <string key="NSFrame">{{21, 42}, {201, 239}}</string> + <string key="NSFrame">{{21, 42}, {414, 239}}</string> <reference key="NSSuperview" ref="1039688935"/> - <reference key="NSNextKeyView" ref="475696168"/> + <reference key="NSNextKeyView" ref="681376734"/> <int key="NSsFlags">133650</int> <reference key="NSVScroller" ref="695806109"/> <reference key="NSHScroller" ref="681376734"/> <reference key="NSContentView" ref="475696168"/> <reference key="NSHeaderClipView" ref="452013144"/> + <reference key="NSCornerView" ref="450730105"/> <bytes key="NSScrollAmts">QSAAAEEgAABBmAAAQZgAAA</bytes> <double key="NSMinMagnification">0.25</double> <double key="NSMaxMagnification">4</double> <double key="NSMagnification">1</double> </object> </array> - <string key="NSFrameSize">{517, 378}</string> + <string key="NSFrameSize">{730, 378}</string> + <reference key="NSNextKeyView" ref="85590559"/> <string key="NSClassName">NSView</string> </object> <object class="NSUserDefaultsController" id="965845845"> @@ -3309,7 +3434,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 </object> <object class="IBConnectionRecord"> <object class="IBOutletConnection" key="connection"> - <string key="label">exportCustomFilenameTokensField</string> + <string key="label">exportCustomFilenameTokenPool</string> <reference key="source" ref="1001"/> <reference key="destination" ref="844630128"/> </object> @@ -3596,6 +3721,22 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <string key="id">1273</string> </object> <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">exportCurrentSettings:</string> + <reference key="source" ref="1001"/> + <reference key="destination" ref="1043233379"/> + </object> + <string key="id">qlz-iS-nue</string> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">importCurrentSettings:</string> + <reference key="source" ref="1001"/> + <reference key="destination" ref="821214266"/> + </object> + <string key="id">abL-95-pHf</string> + </object> + <object class="IBConnectionRecord"> <object class="IBOutletConnection" key="connection"> <string key="label">delegate</string> <reference key="source" ref="834889278"/> @@ -3638,6 +3779,14 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <object class="IBConnectionRecord"> <object class="IBOutletConnection" key="connection"> <string key="label">delegate</string> + <reference key="source" ref="844630128"/> + <reference key="destination" ref="1001"/> + </object> + <string key="id">SPs-so-H4L</string> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">delegate</string> <reference key="source" ref="160760073"/> <reference key="destination" ref="1001"/> </object> @@ -3659,38 +3808,6 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 </object> <string key="id">1238</string> </object> - <object class="IBConnectionRecord"> - <object class="IBBindingConnection" key="connection"> - <string key="label">value: values.SPExportMemoryModeLastValue</string> - <reference key="source" ref="829070828"/> - <reference key="destination" ref="965845845"/> - <object class="NSNibBindingConnector" key="connector"> - <reference key="NSSource" ref="829070828"/> - <reference key="NSDestination" ref="965845845"/> - <string key="NSLabel">value: values.SPExportMemoryModeLastValue</string> - <string key="NSBinding">value</string> - <string key="NSKeyPath">values.SPExportMemoryModeLastValue</string> - <int key="NSNibBindingConnectorVersion">2</int> - </object> - </object> - <string key="id">1389</string> - </object> - <object class="IBConnectionRecord"> - <object class="IBBindingConnection" key="connection"> - <string key="label">selectedIndex: values.SPExportCompressionLastValue</string> - <reference key="source" ref="257072199"/> - <reference key="destination" ref="965845845"/> - <object class="NSNibBindingConnector" key="connector"> - <reference key="NSSource" ref="257072199"/> - <reference key="NSDestination" ref="965845845"/> - <string key="NSLabel">selectedIndex: values.SPExportCompressionLastValue</string> - <string key="NSBinding">selectedIndex</string> - <string key="NSKeyPath">values.SPExportCompressionLastValue</string> - <int key="NSNibBindingConnectorVersion">2</int> - </object> - </object> - <string key="id">1387</string> - </object> </array> <object class="IBMutableOrderedSet" key="objectRecords"> <array key="orderedObjects"> @@ -3978,8 +4095,9 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <reference ref="93580453"/> <reference ref="185191090"/> <reference ref="429080228"/> - <reference ref="1058029781"/> <reference ref="414783283"/> + <reference ref="745374830"/> + <reference ref="1058029781"/> </array> <reference key="parent" ref="0"/> <string key="objectName">Exporter View</string> @@ -5185,6 +5303,47 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <reference key="object" ref="77737022"/> <reference key="parent" ref="193458164"/> </object> + <object class="IBObjectRecord"> + <string key="id">fTa-Pi-oat</string> + <reference key="object" ref="745374830"/> + <array class="NSMutableArray" key="children"> + <reference ref="949231914"/> + </array> + <reference key="parent" ref="1039688935"/> + </object> + <object class="IBObjectRecord"> + <string key="id">CFF-pW-Ooq</string> + <reference key="object" ref="949231914"/> + <array class="NSMutableArray" key="children"> + <reference ref="136150039"/> + </array> + <reference key="parent" ref="745374830"/> + </object> + <object class="IBObjectRecord"> + <string key="id">FZy-tc-1Lv</string> + <reference key="object" ref="136150039"/> + <array class="NSMutableArray" key="children"> + <reference ref="630979950"/> + <reference ref="1043233379"/> + <reference ref="821214266"/> + </array> + <reference key="parent" ref="949231914"/> + </object> + <object class="IBObjectRecord"> + <string key="id">7YA-Zh-COE</string> + <reference key="object" ref="630979950"/> + <reference key="parent" ref="136150039"/> + </object> + <object class="IBObjectRecord"> + <string key="id">n0o-5Z-jWq</string> + <reference key="object" ref="1043233379"/> + <reference key="parent" ref="136150039"/> + </object> + <object class="IBObjectRecord"> + <string key="id">ENy-fo-Iwf</string> + <reference key="object" ref="821214266"/> + <reference key="parent" ref="136150039"/> + </object> </array> </object> <dictionary class="NSMutableDictionary" key="flattenedProperties"> @@ -5194,6 +5353,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <string key="1.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <string key="1.IBWindowTemplateEditedContentRect">{{538, 113}, {517, 498}}</string> <boolean value="NO" key="1.NSWindowTemplate.visibleAtLaunch"/> + <reference key="1086.IBNSViewMetadataGestureRecognizers" ref="0"/> <string key="1086.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <object class="NSColor" key="1086.IBViewIntegration.shadowColor"> <int key="NSColorSpace">2</int> @@ -5532,6 +5692,28 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <string key="579.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <string key="580.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <string key="581.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="7YA-Zh-COE.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="CFF-pW-Ooq.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <object class="NSMutableDictionary" key="ENy-fo-Iwf.IBAttributePlaceholdersKey"> + <string key="NS.key.0">ToolTip</string> + <object class="IBToolTipAttribute" key="NS.object.0"> + <string key="name">ToolTip</string> + <reference key="object" ref="821214266"/> + <string key="toolTip">Replace the current export settings with settings loaded from disk</string> + </object> + </object> + <string key="ENy-fo-Iwf.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="FZy-tc-1Lv.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="fTa-Pi-oat.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <object class="NSMutableDictionary" key="n0o-5Z-jWq.IBAttributePlaceholdersKey"> + <string key="NS.key.0">ToolTip</string> + <object class="IBToolTipAttribute" key="NS.object.0"> + <string key="name">ToolTip</string> + <reference key="object" ref="1043233379"/> + <string key="toolTip">Save the current export settings to disk</string> + </object> + </object> + <string key="n0o-5Z-jWq.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> </dictionary> <dictionary class="NSMutableDictionary" key="unlocalizedProperties"/> <nil key="activeLocalization"/> @@ -7351,7 +7533,7 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <string key="exportCSVLinesTerminatedField">NSComboBox</string> <string key="exportCSVNULLValuesAsTextField">NSTextField</string> <string key="exportCustomFilenameTokenField">NSTokenField</string> - <string key="exportCustomFilenameTokensField">NSTokenField</string> + <string key="exportCustomFilenameTokenPool">NSTokenField</string> <string key="exportCustomFilenameView">NSView</string> <string key="exportCustomFilenameViewButton">NSButton</string> <string key="exportCustomFilenameViewLabelButton">NSButton</string> @@ -7451,8 +7633,8 @@ y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp2 <string key="name">exportCustomFilenameTokenField</string> <string key="candidateClassName">NSTokenField</string> </object> - <object class="IBToOneOutletInfo" key="exportCustomFilenameTokensField"> - <string key="name">exportCustomFilenameTokensField</string> + <object class="IBToOneOutletInfo" key="exportCustomFilenameTokenPool"> + <string key="name">exportCustomFilenameTokenPool</string> <string key="candidateClassName">NSTokenField</string> </object> <object class="IBToOneOutletInfo" key="exportCustomFilenameView"> diff --git a/Interfaces/English.lproj/FieldEditorSheet.xib b/Interfaces/English.lproj/FieldEditorSheet.xib index 45c07288..f9a9fc84 100644 --- a/Interfaces/English.lproj/FieldEditorSheet.xib +++ b/Interfaces/English.lproj/FieldEditorSheet.xib @@ -629,13 +629,13 @@ <object class="NSTextField" id="40787033"> <reference key="NSNextResponder" ref="74424767"/> <int key="NSvFlags">266</int> - <string key="NSFrame">{{9, 408}, {459, 14}}</string> + <string key="NSFrame">{{9, 408}, {379, 14}}</string> <reference key="NSSuperview" ref="74424767"/> <reference key="NSWindow"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="6880799"> <int key="NSCellFlags">67108928</int> - <int key="NSCellFlags2">1078069248</int> + <int key="NSCellFlags2">1078069760</int> <string key="NSContents">Label</string> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="40787033"/> @@ -658,13 +658,13 @@ <object class="NSButton" id="331104474"> <reference key="NSNextResponder" ref="74424767"/> <int key="NSvFlags">265</int> - <string key="NSFrame">{{470, 406}, {185, 18}}</string> + <string key="NSFrame">{{398, 406}, {257, 18}}</string> <reference key="NSSuperview" ref="74424767"/> <reference key="NSWindow"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="213875702"> <int key="NSCellFlags">67108864</int> - <int key="NSCellFlags2">131072</int> + <int key="NSCellFlags2">67239936</int> <string key="NSContents">Edit All Fields in Pop-up Sheet</string> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="331104474"/> @@ -2374,7 +2374,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="244681273"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">71566336</int> + <int key="NSCellFlags2">71566400</int> <string key="NSContents"/> <reference key="NSSupport" ref="22"/> <reference key="NSControlView" ref="709042804"/> @@ -2398,7 +2398,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="743415448"> <int key="NSCellFlags">-1804074943</int> - <int key="NSCellFlags2">71566336</int> + <int key="NSCellFlags2">71566400</int> <string key="NSContents"/> <reference key="NSSupport" ref="22"/> <reference key="NSControlView" ref="1031401799"/> @@ -2435,7 +2435,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="1026525753"> <int key="NSCellFlags">-2075131840</int> - <int key="NSCellFlags2">71566336</int> + <int key="NSCellFlags2">71566400</int> <string key="NSContents"/> <reference key="NSSupport" ref="22"/> <reference key="NSControlView" ref="1003508336"/> @@ -7269,6 +7269,7 @@ <string key="7.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <reference key="7.IBUserGuides" ref="0"/> <boolean value="NO" key="7.showNotes"/> + <boolean value="NO" key="73.IBNSControlSetsMaxLayoutWidthAtFirstLayoutMetadataKey"/> <string key="73.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <reference key="73.IBUserGuides" ref="0"/> <boolean value="NO" key="73.showNotes"/> diff --git a/Interfaces/English.lproj/GotoDatabaseDialog.xib b/Interfaces/English.lproj/GotoDatabaseDialog.xib index 8525bc7f..1f7755df 100644 --- a/Interfaces/English.lproj/GotoDatabaseDialog.xib +++ b/Interfaces/English.lproj/GotoDatabaseDialog.xib @@ -2,7 +2,7 @@ <archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="8.00"> <data> <int key="IBDocument.SystemTarget">1060</int> - <string key="IBDocument.SystemVersion">13F34</string> + <string key="IBDocument.SystemVersion">13F1096</string> <string key="IBDocument.InterfaceBuilderVersion">6751</string> <string key="IBDocument.AppKitVersion">1265.21</string> <string key="IBDocument.HIToolboxVersion">698.00</string> @@ -477,6 +477,14 @@ </object> <string key="id">O6w-uT-xK7</string> </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">delegate</string> + <reference key="source" ref="1032614363"/> + <reference key="destination" ref="1042702399"/> + </object> + <string key="id">6Ur-iZ-LRv</string> + </object> </array> <object class="IBMutableOrderedSet" key="objectRecords"> <array key="orderedObjects"> @@ -720,7 +728,6 @@ <string key="okClicked:">id</string> <string key="searchChanged:">id</string> <string key="toggleWordSearch:">id</string> - <string key="toogleWordSearch:">id</string> </dictionary> <dictionary class="NSMutableDictionary" key="actionInfosByName"> <object class="IBActionInfo" key="cancelClicked:"> @@ -739,10 +746,6 @@ <string key="name">toggleWordSearch:</string> <string key="candidateClassName">id</string> </object> - <object class="IBActionInfo" key="toogleWordSearch:"> - <string key="name">toogleWordSearch:</string> - <string key="candidateClassName">id</string> - </object> </dictionary> <object class="IBClassDescriptionSource" key="sourceIdentifier"> <string key="majorKey">IBProjectSource</string> 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 @@ <bool key="NSEnabled">YES</bool> <object class="NSComboBoxCell" key="NSCell" id="224560182"> <int key="NSCellFlags">342884417</int> - <int key="NSCellFlags2">132352</int> + <int key="NSCellFlags2">132416</int> <string key="NSContents">"</string> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="455670945"/> @@ -255,7 +255,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSComboBoxCell" key="NSCell" id="762842881"> <int key="NSCellFlags">342884417</int> - <int key="NSCellFlags2">132352</int> + <int key="NSCellFlags2">132416</int> <string key="NSContents">\ or "</string> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="595983007"/> @@ -363,7 +363,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSComboBoxCell" key="NSCell" id="658483425"> <int key="NSCellFlags">342884417</int> - <int key="NSCellFlags2">132352</int> + <int key="NSCellFlags2">132416</int> <string key="NSContents">\n</string> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="704974988"/> @@ -471,7 +471,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSComboBoxCell" key="NSCell" id="674293841"> <int key="NSCellFlags">342884417</int> - <int key="NSCellFlags2">132352</int> + <int key="NSCellFlags2">132416</int> <string key="NSContents">,</string> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="557825428"/> 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 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="1000744513"> <int key="NSCellFlags">-1267728319</int> - <int key="NSCellFlags2">4326400</int> + <int key="NSCellFlags2">4326464</int> <string key="NSContents">PRIMARY</string> <reference key="NSSupport" ref="26"/> <string key="NSPlaceholderString">optional</string> @@ -676,7 +676,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="659211423"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">-1874721792</int> + <int key="NSCellFlags2">-1874721728</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <object class="NSNumberFormatter" key="NSFormatter" id="299133641"> diff --git a/Interfaces/English.lproj/MainMenu.xib b/Interfaces/English.lproj/MainMenu.xib index 54bebacb..31f3ff83 100644 --- a/Interfaces/English.lproj/MainMenu.xib +++ b/Interfaces/English.lproj/MainMenu.xib @@ -937,42 +937,178 @@ </array> </object> </object> - <object class="NSMenuItem" id="631929208"> + <object class="NSMenuItem" id="344418183"> <reference key="NSMenu" ref="565667192"/> - <string key="NSTitle">Spelling</string> + <string key="NSTitle">Spelling and Grammar</string> <string key="NSKeyEquiv"/> - <int key="NSKeyEquivModMask">1048576</int> <int key="NSMnemonicLoc">2147483647</int> <reference key="NSOnImage" ref="325032718"/> <reference key="NSMixedImage" ref="674471825"/> <string key="NSAction">submenuAction:</string> - <reference key="NSTarget" ref="871433454"/> - <object class="NSMenu" key="NSSubmenu" id="871433454"> + <reference key="NSTarget" ref="655178834"/> + <object class="NSMenu" key="NSSubmenu" id="655178834"> <string key="NSTitle">Spelling</string> <array class="NSMutableArray" key="NSMenuItems"> - <object class="NSMenuItem" id="145505496"> - <reference key="NSMenu" ref="871433454"/> - <string key="NSTitle">Spelling...</string> + <object class="NSMenuItem" id="121594454"> + <reference key="NSMenu" ref="655178834"/> + <string key="NSTitle">Show Spelling and Grammar</string> <string key="NSKeyEquiv">:</string> <int key="NSKeyEquivModMask">1048576</int> <int key="NSMnemonicLoc">2147483647</int> <reference key="NSOnImage" ref="325032718"/> <reference key="NSMixedImage" ref="674471825"/> </object> - <object class="NSMenuItem" id="377088066"> - <reference key="NSMenu" ref="871433454"/> - <string key="NSTitle">Check Spelling</string> + <object class="NSMenuItem" id="399282874"> + <reference key="NSMenu" ref="655178834"/> + <string key="NSTitle">Check Document Now</string> <string key="NSKeyEquiv">;</string> <int key="NSKeyEquivModMask">1048576</int> <int key="NSMnemonicLoc">2147483647</int> <reference key="NSOnImage" ref="325032718"/> <reference key="NSMixedImage" ref="674471825"/> </object> - <object class="NSMenuItem" id="594554096"> - <reference key="NSMenu" ref="871433454"/> - <string key="NSTitle">Check Spelling As You Type</string> + <object class="NSMenuItem" id="20635564"> + <reference key="NSMenu" ref="655178834"/> + <bool key="NSIsDisabled">YES</bool> + <bool key="NSIsSeparator">YES</bool> + <string key="NSTitle"/> + <string key="NSKeyEquiv"/> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="325032718"/> + <reference key="NSMixedImage" ref="674471825"/> + </object> + <object class="NSMenuItem" id="862457551"> + <reference key="NSMenu" ref="655178834"/> + <string key="NSTitle">Check Spelling While Typing</string> + <string key="NSKeyEquiv"/> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="325032718"/> + <reference key="NSMixedImage" ref="674471825"/> + </object> + <object class="NSMenuItem" id="677476051"> + <reference key="NSMenu" ref="655178834"/> + <string key="NSTitle">Check Grammar With Spelling</string> + <string key="NSKeyEquiv"/> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="325032718"/> + <reference key="NSMixedImage" ref="674471825"/> + </object> + <object class="NSMenuItem" id="119758306"> + <reference key="NSMenu" ref="655178834"/> + <string key="NSTitle">Correct Spelling Automatically</string> + <string key="NSKeyEquiv"/> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="325032718"/> + <reference key="NSMixedImage" ref="674471825"/> + </object> + </array> + </object> + </object> + <object class="NSMenuItem" id="565610698"> + <reference key="NSMenu" ref="565667192"/> + <string key="NSTitle">Substitutions</string> + <string key="NSKeyEquiv"/> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="325032718"/> + <reference key="NSMixedImage" ref="674471825"/> + <string key="NSAction">submenuAction:</string> + <reference key="NSTarget" ref="485408925"/> + <object class="NSMenu" key="NSSubmenu" id="485408925"> + <string key="NSTitle">Substitutions</string> + <array class="NSMutableArray" key="NSMenuItems"> + <object class="NSMenuItem" id="372292189"> + <reference key="NSMenu" ref="485408925"/> + <string key="NSTitle">Show Substitutions</string> + <string key="NSKeyEquiv"/> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="325032718"/> + <reference key="NSMixedImage" ref="674471825"/> + </object> + <object class="NSMenuItem" id="421164362"> + <reference key="NSMenu" ref="485408925"/> + <bool key="NSIsDisabled">YES</bool> + <bool key="NSIsSeparator">YES</bool> + <string key="NSTitle"/> + <string key="NSKeyEquiv"/> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="325032718"/> + <reference key="NSMixedImage" ref="674471825"/> + </object> + <object class="NSMenuItem" id="971852303"> + <reference key="NSMenu" ref="485408925"/> + <string key="NSTitle">Smart Copy/Paste</string> + <string key="NSKeyEquiv"/> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="325032718"/> + <reference key="NSMixedImage" ref="674471825"/> + </object> + <object class="NSMenuItem" id="567275781"> + <reference key="NSMenu" ref="485408925"/> + <string key="NSTitle">Smart Quotes</string> + <string key="NSKeyEquiv"/> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="325032718"/> + <reference key="NSMixedImage" ref="674471825"/> + </object> + <object class="NSMenuItem" id="683208530"> + <reference key="NSMenu" ref="485408925"/> + <string key="NSTitle">Smart Dashes</string> + <string key="NSKeyEquiv"/> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="325032718"/> + <reference key="NSMixedImage" ref="674471825"/> + </object> + <object class="NSMenuItem" id="476546682"> + <reference key="NSMenu" ref="485408925"/> + <string key="NSTitle">Smart Links</string> + <string key="NSKeyEquiv"/> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="325032718"/> + <reference key="NSMixedImage" ref="674471825"/> + </object> + <object class="NSMenuItem" id="145559693"> + <reference key="NSMenu" ref="485408925"/> + <string key="NSTitle">Data Detectors</string> + <string key="NSKeyEquiv"/> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="325032718"/> + <reference key="NSMixedImage" ref="674471825"/> + </object> + <object class="NSMenuItem" id="271746474"> + <reference key="NSMenu" ref="485408925"/> + <string key="NSTitle">Text Replacement</string> + <string key="NSKeyEquiv"/> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="325032718"/> + <reference key="NSMixedImage" ref="674471825"/> + </object> + </array> + </object> + </object> + <object class="NSMenuItem" id="85027785"> + <reference key="NSMenu" ref="565667192"/> + <string key="NSTitle">Speech</string> + <string key="NSKeyEquiv"/> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="325032718"/> + <reference key="NSMixedImage" ref="674471825"/> + <string key="NSAction">submenuAction:</string> + <reference key="NSTarget" ref="923215070"/> + <object class="NSMenu" key="NSSubmenu" id="923215070"> + <string key="NSTitle">Speech</string> + <array class="NSMutableArray" key="NSMenuItems"> + <object class="NSMenuItem" id="761425893"> + <reference key="NSMenu" ref="923215070"/> + <string key="NSTitle">Start Speaking</string> + <string key="NSKeyEquiv"/> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="325032718"/> + <reference key="NSMixedImage" ref="674471825"/> + </object> + <object class="NSMenuItem" id="644588777"> + <reference key="NSMenu" ref="923215070"/> + <string key="NSTitle">Stop Speaking</string> <string key="NSKeyEquiv"/> - <int key="NSKeyEquivModMask">1048576</int> <int key="NSMnemonicLoc">2147483647</int> <reference key="NSOnImage" ref="325032718"/> <reference key="NSMixedImage" ref="674471825"/> @@ -1043,27 +1179,27 @@ <reference key="NSOnImage" ref="325032718"/> <reference key="NSMixedImage" ref="674471825"/> </object> - <object class="NSMenuItem" id="919117831"> + <object class="NSMenuItem" id="340365447"> <reference key="NSMenu" ref="405399363"/> - <string key="NSTitle">Table Info</string> + <string key="NSTitle">Table Triggers</string> <string key="NSKeyEquiv">4</string> <int key="NSKeyEquivModMask">1048576</int> <int key="NSMnemonicLoc">2147483647</int> <reference key="NSOnImage" ref="325032718"/> <reference key="NSMixedImage" ref="674471825"/> </object> - <object class="NSMenuItem" id="153028940"> + <object class="NSMenuItem" id="919117831"> <reference key="NSMenu" ref="405399363"/> - <string key="NSTitle">Custom Query</string> + <string key="NSTitle">Table Info</string> <string key="NSKeyEquiv">5</string> <int key="NSKeyEquivModMask">1048576</int> <int key="NSMnemonicLoc">2147483647</int> <reference key="NSOnImage" ref="325032718"/> <reference key="NSMixedImage" ref="674471825"/> </object> - <object class="NSMenuItem" id="340365447"> + <object class="NSMenuItem" id="153028940"> <reference key="NSMenu" ref="405399363"/> - <string key="NSTitle">Table Triggers</string> + <string key="NSTitle">Custom Query</string> <string key="NSKeyEquiv">6</string> <int key="NSKeyEquivModMask">1048576</int> <int key="NSMnemonicLoc">2147483647</int> @@ -1709,6 +1845,14 @@ <reference key="NSOnImage" ref="325032718"/> <reference key="NSMixedImage" ref="674471825"/> </object> + <object class="NSMenuItem" id="682123491"> + <reference key="NSMenu" ref="762880388"/> + <string key="NSTitle">Shutdown Server…</string> + <string key="NSKeyEquiv"/> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="325032718"/> + <reference key="NSMixedImage" ref="674471825"/> + </object> </array> </object> <object class="NSAttributedString" key="NSAttributedTitle"> @@ -2597,30 +2741,6 @@ </object> <object class="IBConnectionRecord"> <object class="IBActionConnection" key="connection"> - <string key="label">showGuessPanel:</string> - <reference key="source" ref="1005713010"/> - <reference key="destination" ref="145505496"/> - </object> - <string key="id">188</string> - </object> - <object class="IBConnectionRecord"> - <object class="IBActionConnection" key="connection"> - <string key="label">checkSpelling:</string> - <reference key="source" ref="1005713010"/> - <reference key="destination" ref="377088066"/> - </object> - <string key="id">190</string> - </object> - <object class="IBConnectionRecord"> - <object class="IBActionConnection" key="connection"> - <string key="label">toggleContinuousSpellChecking:</string> - <reference key="source" ref="1005713010"/> - <reference key="destination" ref="594554096"/> - </object> - <string key="id">192</string> - </object> - <object class="IBConnectionRecord"> - <object class="IBActionConnection" key="connection"> <string key="label">toggleTabBarShown:</string> <reference key="source" ref="1005713010"/> <reference key="destination" ref="990323635"/> @@ -3196,6 +3316,126 @@ <string key="id">KxK-Oo-Rsq</string> </object> <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">stopSpeaking:</string> + <reference key="source" ref="1005713010"/> + <reference key="destination" ref="644588777"/> + </object> + <string key="id">yqH-pe-byl</string> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">startSpeaking:</string> + <reference key="source" ref="1005713010"/> + <reference key="destination" ref="761425893"/> + </object> + <string key="id">bd2-UP-v4o</string> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">toggleAutomaticLinkDetection:</string> + <reference key="source" ref="1005713010"/> + <reference key="destination" ref="476546682"/> + </object> + <string key="id">2QJ-2Z-Syo</string> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">toggleAutomaticTextReplacement:</string> + <reference key="source" ref="1005713010"/> + <reference key="destination" ref="271746474"/> + </object> + <string key="id">mFh-hJ-6K7</string> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">toggleAutomaticQuoteSubstitution:</string> + <reference key="source" ref="1005713010"/> + <reference key="destination" ref="567275781"/> + </object> + <string key="id">i1g-fk-Nyq</string> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">orderFrontSubstitutionsPanel:</string> + <reference key="source" ref="1005713010"/> + <reference key="destination" ref="372292189"/> + </object> + <string key="id">khk-AE-cvv</string> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">toggleAutomaticDataDetection:</string> + <reference key="source" ref="1005713010"/> + <reference key="destination" ref="145559693"/> + </object> + <string key="id">RxE-Rn-s3M</string> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">toggleAutomaticDashSubstitution:</string> + <reference key="source" ref="1005713010"/> + <reference key="destination" ref="683208530"/> + </object> + <string key="id">HEV-LH-OBw</string> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">toggleSmartInsertDelete:</string> + <reference key="source" ref="1005713010"/> + <reference key="destination" ref="971852303"/> + </object> + <string key="id">UZx-xw-zrS</string> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">toggleGrammarChecking:</string> + <reference key="source" ref="1005713010"/> + <reference key="destination" ref="677476051"/> + </object> + <string key="id">Ovb-9u-igz</string> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">showGuessPanel:</string> + <reference key="source" ref="1005713010"/> + <reference key="destination" ref="121594454"/> + </object> + <string key="id">kGB-mB-OYr</string> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">checkSpelling:</string> + <reference key="source" ref="1005713010"/> + <reference key="destination" ref="399282874"/> + </object> + <string key="id">ecI-hi-eLa</string> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">toggleContinuousSpellChecking:</string> + <reference key="source" ref="1005713010"/> + <reference key="destination" ref="862457551"/> + </object> + <string key="id">nn2-tQ-SiI</string> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">toggleAutomaticSpellingCorrection:</string> + <reference key="source" ref="1005713010"/> + <reference key="destination" ref="119758306"/> + </object> + <string key="id">mhI-Hy-VAZ</string> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">shutdownServer:</string> + <reference key="source" ref="1005713010"/> + <reference key="destination" ref="682123491"/> + </object> + <string key="id">S1h-UE-mTv</string> + </object> + <object class="IBConnectionRecord"> <object class="IBOutletConnection" key="connection"> <string key="label">delegate</string> <reference key="source" ref="524643114"/> @@ -3688,7 +3928,9 @@ <reference ref="775296247"/> <reference ref="986852824"/> <reference ref="125958105"/> - <reference ref="631929208"/> + <reference ref="85027785"/> + <reference ref="565610698"/> + <reference ref="344418183"/> </array> <reference key="parent" ref="160028389"/> </object> @@ -3972,39 +4214,6 @@ <reference key="parent" ref="290049938"/> </object> <object class="IBObjectRecord"> - <string key="id">184</string> - <reference key="object" ref="631929208"/> - <array class="NSMutableArray" key="children"> - <reference ref="871433454"/> - </array> - <reference key="parent" ref="565667192"/> - </object> - <object class="IBObjectRecord"> - <string key="id">185</string> - <reference key="object" ref="871433454"/> - <array class="NSMutableArray" key="children"> - <reference ref="145505496"/> - <reference ref="377088066"/> - <reference ref="594554096"/> - </array> - <reference key="parent" ref="631929208"/> - </object> - <object class="IBObjectRecord"> - <string key="id">187</string> - <reference key="object" ref="145505496"/> - <reference key="parent" ref="871433454"/> - </object> - <object class="IBObjectRecord"> - <string key="id">189</string> - <reference key="object" ref="377088066"/> - <reference key="parent" ref="871433454"/> - </object> - <object class="IBObjectRecord"> - <string key="id">191</string> - <reference key="object" ref="594554096"/> - <reference key="parent" ref="871433454"/> - </object> - <object class="IBObjectRecord"> <string key="id">496</string> <reference key="object" ref="852669482"/> <array class="NSMutableArray" key="children"> @@ -4023,7 +4232,6 @@ <reference ref="217386801"/> <reference ref="919117831"/> <reference ref="153028940"/> - <reference ref="340365447"/> <reference ref="344409374"/> <reference ref="898345065"/> <reference ref="165538449"/> @@ -4038,6 +4246,7 @@ <reference ref="1005782740"/> <reference ref="793101047"/> <reference ref="384637681"/> + <reference ref="340365447"/> <reference ref="403457070"/> </array> <reference key="parent" ref="852669482"/> @@ -4192,6 +4401,7 @@ <reference ref="607642194"/> <reference ref="859280893"/> <reference ref="346403677"/> + <reference ref="682123491"/> </array> <reference key="parent" ref="19149132"/> </object> @@ -4796,6 +5006,152 @@ <reference key="object" ref="383065474"/> <reference key="parent" ref="315159986"/> </object> + <object class="IBObjectRecord"> + <string key="id">qK5-Ty-Uao</string> + <reference key="object" ref="85027785"/> + <array class="NSMutableArray" key="children"> + <reference ref="923215070"/> + </array> + <reference key="parent" ref="565667192"/> + </object> + <object class="IBObjectRecord"> + <string key="id">mGA-xh-Uwq</string> + <reference key="object" ref="923215070"/> + <array class="NSMutableArray" key="children"> + <reference ref="644588777"/> + <reference ref="761425893"/> + </array> + <reference key="parent" ref="85027785"/> + </object> + <object class="IBObjectRecord"> + <string key="id">YwR-Sa-nn5</string> + <reference key="object" ref="644588777"/> + <reference key="parent" ref="923215070"/> + </object> + <object class="IBObjectRecord"> + <string key="id">elh-eq-jVU</string> + <reference key="object" ref="761425893"/> + <reference key="parent" ref="923215070"/> + </object> + <object class="IBObjectRecord"> + <string key="id">zbw-kN-6gM</string> + <reference key="object" ref="565610698"/> + <array class="NSMutableArray" key="children"> + <reference ref="485408925"/> + </array> + <reference key="parent" ref="565667192"/> + </object> + <object class="IBObjectRecord"> + <string key="id">tMm-qx-zUV</string> + <reference key="object" ref="485408925"/> + <array class="NSMutableArray" key="children"> + <reference ref="271746474"/> + <reference ref="145559693"/> + <reference ref="476546682"/> + <reference ref="683208530"/> + <reference ref="567275781"/> + <reference ref="971852303"/> + <reference ref="421164362"/> + <reference ref="372292189"/> + </array> + <reference key="parent" ref="565610698"/> + </object> + <object class="IBObjectRecord"> + <string key="id">D2l-4I-GKa</string> + <reference key="object" ref="271746474"/> + <reference key="parent" ref="485408925"/> + </object> + <object class="IBObjectRecord"> + <string key="id">rv0-EH-lPj</string> + <reference key="object" ref="145559693"/> + <reference key="parent" ref="485408925"/> + </object> + <object class="IBObjectRecord"> + <string key="id">sGf-4f-QUu</string> + <reference key="object" ref="476546682"/> + <reference key="parent" ref="485408925"/> + </object> + <object class="IBObjectRecord"> + <string key="id">Fct-0j-ck4</string> + <reference key="object" ref="683208530"/> + <reference key="parent" ref="485408925"/> + </object> + <object class="IBObjectRecord"> + <string key="id">u1T-CX-dx3</string> + <reference key="object" ref="567275781"/> + <reference key="parent" ref="485408925"/> + </object> + <object class="IBObjectRecord"> + <string key="id">c7b-hr-jQv</string> + <reference key="object" ref="971852303"/> + <reference key="parent" ref="485408925"/> + </object> + <object class="IBObjectRecord"> + <string key="id">LHC-3N-tJv</string> + <reference key="object" ref="421164362"/> + <reference key="parent" ref="485408925"/> + </object> + <object class="IBObjectRecord"> + <string key="id">Kk3-QH-mmH</string> + <reference key="object" ref="372292189"/> + <reference key="parent" ref="485408925"/> + </object> + <object class="IBObjectRecord"> + <string key="id">KPx-re-8EE</string> + <reference key="object" ref="344418183"/> + <array class="NSMutableArray" key="children"> + <reference ref="655178834"/> + </array> + <reference key="parent" ref="565667192"/> + </object> + <object class="IBObjectRecord"> + <string key="id">UYw-Xs-8vf</string> + <reference key="object" ref="655178834"/> + <array class="NSMutableArray" key="children"> + <reference ref="119758306"/> + <reference ref="677476051"/> + <reference ref="862457551"/> + <reference ref="20635564"/> + <reference ref="399282874"/> + <reference ref="121594454"/> + </array> + <reference key="parent" ref="344418183"/> + </object> + <object class="IBObjectRecord"> + <string key="id">Es7-1o-KcI</string> + <reference key="object" ref="119758306"/> + <reference key="parent" ref="655178834"/> + </object> + <object class="IBObjectRecord"> + <string key="id">oqd-zp-I6d</string> + <reference key="object" ref="677476051"/> + <reference key="parent" ref="655178834"/> + </object> + <object class="IBObjectRecord"> + <string key="id">1zB-4V-BF2</string> + <reference key="object" ref="862457551"/> + <reference key="parent" ref="655178834"/> + </object> + <object class="IBObjectRecord"> + <string key="id">Wgq-So-DN8</string> + <reference key="object" ref="20635564"/> + <reference key="parent" ref="655178834"/> + </object> + <object class="IBObjectRecord"> + <string key="id">fCE-bz-bNv</string> + <reference key="object" ref="399282874"/> + <reference key="parent" ref="655178834"/> + </object> + <object class="IBObjectRecord"> + <string key="id">05T-H3-24V</string> + <reference key="object" ref="121594454"/> + <reference key="parent" ref="655178834"/> + </object> + <object class="IBObjectRecord"> + <string key="id">jZI-ad-FsC</string> + <reference key="object" ref="682123491"/> + <reference key="parent" ref="762880388"/> + </object> </array> </object> <dictionary class="NSMutableDictionary" key="flattenedProperties"> @@ -4805,6 +5161,7 @@ <boolean value="NO" key="-2.showNotes"/> <string key="-3.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <boolean value="NO" key="-3.showNotes"/> + <string key="05T-H3-24V.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <string key="0ww-n8-YRC.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <boolean value="NO" key="0ww-n8-YRC.showNotes"/> <string key="1000.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> @@ -5059,20 +5416,11 @@ <boolean value="NO" key="173.showNotes"/> <string key="174.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <boolean value="NO" key="174.showNotes"/> - <string key="184.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> - <boolean value="NO" key="184.showNotes"/> - <string key="185.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> - <boolean value="NO" key="185.showNotes"/> - <string key="187.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> - <boolean value="NO" key="187.showNotes"/> - <string key="189.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> - <boolean value="NO" key="189.showNotes"/> <string key="19.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <boolean value="NO" key="19.showNotes"/> - <string key="191.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> - <boolean value="NO" key="191.showNotes"/> <string key="199.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <boolean value="NO" key="199.showNotes"/> + <string key="1zB-4V-BF2.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <string key="213.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <boolean value="NO" key="213.showNotes"/> <string key="23.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> @@ -5426,9 +5774,30 @@ </object> <string key="998.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <boolean value="NO" key="998.showNotes"/> + <string key="D2l-4I-GKa.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="Es7-1o-KcI.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="Fct-0j-ck4.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <string key="JtW-Ko-SuZ.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="KPx-re-8EE.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="Kk3-QH-mmH.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="LHC-3N-tJv.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="UYw-Xs-8vf.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="Wgq-So-DN8.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="YwR-Sa-nn5.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <string key="Yzp-Qk-v7Y.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="c7b-hr-jQv.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="elh-eq-jVU.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="fCE-bz-bNv.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="jZI-ad-FsC.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="mGA-xh-Uwq.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <string key="nXE-rI-RyI.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="oqd-zp-I6d.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="qK5-Ty-Uao.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="rv0-EH-lPj.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="sGf-4f-QUu.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="tMm-qx-zUV.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="u1T-CX-dx3.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="zbw-kN-6gM.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> </dictionary> <dictionary class="NSMutableDictionary" key="unlocalizedProperties"/> <nil key="activeLocalization"/> @@ -5723,17 +6092,6 @@ <string key="candidateClassName">id</string> </object> </dictionary> - <object class="NSMutableDictionary" key="outlets"> - <string key="NS.key.0">bundleEditorWindow</string> - <string key="NS.object.0">NSWindow</string> - </object> - <object class="NSMutableDictionary" key="toOneOutletInfosByName"> - <string key="NS.key.0">bundleEditorWindow</string> - <object class="IBToOneOutletInfo" key="NS.object.0"> - <string key="name">bundleEditorWindow</string> - <string key="candidateClassName">NSWindow</string> - </object> - </object> <object class="IBClassDescriptionSource" key="sourceIdentifier"> <string key="majorKey">IBProjectSource</string> <string key="minorKey">../Source/SPAppController.h</string> @@ -6395,7 +6753,6 @@ <string key="importFavorites:">id</string> <string key="initiateConnection:">id</string> <string key="makeSelectedFavoriteDefault:">id</string> - <string key="nodeDoubleClicked:">id</string> <string key="removeNode:">id</string> <string key="renameNode:">id</string> <string key="reverseSortFavorites:">NSMenuItem</string> @@ -6446,10 +6803,6 @@ <string key="name">makeSelectedFavoriteDefault:</string> <string key="candidateClassName">id</string> </object> - <object class="IBActionInfo" key="nodeDoubleClicked:"> - <string key="name">nodeDoubleClicked:</string> - <string key="candidateClassName">id</string> - </object> <object class="IBActionInfo" key="removeNode:"> <string key="name">removeNode:</string> <string key="candidateClassName">id</string> @@ -6765,7 +7118,6 @@ <string key="importFavorites:">id</string> <string key="initiateConnection:">id</string> <string key="makeSelectedFavoriteDefault:">id</string> - <string key="nodeDoubleClicked:">id</string> <string key="removeNode:">id</string> <string key="renameNode:">id</string> <string key="saveFavorite:">id</string> @@ -6814,10 +7166,6 @@ <string key="name">makeSelectedFavoriteDefault:</string> <string key="candidateClassName">id</string> </object> - <object class="IBActionInfo" key="nodeDoubleClicked:"> - <string key="name">nodeDoubleClicked:</string> - <string key="candidateClassName">id</string> - </object> <object class="IBActionInfo" key="removeNode:"> <string key="name">removeNode:</string> <string key="candidateClassName">id</string> @@ -7509,6 +7857,7 @@ <string key="showServerProcesses:">id</string> <string key="showServerVariables:">id</string> <string key="showUserManager:">id</string> + <string key="shutdownServer:">id</string> <string key="toggleNavigator:">id</string> <string key="validateSaveConnectionAccessory:">id</string> </dictionary> @@ -7681,6 +8030,10 @@ <string key="name">showUserManager:</string> <string key="candidateClassName">id</string> </object> + <object class="IBActionInfo" key="shutdownServer:"> + <string key="name">shutdownServer:</string> + <string key="candidateClassName">id</string> + </object> <object class="IBActionInfo" key="toggleNavigator:"> <string key="name">toggleNavigator:</string> <string key="candidateClassName">id</string> @@ -7747,6 +8100,7 @@ <string key="tableDataInstance">SPTableData</string> <string key="tableDumpInstance">id</string> <string key="tableInfoScrollView">NSScrollView</string> + <string key="tableInfoSplitView">SPSplitView</string> <string key="tableInfoTable">NSTableView</string> <string key="tableRelationsInstance">id</string> <string key="tableSourceInstance">SPTableStructure</string> @@ -7986,6 +8340,10 @@ <string key="name">tableInfoScrollView</string> <string key="candidateClassName">NSScrollView</string> </object> + <object class="IBToOneOutletInfo" key="tableInfoSplitView"> + <string key="name">tableInfoSplitView</string> + <string key="candidateClassName">SPSplitView</string> + </object> <object class="IBToOneOutletInfo" key="tableInfoTable"> <string key="name">tableInfoTable</string> <string key="candidateClassName">NSTableView</string> @@ -8089,6 +8447,7 @@ <string key="showServerProcesses:">id</string> <string key="showServerVariables:">id</string> <string key="showUserManager:">id</string> + <string key="shutdownServer:">id</string> <string key="toggleNavigator:">id</string> <string key="validateSaveConnectionAccessory:">id</string> </dictionary> @@ -8261,6 +8620,10 @@ <string key="name">showUserManager:</string> <string key="candidateClassName">id</string> </object> + <object class="IBActionInfo" key="shutdownServer:"> + <string key="name">shutdownServer:</string> + <string key="candidateClassName">id</string> + </object> <object class="IBActionInfo" key="toggleNavigator:"> <string key="name">toggleNavigator:</string> <string key="candidateClassName">id</string> @@ -8516,7 +8879,7 @@ <string key="exportCSVLinesTerminatedField">NSComboBox</string> <string key="exportCSVNULLValuesAsTextField">NSTextField</string> <string key="exportCustomFilenameTokenField">NSTokenField</string> - <string key="exportCustomFilenameTokensField">NSTokenField</string> + <string key="exportCustomFilenameTokenPool">NSTokenField</string> <string key="exportCustomFilenameView">NSView</string> <string key="exportCustomFilenameViewButton">NSButton</string> <string key="exportCustomFilenameViewLabelButton">NSButton</string> @@ -8616,8 +8979,8 @@ <string key="name">exportCustomFilenameTokenField</string> <string key="candidateClassName">NSTokenField</string> </object> - <object class="IBToOneOutletInfo" key="exportCustomFilenameTokensField"> - <string key="name">exportCustomFilenameTokensField</string> + <object class="IBToOneOutletInfo" key="exportCustomFilenameTokenPool"> + <string key="name">exportCustomFilenameTokenPool</string> <string key="candidateClassName">NSTokenField</string> </object> <object class="IBToOneOutletInfo" key="exportCustomFilenameView"> @@ -8882,12 +9245,54 @@ </object> </object> <object class="IBPartialClassDescription"> + <string key="className">SPExportController</string> + <dictionary class="NSMutableDictionary" key="actions"> + <string key="exportCurrentSettings:">id</string> + <string key="importCurrentSettings:">id</string> + </dictionary> + <dictionary class="NSMutableDictionary" key="actionInfosByName"> + <object class="IBActionInfo" key="exportCurrentSettings:"> + <string key="name">exportCurrentSettings:</string> + <string key="candidateClassName">id</string> + </object> + <object class="IBActionInfo" key="importCurrentSettings:"> + <string key="name">importCurrentSettings:</string> + <string key="candidateClassName">id</string> + </object> + </dictionary> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBProjectSource</string> + <string key="minorKey">../Source/SPExportSettingsPersistence.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">SPExportController</string> + <dictionary class="NSMutableDictionary" key="actions"> + <string key="exportCurrentSettings:">id</string> + <string key="importCurrentSettings:">id</string> + </dictionary> + <dictionary class="NSMutableDictionary" key="actionInfosByName"> + <object class="IBActionInfo" key="exportCurrentSettings:"> + <string key="name">exportCurrentSettings:</string> + <string key="candidateClassName">id</string> + </object> + <object class="IBActionInfo" key="importCurrentSettings:"> + <string key="name">importCurrentSettings:</string> + <string key="candidateClassName">id</string> + </object> + </dictionary> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBProjectSource</string> + <string key="minorKey">../Source/SPExportSettingsPersistence.m</string> + </object> + </object> + <object class="IBPartialClassDescription"> <string key="className">SPExtendedTableInfo</string> <string key="superclassName">NSObject</string> <dictionary class="NSMutableDictionary" key="actions"> <string key="reloadTable:">id</string> <string key="resetAutoIncrement:">id</string> - <string key="resetAutoIncrementValueWasEdited:">id</string> + <string key="tableRowAutoIncrementWasEdited:">id</string> <string key="updateTableCollation:">id</string> <string key="updateTableEncoding:">id</string> <string key="updateTableType:">id</string> @@ -8901,8 +9306,8 @@ <string key="name">resetAutoIncrement:</string> <string key="candidateClassName">id</string> </object> - <object class="IBActionInfo" key="resetAutoIncrementValueWasEdited:"> - <string key="name">resetAutoIncrementValueWasEdited:</string> + <object class="IBActionInfo" key="tableRowAutoIncrementWasEdited:"> + <string key="name">tableRowAutoIncrementWasEdited:</string> <string key="candidateClassName">id</string> </object> <object class="IBActionInfo" key="updateTableCollation:"> @@ -9037,7 +9442,7 @@ <dictionary class="NSMutableDictionary" key="actions"> <string key="reloadTable:">id</string> <string key="resetAutoIncrement:">id</string> - <string key="resetAutoIncrementValueWasEdited:">id</string> + <string key="tableRowAutoIncrementWasEdited:">id</string> <string key="updateTableCollation:">id</string> <string key="updateTableEncoding:">id</string> <string key="updateTableType:">id</string> @@ -9051,8 +9456,8 @@ <string key="name">resetAutoIncrement:</string> <string key="candidateClassName">id</string> </object> - <object class="IBActionInfo" key="resetAutoIncrementValueWasEdited:"> - <string key="name">resetAutoIncrementValueWasEdited:</string> + <object class="IBActionInfo" key="tableRowAutoIncrementWasEdited:"> + <string key="name">tableRowAutoIncrementWasEdited:</string> <string key="candidateClassName">id</string> </object> <object class="IBActionInfo" key="updateTableCollation:"> @@ -10522,7 +10927,7 @@ <string key="superclassName">NSObject</string> <dictionary class="NSMutableDictionary" key="outlets"> <string key="activitiesTable">NSTableView</string> - <string key="infoTable">id</string> + <string key="infoTable">NSTableView</string> <string key="tableDataInstance">id</string> <string key="tableDocumentInstance">id</string> <string key="tableInfoScrollView">NSScrollView</string> @@ -10536,7 +10941,7 @@ </object> <object class="IBToOneOutletInfo" key="infoTable"> <string key="name">infoTable</string> - <string key="candidateClassName">id</string> + <string key="candidateClassName">NSTableView</string> </object> <object class="IBToOneOutletInfo" key="tableDataInstance"> <string key="name">tableDataInstance</string> @@ -10632,6 +11037,8 @@ <string key="resetAutoIncrementSheet">id</string> <string key="resetAutoIncrementValue">id</string> <string key="structureGrabber">id</string> + <string key="structureHelpPanel">NSPanel</string> + <string key="structureHelpText">NSTextView</string> <string key="tableDataInstance">SPTableData</string> <string key="tableDocumentInstance">SPDatabaseDocument</string> <string key="tableInfoInstance">SPTableInfo</string> @@ -10721,6 +11128,14 @@ <string key="name">structureGrabber</string> <string key="candidateClassName">id</string> </object> + <object class="IBToOneOutletInfo" key="structureHelpPanel"> + <string key="name">structureHelpPanel</string> + <string key="candidateClassName">NSPanel</string> + </object> + <object class="IBToOneOutletInfo" key="structureHelpText"> + <string key="name">structureHelpText</string> + <string key="candidateClassName">NSTextView</string> + </object> <object class="IBToOneOutletInfo" key="tableDataInstance"> <string key="name">tableDataInstance</string> <string key="candidateClassName">SPTableData</string> @@ -11978,6 +12393,33 @@ </object> </object> <object class="IBPartialClassDescription"> + <string key="className">PSMTabBarControl</string> + <string key="superclassName">NSControl</string> + <dictionary class="NSMutableDictionary" key="outlets"> + <string key="delegate">id</string> + <string key="partnerView">id</string> + <string key="tabView">NSTabView</string> + </dictionary> + <dictionary class="NSMutableDictionary" key="toOneOutletInfosByName"> + <object class="IBToOneOutletInfo" key="delegate"> + <string key="name">delegate</string> + <string key="candidateClassName">id</string> + </object> + <object class="IBToOneOutletInfo" key="partnerView"> + <string key="name">partnerView</string> + <string key="candidateClassName">id</string> + </object> + <object class="IBToOneOutletInfo" key="tabView"> + <string key="name">tabView</string> + <string key="candidateClassName">NSTabView</string> + </object> + </dictionary> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">PSMTabBar.framework/Headers/PSMTabBarControl.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> <string key="className">QCView</string> <string key="superclassName">NSView</string> <dictionary class="NSMutableDictionary" key="actions"> 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 @@ <bool key="NSEnabled">YES</bool> <object class="NSSearchFieldCell" key="NSCell" id="753691092"> <int key="NSCellFlags">342884416</int> - <int key="NSCellFlags2">268600320</int> + <int key="NSCellFlags2">268600384</int> <string key="NSContents"/> <object class="NSFont" key="NSSupport" id="26"> <bool key="IBIsSystemFont">YES</bool> 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 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="17465994"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">4326400</int> + <int key="NSCellFlags2">4326464</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="336557380"/> @@ -1207,7 +1207,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="875775803"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">138413056</int> + <int key="NSCellFlags2">138413120</int> <real value="20" key="NSContents"/> <reference key="NSSupport" ref="320262838"/> <object class="NSNumberFormatter" key="NSFormatter" id="202149751"> @@ -1781,7 +1781,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="585678618"> <int key="NSCellFlags">-1804599232</int> - <int key="NSCellFlags2">71304192</int> + <int key="NSCellFlags2">71304256</int> <integer value="100" key="NSContents"/> <reference key="NSSupport" ref="320262838"/> <object class="NSNumberFormatter" key="NSFormatter" id="404558425"> @@ -1857,7 +1857,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="82744709"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="320262838"/> <string key="NSPlaceholderString">NULL</string> @@ -2855,7 +2855,7 @@ AQAAAAA</bytes> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="170658213"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">-1874852864</int> + <int key="NSCellFlags2">-1874852800</int> <string key="NSContents"/> <reference key="NSSupport" ref="320262838"/> <object class="NSNumberFormatter" key="NSFormatter" id="625381134"> @@ -3031,7 +3031,7 @@ AQAAAAA</bytes> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="546511116"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">-2076048384</int> + <int key="NSCellFlags2">-2076048320</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <object class="NSNumberFormatter" key="NSFormatter" id="242266609"> @@ -3413,7 +3413,7 @@ AQAAAAA</bytes> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="248584789"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">71435264</int> + <int key="NSCellFlags2">71435328</int> <reference key="NSSupport" ref="26"/> <object class="NSNumberFormatter" key="NSFormatter" id="141894253"> <dictionary class="NSMutableDictionary" key="NS.attributes"> @@ -3762,7 +3762,7 @@ AQAAAAA</bytes> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="409964321"> <int key="NSCellFlags">-1538260928</int> - <int key="NSCellFlags2">71435264</int> + <int key="NSCellFlags2">71435328</int> <real value="0.5" key="NSContents"/> <reference key="NSSupport" ref="26"/> <object class="NSNumberFormatter" key="NSFormatter" id="706329937"> @@ -3902,7 +3902,7 @@ AQAAAAA</bytes> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="992523117"> <int key="NSCellFlags">-1538260928</int> - <int key="NSCellFlags2">-2076048384</int> + <int key="NSCellFlags2">-2076048320</int> <string key="NSContents">0.1</string> <reference key="NSSupport" ref="26"/> <object class="NSNumberFormatter" key="NSFormatter" id="223702941"> @@ -4139,7 +4139,7 @@ AQAAAAA</bytes> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="749914587"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="320262838"/> <string key="NSPlaceholderString">System default</string> 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 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="276546601"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272761856</int> + <int key="NSCellFlags2">272761920</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <reference key="NSControlView" ref="92903832"/> @@ -994,7 +994,7 @@ <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="474077606"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272761856</int> + <int key="NSCellFlags2">272761920</int> <string key="NSContents"/> <reference key="NSSupport" ref="26"/> <string key="NSPlaceholderString">[no selection]</string> 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 @@ <bool key="NSEnabled">YES</bool> <object class="NSSecureTextFieldCell" key="NSCell" id="709544507"> <int key="NSCellFlags">342884416</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="313221240"/> <reference key="NSControlView" ref="951010250"/> 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 @@ <bool key="NSEnabled">YES</bool> <object class="NSSecureTextFieldCell" key="NSCell" id="23878540"> <int key="NSCellFlags">342884416</int> - <int key="NSCellFlags2">272761856</int> + <int key="NSCellFlags2">272761920</int> <string key="NSContents"/> <object class="NSFont" key="NSSupport" id="26"> <bool key="IBIsSystemFont">YES</bool> diff --git a/Interfaces/English.lproj/UserManagerView.xib b/Interfaces/English.lproj/UserManagerView.xib index 9939ae49..60c8f334 100644 --- a/Interfaces/English.lproj/UserManagerView.xib +++ b/Interfaces/English.lproj/UserManagerView.xib @@ -510,12 +510,13 @@ <string key="NSFrame">{{13, 40}, {611, 460}}</string> <reference key="NSSuperview" ref="755335467"/> <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="143215913"/> <array class="NSMutableArray" key="NSTabViewItems"> <object class="NSTabViewItem" id="820796939"> <string key="NSIdentifier">General</string> <object class="NSView" key="NSView" id="143215913"> - <nil key="NSNextResponder"/> - <int key="NSvFlags">256</int> + <reference key="NSNextResponder" ref="716372522"/> + <int key="NSvFlags">274</int> <array class="NSMutableArray" key="NSSubviews"> <object class="NSBox" id="813339681"> <reference key="NSNextResponder" ref="143215913"/> @@ -530,6 +531,8 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{3, 46}, {134, 17}}</string> <reference key="NSSuperview" ref="338485078"/> + <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="1021720165"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="634743178"> <int key="NSCellFlags">68157504</int> @@ -553,6 +556,8 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{3, 16}, {134, 17}}</string> <reference key="NSSuperview" ref="338485078"/> + <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="926329636"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="403703009"> <int key="NSCellFlags">68157504</int> @@ -571,10 +576,12 @@ <int key="NSvFlags">266</int> <string key="NSFrame">{{142, 44}, {204, 22}}</string> <reference key="NSSuperview" ref="338485078"/> + <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="810288614"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="540584164"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="746597359"/> <reference key="NSControlView" ref="1021720165"/> @@ -603,10 +610,12 @@ <int key="NSvFlags">266</int> <string key="NSFrame">{{142, 14}, {204, 22}}</string> <reference key="NSSuperview" ref="338485078"/> + <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="716372522"/> <bool key="NSEnabled">YES</bool> <object class="NSSecureTextFieldCell" key="NSCell" id="71857161"> <int key="NSCellFlags">342884416</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <string key="NSContents"/> <reference key="NSSupport" ref="746597359"/> <reference key="NSControlView" ref="926329636"/> @@ -623,10 +632,14 @@ </array> <string key="NSFrame">{{1, 1}, {364, 76}}</string> <reference key="NSSuperview" ref="813339681"/> + <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="810516533"/> </object> </array> <string key="NSFrame">{{112, 175}, {366, 92}}</string> <reference key="NSSuperview" ref="143215913"/> + <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="338485078"/> <string key="NSOffsets">{0, 0}</string> <object class="NSTextFieldCell" key="NSTitleCell"> <int key="NSCellFlags">67108864</int> @@ -647,6 +660,9 @@ </object> </array> <string key="NSFrame">{{10, 33}, {591, 414}}</string> + <reference key="NSSuperview" ref="716372522"/> + <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="813339681"/> </object> <string key="NSLabel">General</string> <reference key="NSColor" ref="409859189"/> @@ -656,7 +672,7 @@ <string key="NSIdentifier">Global Privileges</string> <object class="NSView" key="NSView" id="202198269"> <nil key="NSNextResponder"/> - <int key="NSvFlags">288</int> + <int key="NSvFlags">274</int> <array class="NSMutableArray" key="NSSubviews"> <object class="NSCustomView" id="386290455"> <reference key="NSNextResponder" ref="202198269"/> @@ -675,6 +691,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 32}, {221, 18}}</string> <reference key="NSSuperview" ref="507349185"/> + <reference key="NSNextKeyView" ref="557764028"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="919244938"> <int key="NSCellFlags">67108864</int> @@ -703,6 +720,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 12}, {221, 18}}</string> <reference key="NSSuperview" ref="507349185"/> + <reference key="NSNextKeyView" ref="610554589"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="3268085"> <int key="NSCellFlags">67108864</int> @@ -724,10 +742,12 @@ </array> <string key="NSFrame">{{1, 1}, {253, 58}}</string> <reference key="NSSuperview" ref="223367051"/> + <reference key="NSNextKeyView" ref="639415915"/> </object> </array> <string key="NSFrame">{{17, 75}, {255, 74}}</string> <reference key="NSSuperview" ref="386290455"/> + <reference key="NSNextKeyView" ref="507349185"/> <string key="NSOffsets">{0, 0}</string> <object class="NSTextFieldCell" key="NSTitleCell"> <int key="NSCellFlags">67108864</int> @@ -757,8 +777,9 @@ <object class="NSButton" id="165190944"> <reference key="NSNextResponder" ref="1066741257"/> <int key="NSvFlags">268</int> - <string key="NSFrame">{{16, 192}, {124, 18}}</string> + <string key="NSFrame">{{16, 192}, {112, 18}}</string> <reference key="NSSuperview" ref="1066741257"/> + <reference key="NSNextKeyView" ref="1012114470"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="68967500"> <int key="NSCellFlags">67108864</int> @@ -780,8 +801,9 @@ <object class="NSButton" id="948609625"> <reference key="NSNextResponder" ref="1066741257"/> <int key="NSvFlags">268</int> - <string key="NSFrame">{{16, 172}, {259, 18}}</string> + <string key="NSFrame">{{16, 172}, {112, 18}}</string> <reference key="NSSuperview" ref="1066741257"/> + <reference key="NSNextKeyView" ref="93457599"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="410960559"> <int key="NSCellFlags">67108864</int> @@ -805,6 +827,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 152}, {259, 18}}</string> <reference key="NSSuperview" ref="1066741257"/> + <reference key="NSNextKeyView" ref="654009227"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="231869678"> <int key="NSCellFlags">67108864</int> @@ -828,6 +851,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 132}, {259, 18}}</string> <reference key="NSSuperview" ref="1066741257"/> + <reference key="NSNextKeyView" ref="341346472"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="144741035"> <int key="NSCellFlags">67108864</int> @@ -851,6 +875,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 112}, {259, 18}}</string> <reference key="NSSuperview" ref="1066741257"/> + <reference key="NSNextKeyView" ref="460063174"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="967891320"> <int key="NSCellFlags">67108864</int> @@ -874,6 +899,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 92}, {259, 18}}</string> <reference key="NSSuperview" ref="1066741257"/> + <reference key="NSNextKeyView" ref="712816655"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="315454579"> <int key="NSCellFlags">67108864</int> @@ -897,6 +923,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 72}, {259, 18}}</string> <reference key="NSSuperview" ref="1066741257"/> + <reference key="NSNextKeyView" ref="1026585310"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="40570585"> <int key="NSCellFlags">67108864</int> @@ -920,6 +947,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 52}, {259, 18}}</string> <reference key="NSSuperview" ref="1066741257"/> + <reference key="NSNextKeyView" ref="373270478"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="799488283"> <int key="NSCellFlags">67108864</int> @@ -943,6 +971,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 32}, {259, 18}}</string> <reference key="NSSuperview" ref="1066741257"/> + <reference key="NSNextKeyView" ref="285378693"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="1072458811"> <int key="NSCellFlags">67108864</int> @@ -966,6 +995,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 12}, {259, 18}}</string> <reference key="NSSuperview" ref="1066741257"/> + <reference key="NSNextKeyView" ref="716372522"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="205758531"> <int key="NSCellFlags">67108864</int> @@ -987,8 +1017,9 @@ <object class="NSButton" id="1012114470"> <reference key="NSNextResponder" ref="1066741257"/> <int key="NSvFlags">268</int> - <string key="NSFrame">{{144, 192}, {131, 18}}</string> + <string key="NSFrame">{{132, 192}, {143, 18}}</string> <reference key="NSSuperview" ref="1066741257"/> + <reference key="NSNextKeyView" ref="948609625"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="213779970"> <int key="NSCellFlags">67108864</int> @@ -1007,13 +1038,41 @@ </object> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> </object> + <object class="NSButton" id="93457599"> + <reference key="NSNextResponder" ref="1066741257"/> + <int key="NSvFlags">268</int> + <string key="NSFrame">{{132, 172}, {143, 18}}</string> + <reference key="NSSuperview" ref="1066741257"/> + <reference key="NSNextKeyView" ref="514468506"/> + <string key="NSReuseIdentifierKey">_NS:9</string> + <bool key="NSEnabled">YES</bool> + <object class="NSButtonCell" key="NSCell" id="521990639"> + <int key="NSCellFlags">67108864</int> + <int key="NSCellFlags2">0</int> + <string key="NSContents">Create Tablespace</string> + <reference key="NSSupport" ref="746597359"/> + <string key="NSCellIdentifier">_NS:9</string> + <reference key="NSControlView" ref="93457599"/> + <int key="NSButtonFlags">1211912448</int> + <int key="NSButtonFlags2">2</int> + <reference key="NSNormalImage" ref="399252468"/> + <reference key="NSAlternateImage" ref="357101228"/> + <string key="NSAlternateContents"/> + <string key="NSKeyEquivalent"/> + <int key="NSPeriodicDelay">200</int> + <int key="NSPeriodicInterval">25</int> + </object> + <bool key="NSAllowsLogicalLayoutDirection">NO</bool> + </object> </array> <string key="NSFrame">{{1, 1}, {291, 218}}</string> <reference key="NSSuperview" ref="517432726"/> + <reference key="NSNextKeyView" ref="165190944"/> </object> </array> <string key="NSFrame">{{273, 15}, {293, 234}}</string> <reference key="NSSuperview" ref="386290455"/> + <reference key="NSNextKeyView" ref="1066741257"/> <string key="NSOffsets">{0, 0}</string> <object class="NSTextFieldCell" key="NSTitleCell"> <int key="NSCellFlags">67108864</int> @@ -1037,6 +1096,7 @@ <int key="NSvFlags">289</int> <string key="NSFrame">{{65, 44}, {157, 32}}</string> <reference key="NSSuperview" ref="386290455"/> + <reference key="NSNextKeyView" ref="191574098"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="674356083"> <int key="NSCellFlags">67108864</int> @@ -1066,6 +1126,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 192}, {220, 18}}</string> <reference key="NSSuperview" ref="424144569"/> + <reference key="NSNextKeyView" ref="279892883"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="856008013"> <int key="NSCellFlags">67108864</int> @@ -1089,6 +1150,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 172}, {220, 18}}</string> <reference key="NSSuperview" ref="424144569"/> + <reference key="NSNextKeyView" ref="44622938"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="754659519"> <int key="NSCellFlags">67108864</int> @@ -1112,6 +1174,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 152}, {220, 18}}</string> <reference key="NSSuperview" ref="424144569"/> + <reference key="NSNextKeyView" ref="221237545"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="1032540047"> <int key="NSCellFlags">67108864</int> @@ -1135,6 +1198,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 132}, {220, 18}}</string> <reference key="NSSuperview" ref="424144569"/> + <reference key="NSNextKeyView" ref="173464062"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="127371548"> <int key="NSCellFlags">67108864</int> @@ -1158,6 +1222,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 112}, {220, 18}}</string> <reference key="NSSuperview" ref="424144569"/> + <reference key="NSNextKeyView" ref="905629414"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="460476562"> <int key="NSCellFlags">67108864</int> @@ -1181,6 +1246,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 92}, {220, 18}}</string> <reference key="NSSuperview" ref="424144569"/> + <reference key="NSNextKeyView" ref="384517358"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="143275795"> <int key="NSCellFlags">67108864</int> @@ -1204,6 +1270,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 72}, {220, 18}}</string> <reference key="NSSuperview" ref="424144569"/> + <reference key="NSNextKeyView" ref="577327681"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="1016258703"> <int key="NSCellFlags">67108864</int> @@ -1227,6 +1294,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 52}, {220, 18}}</string> <reference key="NSSuperview" ref="424144569"/> + <reference key="NSNextKeyView" ref="658073531"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="921961521"> <int key="NSCellFlags">67108864</int> @@ -1250,6 +1318,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 32}, {220, 18}}</string> <reference key="NSSuperview" ref="424144569"/> + <reference key="NSNextKeyView" ref="458971250"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="489524765"> <int key="NSCellFlags">67108864</int> @@ -1273,6 +1342,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 12}, {220, 18}}</string> <reference key="NSSuperview" ref="424144569"/> + <reference key="NSNextKeyView" ref="871205380"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="282706704"> <int key="NSCellFlags">67108864</int> @@ -1294,10 +1364,12 @@ </array> <string key="NSFrame">{{1, 1}, {252, 218}}</string> <reference key="NSSuperview" ref="1050276179"/> + <reference key="NSNextKeyView" ref="354897609"/> </object> </array> <string key="NSFrame">{{17, 153}, {254, 234}}</string> <reference key="NSSuperview" ref="386290455"/> + <reference key="NSNextKeyView" ref="424144569"/> <string key="NSOffsets">{0, 0}</string> <object class="NSTextFieldCell" key="NSTitleCell"> <int key="NSCellFlags">67108864</int> @@ -1321,6 +1393,7 @@ <int key="NSvFlags">289</int> <string key="NSFrame">{{66, 12}, {157, 32}}</string> <reference key="NSSuperview" ref="386290455"/> + <reference key="NSNextKeyView" ref="517432726"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="513183495"> <int key="NSCellFlags">67108864</int> @@ -1350,6 +1423,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 92}, {259, 18}}</string> <reference key="NSSuperview" ref="851437767"/> + <reference key="NSNextKeyView" ref="83587898"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="189629129"> <int key="NSCellFlags">67108864</int> @@ -1373,6 +1447,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 72}, {259, 18}}</string> <reference key="NSSuperview" ref="851437767"/> + <reference key="NSNextKeyView" ref="969573372"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="649732615"> <int key="NSCellFlags">67108864</int> @@ -1396,6 +1471,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 52}, {259, 18}}</string> <reference key="NSSuperview" ref="851437767"/> + <reference key="NSNextKeyView" ref="905340786"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="637072733"> <int key="NSCellFlags">67108864</int> @@ -1419,6 +1495,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 32}, {259, 18}}</string> <reference key="NSSuperview" ref="851437767"/> + <reference key="NSNextKeyView" ref="757005232"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="1073285410"> <int key="NSCellFlags">67108864</int> @@ -1442,6 +1519,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{16, 12}, {259, 18}}</string> <reference key="NSSuperview" ref="851437767"/> + <reference key="NSNextKeyView" ref="223367051"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="578316308"> <int key="NSCellFlags">67108864</int> @@ -1463,10 +1541,12 @@ </array> <string key="NSFrame">{{1, 1}, {291, 118}}</string> <reference key="NSSuperview" ref="871205380"/> + <reference key="NSNextKeyView" ref="556933759"/> </object> </array> <string key="NSFrame">{{273, 253}, {293, 134}}</string> <reference key="NSSuperview" ref="386290455"/> + <reference key="NSNextKeyView" ref="851437767"/> <string key="NSOffsets">{0, 0}</string> <object class="NSTextFieldCell" key="NSTitleCell"> <int key="NSCellFlags">67108864</int> @@ -1488,10 +1568,12 @@ </array> <string key="NSFrame">{{8, 12}, {574, 399}}</string> <reference key="NSSuperview" ref="202198269"/> + <reference key="NSNextKeyView" ref="1050276179"/> <string key="NSClassName">NSView</string> </object> </array> <string key="NSFrame">{{10, 33}, {591, 414}}</string> + <reference key="NSNextKeyView" ref="386290455"/> </object> <string key="NSLabel">Global Privileges</string> <reference key="NSColor" ref="409859189"/> @@ -1500,7 +1582,7 @@ <object class="NSTabViewItem" id="487249930"> <string key="NSIdentifier">Schema Privileges</string> <object class="NSView" key="NSView" id="601698335"> - <reference key="NSNextResponder" ref="716372522"/> + <nil key="NSNextResponder"/> <int key="NSvFlags">274</int> <array class="NSMutableArray" key="NSSubviews"> <object class="NSScrollView" id="307214944"> @@ -1516,7 +1598,7 @@ <int key="NSvFlags">256</int> <string key="NSFrameSize">{163, 363}</string> <reference key="NSSuperview" ref="380397569"/> - <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="304497597"/> <bool key="NSEnabled">YES</bool> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <bool key="NSControlAllowsExpansionToolTips">YES</bool> @@ -1525,7 +1607,7 @@ <int key="NSvFlags">256</int> <string key="NSFrameSize">{163, 17}</string> <reference key="NSSuperview" ref="999658386"/> - <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="636326150"/> <reference key="NSTableView" ref="280739950"/> </object> <object class="_NSCornerView" key="NSCornerView" id="636326150"> @@ -1533,7 +1615,7 @@ <int key="NSvFlags">-2147483392</int> <string key="NSFrame">{{110, 0}, {12, 17}}</string> <reference key="NSSuperview" ref="307214944"/> - <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="380397569"/> </object> <array class="NSMutableArray" key="NSTableColumns"> <object class="NSTableColumn" id="919609640"> @@ -1584,7 +1666,6 @@ </array> <string key="NSFrame">{{1, 17}, {163, 363}}</string> <reference key="NSSuperview" ref="307214944"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="280739950"/> <reference key="NSDocView" ref="280739950"/> <reference key="NSBGColor" ref="898983655"/> @@ -1598,7 +1679,6 @@ </array> <string key="NSFrame">{{1, 0}, {163, 17}}</string> <reference key="NSSuperview" ref="307214944"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="406943647"/> <reference key="NSDocView" ref="406943647"/> <reference key="NSBGColor" ref="898983655"/> @@ -1609,7 +1689,7 @@ <int key="NSvFlags">-2147483392</int> <string key="NSFrame">{{110, 17}, {11, 352}}</string> <reference key="NSSuperview" ref="307214944"/> - <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="1067950228"/> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <int key="NSsFlags">256</int> <reference key="NSTarget" ref="307214944"/> @@ -1622,7 +1702,7 @@ <int key="NSvFlags">-2147483392</int> <string key="NSFrame">{{1, 369}, {109, 11}}</string> <reference key="NSSuperview" ref="307214944"/> - <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="728294866"/> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <int key="NSsFlags">257</int> <reference key="NSTarget" ref="307214944"/> @@ -1633,8 +1713,7 @@ </array> <string key="NSFrame">{{17, 17}, {165, 381}}</string> <reference key="NSSuperview" ref="601698335"/> - <reference key="NSWindow"/> - <reference key="NSNextKeyView" ref="380397569"/> + <reference key="NSNextKeyView" ref="999658386"/> <int key="NSsFlags">133682</int> <reference key="NSVScroller" ref="304497597"/> <reference key="NSHScroller" ref="1067950228"/> @@ -1659,7 +1738,7 @@ <int key="NSvFlags">256</int> <string key="NSFrameSize">{163, 363}</string> <reference key="NSSuperview" ref="239762482"/> - <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="210192488"/> <bool key="NSEnabled">YES</bool> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <bool key="NSControlAllowsExpansionToolTips">YES</bool> @@ -1668,7 +1747,7 @@ <int key="NSvFlags">256</int> <string key="NSFrameSize">{163, 17}</string> <reference key="NSSuperview" ref="2173"/> - <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="222006363"/> <reference key="NSTableView" ref="64638447"/> </object> <object class="_NSCornerView" key="NSCornerView" id="222006363"> @@ -1676,7 +1755,7 @@ <int key="NSvFlags">-2147483392</int> <string key="NSFrame">{{110, 0}, {12, 17}}</string> <reference key="NSSuperview" ref="728294866"/> - <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="239762482"/> </object> <array class="NSMutableArray" key="NSTableColumns"> <object class="NSTableColumn" id="1048567781"> @@ -1727,7 +1806,6 @@ </array> <string key="NSFrame">{{1, 17}, {163, 363}}</string> <reference key="NSSuperview" ref="728294866"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="64638447"/> <reference key="NSDocView" ref="64638447"/> <reference key="NSBGColor" ref="898983655"/> @@ -1741,7 +1819,6 @@ </array> <string key="NSFrame">{{1, 0}, {163, 17}}</string> <reference key="NSSuperview" ref="728294866"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="599931188"/> <reference key="NSDocView" ref="599931188"/> <reference key="NSBGColor" ref="898983655"/> @@ -1752,7 +1829,7 @@ <int key="NSvFlags">-2147483392</int> <string key="NSFrame">{{110, 17}, {11, 352}}</string> <reference key="NSSuperview" ref="728294866"/> - <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="253008686"/> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <int key="NSsFlags">256</int> <reference key="NSTarget" ref="728294866"/> @@ -1764,7 +1841,7 @@ <int key="NSvFlags">-2147483392</int> <string key="NSFrame">{{1, 369}, {109, 11}}</string> <reference key="NSSuperview" ref="728294866"/> - <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="763888467"/> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <int key="NSsFlags">257</int> <reference key="NSTarget" ref="728294866"/> @@ -1775,8 +1852,7 @@ </array> <string key="NSFrame">{{190, 17}, {165, 381}}</string> <reference key="NSSuperview" ref="601698335"/> - <reference key="NSWindow"/> - <reference key="NSNextKeyView" ref="239762482"/> + <reference key="NSNextKeyView" ref="2173"/> <int key="NSsFlags">133682</int> <reference key="NSVScroller" ref="210192488"/> <reference key="NSHScroller" ref="253008686"/> @@ -1801,7 +1877,7 @@ <int key="NSvFlags">256</int> <string key="NSFrameSize">{163, 363}</string> <reference key="NSSuperview" ref="728383964"/> - <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="939829514"/> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <bool key="NSControlAllowsExpansionToolTips">YES</bool> <object class="NSTableHeaderView" key="NSHeaderView" id="120612996"> @@ -1809,7 +1885,7 @@ <int key="NSvFlags">256</int> <string key="NSFrameSize">{163, 17}</string> <reference key="NSSuperview" ref="226879331"/> - <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="41340999"/> <reference key="NSTableView" ref="443904239"/> </object> <object class="_NSCornerView" key="NSCornerView" id="41340999"> @@ -1817,7 +1893,7 @@ <int key="NSvFlags">-2147483392</int> <string key="NSFrame">{{110, 0}, {12, 17}}</string> <reference key="NSSuperview" ref="898351365"/> - <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="728383964"/> </object> <array class="NSMutableArray" key="NSTableColumns"> <object class="NSTableColumn" id="408402649"> @@ -1868,7 +1944,6 @@ </array> <string key="NSFrame">{{1, 17}, {163, 363}}</string> <reference key="NSSuperview" ref="898351365"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="443904239"/> <reference key="NSDocView" ref="443904239"/> <reference key="NSBGColor" ref="898983655"/> @@ -1882,7 +1957,6 @@ </array> <string key="NSFrame">{{1, 0}, {163, 17}}</string> <reference key="NSSuperview" ref="898351365"/> - <reference key="NSWindow"/> <reference key="NSNextKeyView" ref="120612996"/> <reference key="NSDocView" ref="120612996"/> <reference key="NSBGColor" ref="898983655"/> @@ -1893,7 +1967,7 @@ <int key="NSvFlags">-2147483392</int> <string key="NSFrame">{{110, 17}, {11, 352}}</string> <reference key="NSSuperview" ref="898351365"/> - <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="1063461866"/> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <int key="NSsFlags">256</int> <reference key="NSTarget" ref="898351365"/> @@ -1905,7 +1979,7 @@ <int key="NSvFlags">-2147483392</int> <string key="NSFrame">{{1, 369}, {109, 11}}</string> <reference key="NSSuperview" ref="898351365"/> - <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="716372522"/> <bool key="NSAllowsLogicalLayoutDirection">NO</bool> <int key="NSsFlags">257</int> <reference key="NSTarget" ref="898351365"/> @@ -1916,8 +1990,7 @@ </array> <string key="NSFrame">{{409, 17}, {165, 381}}</string> <reference key="NSSuperview" ref="601698335"/> - <reference key="NSWindow"/> - <reference key="NSNextKeyView" ref="728383964"/> + <reference key="NSNextKeyView" ref="226879331"/> <int key="NSsFlags">133682</int> <reference key="NSVScroller" ref="939829514"/> <reference key="NSHScroller" ref="1063461866"/> @@ -1934,7 +2007,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{363, 206}, {31, 32}}</string> <reference key="NSSuperview" ref="601698335"/> - <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="832112691"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="402609054"> <int key="NSCellFlags">603979776</int> @@ -1960,7 +2033,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{363, 177}, {31, 32}}</string> <reference key="NSSuperview" ref="601698335"/> - <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="898351365"/> <bool key="NSEnabled">YES</bool> <object class="NSButtonCell" key="NSCell" id="847011406"> <int key="NSCellFlags">603979776</int> @@ -1983,8 +2056,7 @@ </object> </array> <string key="NSFrame">{{10, 33}, {591, 414}}</string> - <reference key="NSSuperview" ref="716372522"/> - <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="307214944"/> </object> <string key="NSLabel">Schema Privileges</string> <reference key="NSColor" ref="409859189"/> @@ -1994,7 +2066,7 @@ <string key="NSIdentifier">Resources</string> <object class="NSView" key="NSView" id="169019919"> <nil key="NSNextResponder"/> - <int key="NSvFlags">256</int> + <int key="NSvFlags">274</int> <array class="NSMutableArray" key="NSSubviews"> <object class="NSBox" id="991025453"> <reference key="NSNextResponder" ref="169019919"/> @@ -2009,6 +2081,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{15, 84}, {164, 17}}</string> <reference key="NSSuperview" ref="289920921"/> + <reference key="NSNextKeyView" ref="65963335"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="337800886"> <int key="NSCellFlags">68157504</int> @@ -2027,6 +2100,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{15, 49}, {164, 17}}</string> <reference key="NSSuperview" ref="289920921"/> + <reference key="NSNextKeyView" ref="111842321"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="677274224"> <int key="NSCellFlags">68157504</int> @@ -2045,6 +2119,7 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{15, 17}, {164, 17}}</string> <reference key="NSSuperview" ref="289920921"/> + <reference key="NSNextKeyView" ref="165705493"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="829047444"> <int key="NSCellFlags">68157504</int> @@ -2063,10 +2138,11 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{184, 81}, {149, 22}}</string> <reference key="NSSuperview" ref="289920921"/> + <reference key="NSNextKeyView" ref="23673932"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="228458735"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <reference key="NSSupport" ref="746597359"/> <object class="NSNumberFormatter" key="NSFormatter" id="268664286"> <dictionary class="NSMutableDictionary" key="NS.attributes"> @@ -2128,10 +2204,11 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{184, 46}, {149, 22}}</string> <reference key="NSSuperview" ref="289920921"/> + <reference key="NSNextKeyView" ref="520264508"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="950352714"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <reference key="NSSupport" ref="746597359"/> <object class="NSNumberFormatter" key="NSFormatter" id="867019301"> <dictionary class="NSMutableDictionary" key="NS.attributes"> @@ -2184,10 +2261,11 @@ <int key="NSvFlags">268</int> <string key="NSFrame">{{184, 14}, {149, 22}}</string> <reference key="NSSuperview" ref="289920921"/> + <reference key="NSNextKeyView" ref="716372522"/> <bool key="NSEnabled">YES</bool> <object class="NSTextFieldCell" key="NSCell" id="473921129"> <int key="NSCellFlags">-1804599231</int> - <int key="NSCellFlags2">272630784</int> + <int key="NSCellFlags2">272630848</int> <reference key="NSSupport" ref="746597359"/> <object class="NSNumberFormatter" key="NSFormatter" id="765293659"> <dictionary class="NSMutableDictionary" key="NS.attributes"> @@ -2238,10 +2316,12 @@ </array> <string key="NSFrame">{{1, 1}, {351, 113}}</string> <reference key="NSSuperview" ref="991025453"/> + <reference key="NSNextKeyView" ref="1055078320"/> </object> </array> <string key="NSFrame">{{119, 140}, {353, 129}}</string> <reference key="NSSuperview" ref="169019919"/> + <reference key="NSNextKeyView" ref="289920921"/> <string key="NSOffsets">{0, 0}</string> <object class="NSTextFieldCell" key="NSTitleCell"> <int key="NSCellFlags">67108864</int> @@ -2262,18 +2342,19 @@ </object> </array> <string key="NSFrame">{{10, 33}, {591, 414}}</string> + <reference key="NSNextKeyView" ref="991025453"/> </object> <string key="NSLabel">Resources</string> <reference key="NSColor" ref="409859189"/> <reference key="NSTabView" ref="716372522"/> </object> </array> - <reference key="NSSelectedTabViewItem" ref="487249930"/> + <reference key="NSSelectedTabViewItem" ref="820796939"/> <reference key="NSFont" ref="746597359"/> <int key="NSTvFlags">0</int> <bool key="NSDrawsBackground">YES</bool> <array class="NSMutableArray" key="NSSubviews"> - <reference ref="601698335"/> + <reference ref="143215913"/> </array> </object> <object class="NSButton" id="445730006"> @@ -4397,6 +4478,42 @@ IGVycm9ycyBiZWxvdyBiZWZvcmUgcHJvY2VlZGluZy4</string> </object> <string key="id">1024</string> </object> + <object class="IBConnectionRecord"> + <object class="IBBindingConnection" key="connection"> + <string key="label">enabled: create_tablespace_priv</string> + <reference key="source" ref="93457599"/> + <reference key="destination" ref="674959820"/> + <object class="NSNibBindingConnector" key="connector"> + <reference key="NSSource" ref="93457599"/> + <reference key="NSDestination" ref="674959820"/> + <string key="NSLabel">enabled: create_tablespace_priv</string> + <string key="NSBinding">enabled</string> + <string key="NSKeyPath">create_tablespace_priv</string> + <object class="NSDictionary" key="NSOptions"> + <string key="NS.key.0">NSValueTransformerName</string> + <string key="NS.object.0">NSIsNotNil</string> + </object> + <int key="NSNibBindingConnectorVersion">2</int> + </object> + </object> + <string key="id">Pyl-hz-npp</string> + </object> + <object class="IBConnectionRecord"> + <object class="IBBindingConnection" key="connection"> + <string key="label">value: selection.create_tablespace_priv</string> + <reference key="source" ref="93457599"/> + <reference key="destination" ref="889422316"/> + <object class="NSNibBindingConnector" key="connector"> + <reference key="NSSource" ref="93457599"/> + <reference key="NSDestination" ref="889422316"/> + <string key="NSLabel">value: selection.create_tablespace_priv</string> + <string key="NSBinding">value</string> + <string key="NSKeyPath">selection.create_tablespace_priv</string> + <int key="NSNibBindingConnectorVersion">2</int> + </object> + </object> + <string key="id">6GM-JO-bwm</string> + </object> </array> <object class="IBMutableOrderedSet" key="objectRecords"> <array key="orderedObjects"> @@ -4905,6 +5022,7 @@ IGVycm9ycyBiZWxvdyBiZWZvcmUgcHJvY2VlZGluZy4</string> <reference ref="373270478"/> <reference ref="285378693"/> <reference ref="1012114470"/> + <reference ref="93457599"/> </array> <reference key="parent" ref="386290455"/> </object> @@ -5659,6 +5777,19 @@ IGVycm9ycyBiZWxvdyBiZWZvcmUgcHJvY2VlZGluZy4</string> <reference key="object" ref="234756526"/> <reference key="parent" ref="798811064"/> </object> + <object class="IBObjectRecord"> + <string key="id">ecH-YW-CVE</string> + <reference key="object" ref="93457599"/> + <array class="NSMutableArray" key="children"> + <reference ref="521990639"/> + </array> + <reference key="parent" ref="517432726"/> + </object> + <object class="IBObjectRecord"> + <string key="id">ftz-WX-x7r</string> + <reference key="object" ref="521990639"/> + <reference key="parent" ref="93457599"/> + </object> </array> </object> <dictionary class="NSMutableDictionary" key="flattenedProperties"> @@ -5877,6 +6008,8 @@ IGVycm9ycyBiZWxvdyBiZWZvcmUgcHJvY2VlZGluZy4</string> <string key="990.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <string key="995.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> <string key="996.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="ecH-YW-CVE.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="ftz-WX-x7r.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> </dictionary> <dictionary class="NSMutableDictionary" key="unlocalizedProperties"/> <nil key="activeLocalization"/> @@ -1,4 +1,4 @@ -Copyright (c) 2002-2015 Sequel Pro & CocoaMySQL Teams. +Copyright (c) 2002-2016 Sequel Pro & CocoaMySQL Teams. All rights reserved. diff --git a/Models/SPUserManager.xcdatamodel/contents b/Models/SPUserManager.xcdatamodel/contents new file mode 100644 index 00000000..20b8ce73 --- /dev/null +++ b/Models/SPUserManager.xcdatamodel/contents @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6751" systemVersion="13F1507" minimumToolsVersion="Xcode 4.3" macOSVersion="Automatic" iOSVersion="Automatic"> + <entity name="Privileges" representedClassName="SPPrivilegesMO" syncable="YES"> + <attribute name="alter_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="alter_routine_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="create_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="create_routine_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="create_temporary_tables_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="create_view_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="db" attributeType="String" maxValueString="64" indexed="YES" syncable="YES"/> + <attribute name="delete_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="drop_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="event_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="execute_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="grant_option_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="index_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="insert_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="lock_tables_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="references_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="select_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="show_view_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="trigger_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="update_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="userManager" optional="YES" transient="YES" syncable="YES"/> + <relationship name="user" optional="YES" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="SPUser" inverseName="schema_privileges" inverseEntity="SPUser" indexed="YES" syncable="YES"/> + </entity> + <entity name="SPUser" representedClassName="SPUserMO" syncable="YES"> + <attribute name="alter_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="alter_routine_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="authentication_string" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="create_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="create_routine_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="create_tablespace_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="create_temporary_tables_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="create_user_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="create_view_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="delete_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="drop_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="event_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="execute_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="file_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="grant_option_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="host" optional="YES" attributeType="String" maxValueString="60" defaultValueString="%" syncable="YES"/> + <attribute name="index_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="insert_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="lock_tables_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="max_connections" optional="YES" attributeType="Integer 32" minValueString="0" maxValueString="99999999999" defaultValueString="0" syncable="YES"/> + <attribute name="max_questions" optional="YES" attributeType="Integer 32" minValueString="0" maxValueString="99999999999" defaultValueString="0" syncable="YES"/> + <attribute name="max_updates" optional="YES" attributeType="Integer 32" minValueString="0" maxValueString="99999999999" defaultValueString="0" syncable="YES"/> + <attribute name="originalhost" optional="YES" attributeType="String" maxValueString="60" defaultValueString="%" syncable="YES"/> + <attribute name="originalpassword" optional="YES" attributeType="String"> + <userInfo/> + </attribute> + <attribute name="originaluser" optional="YES" attributeType="String"> + <userInfo/> + </attribute> + <attribute name="password" optional="YES" attributeType="String" maxValueString="41" syncable="YES"/> + <attribute name="plugin" optional="YES" attributeType="String" maxValueString="64" syncable="YES"/> + <attribute name="process_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="references_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="reload_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="replication_client_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="replication_slave_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="select_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="show_databases_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="show_view_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="shutdown_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="super_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="trigger_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="update_priv" optional="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> + <attribute name="user" optional="YES" attributeType="String" syncable="YES"/> + <attribute name="userManager" optional="YES" transient="YES" syncable="YES"/> + <relationship name="children" optional="YES" toMany="YES" minCount="1" deletionRule="Cascade" destinationEntity="SPUser" inverseName="parent" inverseEntity="SPUser" indexed="YES" syncable="YES"/> + <relationship name="parent" optional="YES" minCount="1" maxCount="1" deletionRule="Cascade" destinationEntity="SPUser" inverseName="children" inverseEntity="SPUser" indexed="YES" syncable="YES"/> + <relationship name="schema_privileges" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Privileges" inverseName="user" inverseEntity="Privileges" indexed="YES" syncable="YES"/> + </entity> + <elements> + <element name="Privileges" positionX="412" positionY="90" width="128" height="373"/> + <element name="SPUser" positionX="135" positionY="45" width="207" height="705"/> + </elements> +</model>
\ No newline at end of file diff --git a/Models/SPUserManager.xcdatamodel/elements b/Models/SPUserManager.xcdatamodel/elements Binary files differdeleted file mode 100644 index a4d11321..00000000 --- a/Models/SPUserManager.xcdatamodel/elements +++ /dev/null diff --git a/Models/SPUserManager.xcdatamodel/layout b/Models/SPUserManager.xcdatamodel/layout Binary files differdeleted file mode 100644 index 62662ba9..00000000 --- a/Models/SPUserManager.xcdatamodel/layout +++ /dev/null diff --git a/Resources/English.lproj/Credits.rtf b/Resources/English.lproj/Credits.rtf index da243416..41c17245 100644 --- a/Resources/English.lproj/Credits.rtf +++ b/Resources/English.lproj/Credits.rtf @@ -228,6 +228,6 @@ All rights reserved.\ \b0\fs22 \cf0 \ \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\qc\pardirnatural -\cf0 Icon by {\field{\*\fldinst{HYPERLINK "http://kenichiyoshida.jp/"}}{\fldrslt Kenichi Yoshida}}, with thanks to {\field{\*\fldinst{HYPERLINK "http://panic.com/"}}{\fldrslt Panic}}\ +\cf0 Icon by {\field{\*\fldinst{HYPERLINK "http://www.kenichi27.com/"}}{\fldrslt Kenichi Yoshida}}, with thanks to {\field{\*\fldinst{HYPERLINK "http://panic.com/"}}{\fldrslt Panic}}\ GUI design by {\field{\*\fldinst{HYPERLINK "http://www.sequelpro.com/"}}{\fldrslt Sequel Pro}} team.\ } diff --git a/Resources/English.lproj/GotoDatabaseDialog.strings b/Resources/English.lproj/GotoDatabaseDialog.strings Binary files differindex bf48b104..6512a3de 100644 --- a/Resources/English.lproj/GotoDatabaseDialog.strings +++ b/Resources/English.lproj/GotoDatabaseDialog.strings diff --git a/Resources/English.lproj/InfoPlist.strings b/Resources/English.lproj/InfoPlist.strings index af866cf8..64644f83 100644 --- a/Resources/English.lproj/InfoPlist.strings +++ b/Resources/English.lproj/InfoPlist.strings @@ -1,4 +1,4 @@ /* Localized versions of Info.plist keys */ -CFBundleGetInfoString = "Sequel Pro version 1.0.2, Copyright 2002-2013 Sequel Pro and CocoaMySQL team."; -NSHumanReadableCopyright = "Copyright 2002-2013 Sequel Pro and CocoaMySQL team.";
\ No newline at end of file +CFBundleGetInfoString = "Sequel Pro version 1.1, Copyright 2002-2016 Sequel Pro and CocoaMySQL team."; +NSHumanReadableCopyright = "Copyright 2002-2016 Sequel Pro and CocoaMySQL team.";
\ No newline at end of file diff --git a/Resources/English.lproj/Localizable.strings b/Resources/English.lproj/Localizable.strings Binary files differindex a571d298..bdeee714 100644 --- a/Resources/English.lproj/Localizable.strings +++ b/Resources/English.lproj/Localizable.strings diff --git a/Resources/English.lproj/MainMenu.strings b/Resources/English.lproj/MainMenu.strings Binary files differindex b5593a7d..b6407843 100644 --- a/Resources/English.lproj/MainMenu.strings +++ b/Resources/English.lproj/MainMenu.strings 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 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <style> + body { + font-family:'Lucida Grande'; + font-size:10pt; + } + .token { + background: linear-gradient(to top, rgb(199,216,244) 0%,rgb(217,229,247) 100%); + border: 1px solid rgb(168,184,249); + padding: 0 5px; + margin: 2px; + border-radius: 10px; + } + </style> + </head> + <body> + <center> + <table> + <tr> + <td colspan="2"> + <center> + <b style="font-size:12pt">Sequel Pro Saved Export Settings</b><br /> + {{ filename }} + </center><br /><br /> + </td> + </tr> + <tr> + <td valign="right"> + <img width="128" heigth="128" src="cid:icon.tiff"> + </td> + <td> + <table> + <tr> + <td align="right">Path: </td><td>{{ exportPath }}</td> + </tr> + {% if customFilename %} + <tr> + <td align="right">Filename: </td> + <td><!-- Those comments are only used to eliminate visible whitespace + {% /if %} + {% for part in customFilename %} + {% if part.isToken %} + --><span class="token">{{ part.name }}</span><!-- + {% else %} + -->{{ part.name }}<!-- + {% /if %} + {% /for %} + {% comment This is most likely a bug in our version of MGTemplateEngine, but putting the for loop inside the if will create broken output %} + {% if customFilename %} + --></td> + </tr> + {% /if %} + <tr> + <td align="right">Format: </td><td><b>{{ exportType }}</b></td> + </tr> + <tr> + <td align="right">Compression: </td><td>{{ compressionFormat }}</td> + </tr> + <tr> + <td align="right">Input data: </td><td>{{ exportSource }}</td> + </tr> + <tr> + <td align="right">Low Memory: </td><td><input type="checkbox" {% if lowMemoryStreaming %}checked{% /if %} disabled></td> + </tr> + <tr> + <td colspan="2" style="color: grey;"><br />(format specific settings not shown)</td> + </tr> + </table> + </td> + </tr> + </table> + </center> + </body> +</html> diff --git a/Resources/License.rtf b/Resources/License.rtf index a9ce83c0..5f5e1c22 100644 --- a/Resources/License.rtf +++ b/Resources/License.rtf @@ -5,7 +5,7 @@ \deftab560 \pard\tx560\pardeftab560\pardirnatural -\f0\b\fs22 \cf2 \CocoaLigature0 Copyright (c) 2002-2015 Sequel Pro & CocoaMySQL Teams. \ +\f0\b\fs22 \cf2 \CocoaLigature0 Copyright (c) 2002-2016 Sequel Pro & CocoaMySQL Teams. \ \ All rights reserved. \b0 \ @@ -34,4 +34,4 @@ CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN \ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER \ DEALINGS IN THE SOFTWARE. \b0 \cf0 \CocoaLigature1 \ -}
\ No newline at end of file +} diff --git a/Resources/Plists/CompletionTokens.plist b/Resources/Plists/CompletionTokens.plist index f089401d..fc10db70 100644 --- a/Resources/Plists/CompletionTokens.plist +++ b/Resources/Plists/CompletionTokens.plist @@ -289,6 +289,7 @@ <string>ISSUER</string> <string>ITERATE</string> <string>JOIN</string> + <string>JSON</string> <string>KEY</string> <string>KEYS</string> <string>KEY_BLOCK_SIZE</string> @@ -834,6 +835,27 @@ <string>ISSIMPLE</string> <string>IS_FREE_LOCK</string> <string>IS_USED_LOCK</string> + <string>JSON_APPEND</string> + <string>JSON_ARRAY_APPEND</string> + <string>JSON_ARRAY_INSERT</string> + <string>JSON_ARRAY</string> + <string>JSON_CONTAINS_PATH</string> + <string>JSON_CONTAINS</string> + <string>JSON_DEPTH</string> + <string>JSON_EXTRACT</string> + <string>JSON_INSERT</string> + <string>JSON_KEYS</string> + <string>JSON_LENGTH</string> + <string>JSON_MERGE</string> + <string>JSON_OBJECT</string> + <string>JSON_QUOTE</string> + <string>JSON_REMOVE</string> + <string>JSON_REPLACE</string> + <string>JSON_SEARCH</string> + <string>JSON_SET</string> + <string>JSON_TYPE</string> + <string>JSON_UNQUOTE</string> + <string>JSON_VALID</string> <string>LAST_DAY</string> <string>LAST_INSERT_ID</string> <string>LCASE</string> @@ -1162,6 +1184,48 @@ <string>${1:bits}, ${2:str1}</string> <key>FIND_IN_SET</key> <string>${1:str}, ${2:strlist}</string> + <key>JSON_APPEND</key> + <string>${1:json_doc}, ${2:path}, ${3:val}${4:, ${5:path}, ${6:val}${7:, ${8:...}}}</string> + <key>JSON_ARRAY_APPEND</key> + <string>${1:json_doc}, ${2:path}, ${3:val}${4:, ${5:path}, ${6:val}${7:, ${8:...}}}</string> + <key>JSON_ARRAY_INSERT</key> + <string>${1:json_doc}, ${2:path}, ${3:val}${4:, ${5:path}, ${6:val}${7:, ${8:...}}}</string> + <key>JSON_ARRAY</key> + <string>${1:${2:val} ${3:, ${4:val}${5:, ${6:...}}}}</string> + <key>JSON_CONTAINS_PATH</key> + <string>${1:json_doc}, ${2:¦'one'¦'all'¦}, ${3:path}${4:, ${5:path}${6:, ${7:...}}}</string> + <key>JSON_CONTAINS</key> + <string>${1:json_doc}, ${2:val}${3:, ${4:path}}</string> + <key>JSON_DEPTH</key> + <string>${1:json_doc}</string> + <key>JSON_EXTRACT</key> + <string>${1:json_doc}, ${2:path}${3:, ${4:path}${5:, ${6:...}}}</string> + <key>JSON_INSERT</key> + <string>${1:json_doc}, ${2:path}, ${3:val}${4:, ${5:path}, ${6:val}${7:, ${8:...}}}</string> + <key>JSON_KEYS</key> + <string>${1:json_doc}${2:, ${3:path}}</string> + <key>JSON_LENGTH</key> + <string>${1:json_doc}${2:, ${3:path}}</string> + <key>JSON_MERGE</key> + <string>${1:json_doc}, ${2:json_doc}${3:, ${4:...}}</string> + <key>JSON_OBJECT</key> + <string>${1:${2:key}, ${3:val}${4:, ${5:key}, ${6:val}${7:, ${8:...}}}}</string> + <key>JSON_QUOTE</key> + <string>${1:json_val}</string> + <key>JSON_REMOVE</key> + <string>${1:json_doc}, ${2:path}${3:, ${4:path}${5:, ${6:...}}}</string> + <key>JSON_REPLACE</key> + <string>${1:json_doc}, ${2:path}, ${3:val}${4:, ${5:path}, ${6:val}${7:, ${8:...}}}</string> + <key>JSON_SEARCH</key> + <string>${1:json_doc}, ${2:¦'one'¦'all'¦}, ${3:search_str}${4:, ${5:escape_char}${6:, ${7:path${8:, ${9:...}}}}}</string> + <key>JSON_SET</key> + <string>${1:json_doc}, ${2:path}, ${3:val}${4:, ${5:path}, ${6:val}${7:, ${8:...}}}</string> + <key>JSON_TYPE</key> + <string>${1:json_val}</string> + <key>JSON_UNQUOTE</key> + <string>${1:val}</string> + <key>JSON_VALID</key> + <string>${1:val}</string> </dict> </dict> </plist> diff --git a/Resources/Plists/Info.plist b/Resources/Plists/Info.plist index 2ba52aa3..278ded51 100644 --- a/Resources/Plists/Info.plist +++ b/Resources/Plists/Info.plist @@ -173,6 +173,14 @@ <string>sequelpro</string> </array> </dict> + <dict> + <key>CFBundleURLName</key> + <string>MySQL URL scheme</string> + <key>CFBundleURLSchemes</key> + <array> + <string>mysql</string> + </array> + </dict> </array> <key>CFBundleVersion</key> <string></string> @@ -194,7 +202,7 @@ <key>NSAppleScriptEnabled</key> <true/> <key>NSHumanReadableCopyright</key> - <string>Copyright 2002-2015 Sequel Pro and CocoaMySQL team.</string> + <string>Copyright 2002-2016 Sequel Pro and CocoaMySQL team.</string> <key>NSMainNibFile</key> <string>MainMenu</string> <key>NSPrincipalClass</key> @@ -232,7 +240,7 @@ <key>SUEnableSystemProfiling</key> <true/> <key>SUFeedURL</key> - <string>http://www.sequelpro.com/appcast/app-releases.xml</string> + <string>https://www.sequelpro.com/appcast/app-releases.xml</string> <key>SUPublicDSAKeyFile</key> <string>sparkle-public-key.pem</string> <key>UTExportedTypeDeclarations</key> 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 @@ <real>3</real> <key>NewFieldsAllowNulls</key> <true/> - <key>NoBOMforSQLdumpFile</key> - <true/> <key>NullValue</key> <string>NULL</string> <key>PrintBackground</key> @@ -189,8 +187,6 @@ <true/> <key>SPFirstRun</key> <true/> - <key>SQLExportUseCompression</key> - <false/> <key>SSHMultiplexingEnabled</key> <false/> <key>TableInformationPanelCollapsed</key> diff --git a/Scripts/package-application.sh b/Scripts/package-application.sh index 002d51ce..86b8082d 100755 --- a/Scripts/package-application.sh +++ b/Scripts/package-application.sh @@ -67,6 +67,12 @@ mkdir "${DISTTEMP}" # Copy in the required distribution files cp -R "${BUILT_PRODUCTS_DIR}/${TARGET_NAME}${WRAPPER_SUFFIX}" "${DMG_BUILD_PATH}/disttemp" +# Add a link to the Applications dir +echo "Add link to /Applications" +pushd "${DMG_BUILD_PATH}/disttemp" +ln -s /Applications +popd + # Create a disk image hdiutil create -srcfolder "${DISTTEMP}" -volname "$DMG_VOLUME_NAME" -fs HFS+ -fsargs '-c c=64,a=16,e=16' -format UDRW "${DMG_BUILD_PATH}/${DMG_NAME}.temp.dmg" > /dev/null diff --git a/Source/GeneratePreviewForURL.m b/Source/GeneratePreviewForURL.m index ef20b852..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:@"format"] 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:@"format"] 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:@"format"] 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:@"<table width='100%' border=1 style='border-collapse:collapse;border:2px solid lightgrey'>"]; - - NSArray *theTabs = [window objectForKey:@"tabs"]; - for(NSDictionary *tab in theTabs) { - - connectionCounter++; - - if(tabCounter == selectedTab) - [spfsHTML appendString:@"<tr><td style='background-color:#EEEEEE'>"]; - else - [spfsHTML appendString:@"<tr><td>"]; - - 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:@"<table width='100%' border=1 style='border-collapse:collapse;border:2px solid lightgrey'>"]; + + NSArray *theTabs = [window objectForKey:@"tabs"]; + for(NSDictionary *tab in theTabs) { + + connectionCounter++; + + if(tabCounter == selectedTab) + [spfsHTML appendString:@"<tr><td style='background-color:#EEEEEE'>"]; + else + [spfsHTML appendString:@"<tr><td>"]; + + 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:@"</td></tr>"]; - } - - [spfsHTML appendString:@"</table><br />"]; - + + tabCounter++; + + [spfsHTML appendString:@"</td></tr>"]; + } - - if(connectionCounter > 1) - previewHeight = 495; - - html = [NSString stringWithFormat:template, + + [spfsHTML appendString:@"</table><br />"]; + + } + + 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:@"<font color=%@>%@</font>", 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:@"<font color=%@>%@</font>", 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:@"<br>" 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:@"<br>" 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/SPAlertSheets.h b/Source/SPAlertSheets.h index f61d0e38..c92b635d 100644 --- a/Source/SPAlertSheets.h +++ b/Source/SPAlertSheets.h @@ -59,7 +59,14 @@ void SPBeginAlertSheet( void SPOnewayAlertSheet( NSString *title, - NSString *defaultButton, NSWindow *docWindow, NSString *msg ); + +void SPOnewayAlertSheetWithStyle( + NSString *title, + NSString *defaultButton, + NSWindow *docWindow, + NSString *msg, + NSAlertStyle alertStyle +); diff --git a/Source/SPAlertSheets.m b/Source/SPAlertSheets.m index 85afbdfb..258aac6d 100644 --- a/Source/SPAlertSheets.m +++ b/Source/SPAlertSheets.m @@ -144,17 +144,30 @@ @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. * Because of that there is no way to set a delegate and callback method * and there is only one default button. * 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, - NSString *msg) + NSString *msg, + NSAlertStyle alertStyle) { NSString *defaultText = (defaultButton)? defaultButton : NSLocalizedString(@"OK", @"OK button"); @@ -168,12 +181,17 @@ void SPOnewayAlertSheet( // Set the informative message if supplied if (msg) [alert setInformativeText:msg]; + + // Set style (Defaults to NSWarningAlertStyle) + [alert setAlertStyle:alertStyle]; // Run the alert - [alert beginSheetModalForWindow:docWindow modalDelegate:nil didEndSelector:NULL contextInfo:NULL]; - - // Ensure the alerting window is frontmost - [docWindow makeKeyWindow]; + if (docWindow) { + [alert beginSheetModalForWindow:docWindow modalDelegate:nil didEndSelector:NULL contextInfo:NULL]; + [docWindow makeKeyWindow]; // Ensure the alerting window is frontmost + } else { + [alert runModal]; + } }); } 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 <FRFeedbackReporterDelegate, NSApplicationDelegate, NSOpenSavePanelDelegate, NSFileManagerDelegate> { - IBOutlet NSWindow* bundleEditorWindow; - - BOOL isNewFavorite; - SPAboutController *aboutController; SPPreferenceController *prefsController; SPBundleEditorController *bundleEditorController; diff --git a/Source/SPAppController.m b/Source/SPAppController.m index 9ff2cad0..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 @@ -131,7 +137,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; } /** @@ -165,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]; } } } @@ -181,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]; } } } @@ -328,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]; } } @@ -765,15 +753,56 @@ { 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 + SPDatabaseDocument *doc = [self makeNewConnectionTabOrWindow]; + + 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]; @@ -809,8 +838,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; @@ -855,13 +883,16 @@ 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; } - NSString *activeProcessID = [[(SPWindowController *)[[self frontDocumentWindow] delegate] selectedTableDocument] processID]; + NSString *activeProcessID = [[self frontDocument] processID]; SPDatabaseDocument *processDocument = nil; @@ -869,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"]) { @@ -897,8 +926,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"; @@ -938,9 +970,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]; @@ -951,8 +985,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) @@ -1099,8 +1136,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; } @@ -1193,8 +1233,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] + ); } } @@ -1216,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]; @@ -1383,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]; } /** @@ -1966,7 +1999,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; @@ -2018,7 +2051,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 { @@ -2037,7 +2070,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 { @@ -2136,6 +2169,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]; @@ -2164,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/SPBundleCommandTextView.m b/Source/SPBundleCommandTextView.m index f8d4e53f..4dfe4002 100644 --- a/Source/SPBundleCommandTextView.m +++ b/Source/SPBundleCommandTextView.m @@ -816,6 +816,13 @@ [commandScrollView display]; } +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + if([keyPath isEqualToString:SPCustomQueryEditorTabStopWidth]) { + [self setTabStops]; + } +} + #pragma mark - // Store the font in the prefs for selected delegates only diff --git a/Source/SPBundleEditorController.m b/Source/SPBundleEditorController.m index f38ffb28..9986fa98 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 != NSFileHandlingPanelOKButton) 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); + } } @@ -1325,7 +1334,7 @@ static NSString *SPSaveBundleAction = @"SPSaveBundle"; //abort editing [control abortEditing]; - [[NSApp mainWindow] makeFirstResponder:commandsOutlineView]; + [[commandsOutlineView window] makeFirstResponder:commandsOutlineView]; return YES; } else{ return NO; @@ -1443,7 +1452,7 @@ static NSString *SPSaveBundleAction = @"SPSaveBundle"; (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/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/SPCSVExporter.m b/Source/SPCSVExporter.m index 5fb6f664..6657aa8f 100644 --- a/Source/SPCSVExporter.m +++ b/Source/SPCSVExporter.m @@ -68,13 +68,10 @@ } /** - * Start the CSV export process. This method is automatically called when an instance of this class - * is placed on an NSOperationQueue. Do not call it directly as there is no manual multithreading. + * Start the CSV export process. */ -- (void)main -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - +- (void)exportOperation +{ NSMutableString *csvString = [NSMutableString string]; NSMutableString *csvCellString = [NSMutableString string]; @@ -96,7 +93,6 @@ if ((![self csvTableName] && ![self csvDataArray]) || ([[self csvTableName] isEqualToString:@""] && [[self csvDataArray] count] == 0)) { - [pool release]; return; } @@ -105,7 +101,6 @@ (![self csvEscapeString]) || (![self csvLineEndingString])) { - [pool release]; return; } @@ -114,7 +109,6 @@ ([[self csvEscapeString] isEqualToString:@""]) || ([[self csvLineEndingString] isEqualToString:@""])) { - [pool release]; return; } @@ -233,8 +227,7 @@ } [csvExportPool release]; - [pool release]; - + return; } @@ -266,8 +259,7 @@ // Check for cancellation flag if ([self isCancelled]) { [csvExportPool release]; - [pool release]; - + return; } @@ -371,7 +363,7 @@ currentPoolDataLength += [csvString length]; // Write it to the fileHandle - [[self exportOutputFile] writeData:[csvString dataUsingEncoding:[self exportOutputEncoding]]]; + [self writeString:csvString]; currentRowIndex++; @@ -408,7 +400,6 @@ [delegate performSelectorOnMainThread:@selector(csvExportProcessComplete:) withObject:self waitUntilDone:NO]; [csvExportPool release]; - [pool release]; } #pragma mark - diff --git a/Source/SPCSVExporterDelegate.m b/Source/SPCSVExporterDelegate.m index e51fe978..38176776 100644 --- a/Source/SPCSVExporterDelegate.m +++ b/Source/SPCSVExporterDelegate.m @@ -30,31 +30,31 @@ #import "SPCSVExporter.h" #import "SPCSVExporterDelegate.h" -#import "SPDatabaseDocument.h" #import "SPExportFile.h" +#import "SPExportInitializer.h" @implementation SPExportController (SPCSVExporterDelegate) - (void)csvExportProcessWillBegin:(SPCSVExporter *)exporter -{ - [[exportProgressText onMainThread] displayIfNeeded]; - - [[exportProgressIndicator onMainThread] setIndeterminate:YES]; - [[exportProgressIndicator onMainThread] setUsesThreadedAnimation:YES]; - [[exportProgressIndicator onMainThread] startAnimation:self]; - +{ + [exportProgressText displayIfNeeded]; + + [exportProgressIndicator setIndeterminate:YES]; + [exportProgressIndicator setUsesThreadedAnimation:YES]; + [exportProgressIndicator startAnimation:self]; + // Only update the progress text if this is a table export if (exportSource == SPTableExport) { // Update the current table export index currentTableExportIndex = (exportTableCount - [exporters count]); - - [[exportProgressText onMainThread] setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Fetching data...", @"export label showing that the app is fetching data for a specific table"), currentTableExportIndex, exportTableCount, [exporter csvTableName]]]; + + [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Fetching data...", @"export label showing that the app is fetching data for a specific table"), currentTableExportIndex, exportTableCount, [exporter csvTableName]]]; } else { - [[exportProgressText onMainThread] setStringValue:NSLocalizedString(@"Fetching data...", @"export label showing that the app is fetching data")]; + [exportProgressText setStringValue:NSLocalizedString(@"Fetching data...", @"export label showing that the app is fetching data")]; } - - [[exportProgressText onMainThread] displayIfNeeded]; + + [exportProgressText displayIfNeeded]; } - (void)csvExportProcessComplete:(SPCSVExporter *)exporter @@ -93,15 +93,8 @@ else { // Close the last exporter's file handle [[exporter exportOutputFile] close]; - - [NSApp endSheet:exportProgressWindow returnCode:0]; - [exportProgressWindow orderOut:self]; - - // Restore query mode - [tableDocumentInstance setQueryMode:SPInterfaceQueryMode]; - - // Display Growl notification - [self displayExportFinishedGrowlNotification]; + + [self exportEnded]; } } @@ -109,22 +102,22 @@ { // Only update the progress text if this is a table export if (exportSource == SPTableExport) { - [[exportProgressText onMainThread] setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Writing data...", @"export label showing app if writing data for a specific table"), currentTableExportIndex, exportTableCount, [exporter csvTableName]]]; + [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Writing data...", @"export label showing app if writing data for a specific table"), currentTableExportIndex, exportTableCount, [exporter csvTableName]]]; } else { - [[exportProgressText onMainThread] setStringValue:NSLocalizedString(@"Writing data...", @"export label showing app is writing data")]; + [exportProgressText setStringValue:NSLocalizedString(@"Writing data...", @"export label showing app is writing data")]; } - - [[exportProgressText onMainThread] displayIfNeeded]; - - [[exportProgressIndicator onMainThread] stopAnimation:self]; - [[exportProgressIndicator onMainThread] setUsesThreadedAnimation:NO]; - [[exportProgressIndicator onMainThread] setIndeterminate:NO]; - [[exportProgressIndicator onMainThread] setDoubleValue:0]; + + [exportProgressText displayIfNeeded]; + + [exportProgressIndicator stopAnimation:self]; + [exportProgressIndicator setUsesThreadedAnimation:NO]; + [exportProgressIndicator setIndeterminate:NO]; + [exportProgressIndicator setDoubleValue:0]; } - (void)csvExportProcessProgressUpdated:(SPCSVExporter *)exporter -{ +{ [exportProgressIndicator setDoubleValue:[exporter exportProgressValue]]; } 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/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; @@ -106,6 +107,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/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 <https://github.com/sequelpro/sequelpro> + +#import <Cocoa/Cocoa.h> + +@class SPComboBoxCell; + +@protocol SPComboBoxCellDelegate <NSObject> + +@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<SPComboBoxCellDelegate> spDelegate; +} + +@property (assign) IBOutlet id<SPComboBoxCellDelegate> 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 <https://github.com/sequelpro/sequelpro> + +#import "SPComboBoxCell.h" +#import <objc/runtime.h> + +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/SPComboPopupButton.m b/Source/SPComboPopupButton.m index 8c090911..3df3d6ec 100644 --- a/Source/SPComboPopupButton.m +++ b/Source/SPComboPopupButton.m @@ -129,9 +129,9 @@ CGContextSaveGState(context); CGContextSetStrokeColor(context, lineColorParts); - CGContextAddRect(context, CGRectMake(boundingLinePosition - 0.5, boundsRect.origin.y + heightIndent, 1.f, boundsRect.size.height - abs(2 * heightIndent))); + CGContextAddRect(context, CGRectMake(boundingLinePosition - 0.5, boundsRect.origin.y + heightIndent, 1.f, boundsRect.size.height - fabs(2 * heightIndent))); CGContextClip(context); - CGContextDrawLinearGradient(context, lineGradient, CGPointMake(boundingLinePosition - 0.5, boundsRect.origin.y + heightIndent), CGPointMake(boundingLinePosition - 0.5, boundsRect.origin.y + boundsRect.size.height - abs(heightIndent)), 0); + CGContextDrawLinearGradient(context, lineGradient, CGPointMake(boundingLinePosition - 0.5, boundsRect.origin.y + heightIndent), CGPointMake(boundingLinePosition - 0.5, boundsRect.origin.y + boundsRect.size.height - fabs(heightIndent)), 0); CGContextRestoreGState(context); CGGradientRelease(lineGradient); @@ -281,7 +281,7 @@ // Custom tracking to be performed - indent the vertical button area slightly - activeRect = NSMakeRect(cellFrame.origin.x, cellFrame.origin.y + heightIndent, cellFrame.size.width - [(SPComboPopupButton *)controlView lineOffset] + 1, cellFrame.size.height - fabsf(2 * heightIndent)); + activeRect = NSMakeRect(cellFrame.origin.x, cellFrame.origin.y + heightIndent, cellFrame.size.width - [(SPComboPopupButton *)controlView lineOffset] + 1, cellFrame.size.height - fabs(2 * heightIndent)); // Continue tracking the mouse while it's down, updating the state as it enters and leaves the cell, // until it is released; if still within the cell, perform a click. diff --git a/Source/SPConnectionController.h b/Source/SPConnectionController.h index 99488600..59a83740 100644 --- a/Source/SPConnectionController.h +++ b/Source/SPConnectionController.h @@ -47,7 +47,7 @@ #endif ; -@interface SPConnectionController : NSViewController <SPMySQLConnectionDelegate> +@interface SPConnectionController : NSViewController <SPMySQLConnectionDelegate, NSOpenSavePanelDelegate> { id <SPConnectionControllerDelegateProtocol, NSObject> delegate; @@ -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; @@ -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 3176fb2f..91629bf3 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -52,6 +52,8 @@ #import "SPThreadAdditions.h" #import "SPFavoriteColorSupport.h" #import "SPNamedNode.h" +#import "SPWindowController.h" +#import "SPFavoritesOutlineView.h" #import <SPMySQL/SPMySQL.h> @@ -61,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 () @@ -168,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; } @@ -182,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; } } @@ -199,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; } @@ -210,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; } @@ -221,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; } @@ -342,10 +381,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) { @@ -429,7 +468,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]; @@ -484,6 +523,68 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, #endif } +- (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) { + // 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; + } + + // see PemToDer() in crypto_wrapper.cpp in yaSSL + const char rsaHead[] = "-----BEGIN RSA PRIVATE KEY-----"; + const char rsaFoot[] = "-----END RSA PRIVATE KEY-----"; + + 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.", @"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; + + */ +} + /** * Show connection help webpage. */ @@ -1012,6 +1113,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 @@ -1142,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; } @@ -1406,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; } @@ -1628,7 +1738,7 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, if (![NSThread isMainThread]) return [[self onMainThread] _restoreConnectionInterface]; // Reset the window title - [[dbDocument parentWindow] setTitle:[dbDocument displayName]]; + [dbDocument updateWindowTitle:self]; [[dbDocument parentTabViewItem] setLabel:[dbDocument displayName]]; [[dbDocument parentTabViewItem] setColor:nil]; @@ -1703,7 +1813,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")]; @@ -1941,3 +2051,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/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/SPConstants.h b/Source/SPConstants.h index 2c2555a8..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 @@ -259,6 +260,7 @@ extern NSString *SPFavoritesPasteboardDragType; extern NSString *SPContentFilterPasteboardDragType; extern NSString *SPNavigatorPasteboardDragType; extern NSString *SPNavigatorTableDataPasteboardDragType; +extern NSString *SPExportCustomFileNameTokenPlistType; // File extensions extern NSString *SPFileExtensionDefault; @@ -273,6 +275,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; @@ -395,10 +403,18 @@ extern NSString *SPCSVImportFieldEscapeCharacter; extern NSString *SPCSVImportFirstLineIsHeader; extern NSString *SPCSVFieldImportMappingAlignment; extern NSString *SPImportClipboardTempFileNamePrefix; -extern NSString *SPSQLExportUseCompression; -extern NSString *SPNoBOMforSQLdumpFile; -extern NSString *SPExportLastDirectory; -extern NSString *SPExportFilenameFormat; +extern NSString *SPLastExportSettings; + +// 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; @@ -492,6 +508,9 @@ extern NSString *SPFavoriteSSLCACertFileLocationKey; extern NSString *SPFavoriteUseCompressionKey; extern NSString *SPConnectionFavoritesChangedNotification; +extern NSString *SPFFormatKey; +extern NSString *SPFVersionKey; + // Favorites import/export extern NSString *SPFavoritesDataRootKey; @@ -604,6 +623,8 @@ extern NSString *SPBundleShellVariableAllFunctions; extern NSString *SPBundleShellVariableAllViews; extern NSString *SPBundleShellVariableAllTables; +extern NSString *SPCurrentTimestampPattern; + typedef NS_ENUM(NSInteger, SPBundleRedirectAction) { SPBundleRedirectActionNone = 200, SPBundleRedirectActionReplaceSection = 201, @@ -622,6 +643,18 @@ extern NSString *SPURLSchemeQueryResultPathHeader; 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]) // Provides a standard method for our "[x release], x = nil;" convention. @@ -652,3 +685,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/SPConstants.m b/Source/SPConstants.m index 5f3348ed..16f59f7a 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"; @@ -55,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"; @@ -195,10 +202,18 @@ 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 *SPLastExportSettings = @"LastExportSettings"; + +// 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"; @@ -302,6 +317,9 @@ NSString *SPFavoriteSSLCACertFileLocationKey = @"sslCACertFileLocati NSString *SPFavoriteUseCompressionKey = @"useCompression"; NSString *SPConnectionFavoritesChangedNotification = @"SPConnectionFavoritesChanged"; +NSString *SPFFormatKey = @"format"; +NSString *SPFVersionKey = @"version"; + // Favorites import/export NSString *SPFavoritesDataRootKey = @"SPConnectionFavorites"; @@ -415,12 +433,20 @@ 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_"; NSString *SPURLSchemeQueryResultStatusPathHeader = @"/tmp/SP_QUERY_RESULT_STATUS_"; 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/SPContentFilterManager.m b/Source/SPContentFilterManager.m index a8e747e2..3f59f198 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:SPFContentFiltersContentType forKey:SPFFormatKey]; [spfdata setObject:@NO forKey:@"encrypted"]; NSIndexSet *indexes = [contentFilterTableView selectedRowIndexes]; 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 15839a7e..3a04d1d5 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; } @@ -1990,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]; } } @@ -2016,9 +2023,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 +2035,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 +2061,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"]] + ); } } @@ -2111,7 +2124,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 @""; @@ -2441,6 +2457,12 @@ // Check if the field can identified bijectively if ( aTableView == customQueryView ) { + + // Nothing is editable while the field editor is running. + // This guards against a special case where accessibility services might + // check if a table field is editable while the sheet is running. + if (fieldEditor) return NO; + NSDictionary *columnDefinition = [cqColumnDefinition objectAtIndex:[[aTableColumn identifier] integerValue]]; // Check if current field is a blob @@ -2450,7 +2472,6 @@ // Open the editing sheet if required if ([customQueryView shouldUseFieldEditorForRow:rowIndex column:[[aTableColumn identifier] integerValue] checkWithLock:NULL]) { - if (fieldEditor) SPClear(fieldEditor); fieldEditor = [[SPFieldEditorController alloc] init]; // Remember edited row for reselecting and setting the scroll view after reload @@ -2563,16 +2584,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]; } } } @@ -3382,12 +3404,16 @@ [queryFavoritesButton removeItemAtIndex:[queryFavoritesButton numberOfItems]-1]; // Build document-based list - headerMenuItem = [[NSMenuItem alloc] initWithTitle: - [[[[tableDocumentInstance fileURL] absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] lastPathComponent] - action:NULL keyEquivalent:@""]; + NSString *tblDocName = [[[[tableDocumentInstance fileURL] absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] lastPathComponent]; + if(!tblDocName) { + //NSMenuItem will not accept nil as title + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:[NSString stringWithFormat:@"Document name conversion resulted in nil string!? tableDocumentInstance=%@ fileURL=%@",tableDocumentInstance,[tableDocumentInstance fileURL]] + userInfo:nil]; + } + headerMenuItem = [[NSMenuItem alloc] initWithTitle:tblDocName action:NULL keyEquivalent:@""]; [headerMenuItem setTag:SP_FAVORITE_HEADER_MENUITEM_TAG]; - [headerMenuItem setToolTip:[NSString stringWithFormat:@"‘%@’ based favorites", - [[[[tableDocumentInstance fileURL] absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] lastPathComponent]]]; + [headerMenuItem setToolTip:[NSString stringWithFormat:NSLocalizedString(@"‘%@’ based favorites",@"Query Favorites : List : Section Heading : current connection document : tooltip (arg is the name of the spf file)"), tblDocName]]; [headerMenuItem setIndentationLevel:0]; [menu addItem:headerMenuItem]; [headerMenuItem release]; @@ -3411,9 +3437,9 @@ } // Build global list - headerMenuItem = [[NSMenuItem alloc] initWithTitle:@"Global" action:NULL keyEquivalent:@""]; + headerMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Global",@"Query Favorites : List : Section Heading : global query favorites") action:NULL keyEquivalent:@""]; [headerMenuItem setTag:SP_FAVORITE_HEADER_MENUITEM_TAG]; - [headerMenuItem setToolTip:@"Globally stored favorites"]; + [headerMenuItem setToolTip:NSLocalizedString(@"Globally stored favorites",@"Query Favorites : List : Section Heading : global : tooltip")]; [headerMenuItem setIndentationLevel:0]; [menu addItem:headerMenuItem]; [headerMenuItem release]; @@ -3681,9 +3707,9 @@ } } - if(fieldEditor) { - SPClear(fieldEditor); - } + // this is a delegate method of the field editor controller. calling release + // now would risk a dealloc while it is still our parent on the stack: + [fieldEditor autorelease], fieldEditor = nil; // Preserve focus and restore selection indexes if appropriate [[tableDocumentInstance parentWindow] makeFirstResponder:customQueryView]; @@ -3987,6 +4013,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 @@ -3997,11 +4024,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); diff --git a/Source/SPDataAdditions.h b/Source/SPDataAdditions.h index 0ceca0d3..cd8374f6 100644 --- a/Source/SPDataAdditions.h +++ b/Source/SPDataAdditions.h @@ -28,10 +28,22 @@ // // More info at <https://github.com/sequelpro/sequelpro> +typedef NS_OPTIONS(NSUInteger, SPLineTerminator) { + SPLineTerminatorAny = 0, + SPLineTerminatorCR = 1, + SPLineTerminatorLF = 2, + SPLineTerminatorCRLF = 4, +}; + @interface NSData (SPDataAdditions) +- (NSData *)sha1Hash; + - (NSData *)dataEncryptedWithPassword:(NSString *)password; +- (NSData *)dataEncryptedWithKey:(NSData *)aesKey IV:(NSData *)iv; - (NSData *)dataDecryptedWithPassword:(NSString *)password; +- (NSData *)dataDecryptedWithKey:(NSData *)key; + - (NSData *)compress; - (NSData *)decompress; @@ -41,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 e9eaa927..65605577 100644 --- a/Source/SPDataAdditions.m +++ b/Source/SPDataAdditions.m @@ -35,72 +35,193 @@ #import "SPDataAdditions.h" #include <zlib.h> -#include <openssl/aes.h> -#include <openssl/sha.h> +#include <CommonCrypto/CommonCrypto.h> #include <stdlib.h> +#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 - @implementation NSData (SPDataAdditions) +- (NSData *)sha1Hash +{ + unsigned char digest[CC_SHA1_DIGEST_LENGTH]; + + //let's do it as a one step operation, if it fits + if([self length] <= UINT32_MAX) { + CC_SHA1([self bytes], (uint32_t)[self length], digest); + } + // or multi-step if length > 32 bit + else { + CC_SHA1_CTX ctx; + CC_SHA1_Init(&ctx); + + NSUInteger offset = 0; + uint32_t len; + while((len = LimitUInt32([self length]-offset)) > 0) { + CC_SHA1_Update(&ctx, ([self bytes]+offset), len); + offset += len; + } + + CC_SHA1_Final(digest, &ctx); + } + + return [NSData dataWithBytes:digest length:CC_SHA1_DIGEST_LENGTH]; +} + - (NSData *)dataEncryptedWithPassword:(NSString *)password { // Create a random 128-bit initialization vector - srand((unsigned int)time(NULL)); - NSInteger ivIndex; - unsigned char iv[16]; - for (ivIndex = 0; ivIndex < 16; ivIndex++) - iv[ivIndex] = rand() & 0xff; + // IV is block "-1" of plaintext data, therefore it is blockSize long + unsigned char iv[kCCBlockSizeAES128]; + 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 + NSData *passwordDigest = [[[password dataUsingEncoding:NSUTF8StringEncoding] sha1Hash] subdataWithRange:NSMakeRange(0, kCCKeySizeAES128)]; + + return [self dataEncryptedWithKey:passwordDigest IV:ivData]; +} - // Calculate the 16-byte AES block padding - NSInteger dataLength = [self length]; - NSInteger paddedLength = dataLength + (32 - (dataLength % 16)); - NSInteger totalLength = paddedLength + 16; // Data plus IV +/* + * ABNF for the returned data: + * OCTET = <any 8-bit sequence of data> + * ENCRYPTED = IV AES + * IV = 16OCTET ; 16 random bytes + * AES = <AES_128_CBC(PADDED)> + * PADDED = PLAINTEXT 12*28OCTET LEN ; 13-28 bytes padding (value irrelevant) + * PLAINTEXT = *OCTET ; the raw data + * LEN = 4OCTET ; big endian length of plaintext + * + * Examples for padding: + * Data len padding len = total + * --------- -------- ----- -------- + * 0 28 4 32 + * 1 27 4 32 + * ... + * 15 13 4 32 + * 16 28 4 48 + * 17 27 4 48 + * ... + * + * Note that total has to be a multiple of 16 for AES 128. + * Our padding scheme also requires 4 bytes of storage for len. + * This is were the 32 comes from: Without that 15 data bytes would produce + * only 1 padding byte, which is not enough to store the 4 byte len. + */ +- (NSData *)dataEncryptedWithKey:(NSData *)aesKey IV:(NSData *)iv +{ + if([self length] > UINT32_MAX) + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Length of NSData exceeds 32 Bit, not supported!" userInfo:nil]; + + if([iv length] != kCCBlockSizeAES128) + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Length of ivData must be == kCCBlockSizeAES128!" userInfo:nil]; + if([aesKey length] != kCCKeySizeAES128) + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Key length invalid. Must be kCCKeySizeAES128 bytes!" userInfo:nil]; + + // Calculate the 16-byte AES block padding + uint32_t dataLength = (uint32_t)[self length]; + NSInteger paddedLength = dataLength + (2*kCCBlockSizeAES128 - (dataLength % kCCBlockSizeAES128)); + NSInteger totalLength = paddedLength + kCCBlockSizeAES128; // Data plus IV + // Allocate enough space for the IV + ciphertext unsigned char *encryptedBytes = calloc(1, totalLength); // The first block of the ciphertext buffer is the IV - memcpy(encryptedBytes, iv, 16); + memcpy(encryptedBytes, [iv bytes], kCCBlockSizeAES128); - unsigned char *paddedBytes = calloc(1, paddedLength); + unsigned char *paddedBytes = encryptedBytes + kCCBlockSizeAES128; memcpy(paddedBytes, [self bytes], dataLength); // The last 32-bit chunk is the size of the plaintext, which is encrypted with the plaintext - NSInteger bigIntDataLength = NSSwapHostIntToBig((unsigned int)dataLength); - memcpy(paddedBytes + (paddedLength - 4), &bigIntDataLength, 4); - - // Create the key from first 128-bits of the 160-bit password hash - unsigned char passwordDigest[20]; - SHA1((const unsigned char *)[password UTF8String], strlen([password UTF8String]), passwordDigest); - AES_KEY aesKey; - AES_set_encrypt_key(passwordDigest, 128, &aesKey); - - // AES-128-cbc encrypt the data, filling in the buffer after the IV - AES_cbc_encrypt(paddedBytes, encryptedBytes + 16, paddedLength, &aesKey, iv, AES_ENCRYPT); - free(paddedBytes); - + uint32_t bigIntDataLength = NSSwapHostIntToBig(dataLength); + unsigned char *lenPtr = paddedBytes + (paddedLength - 4); + memcpy(lenPtr, &bigIntDataLength, 4); + + CCCryptorStatus res = CCCrypt( + kCCEncrypt, // operation mode + kCCAlgorithmAES128, // algorithm + 0, // options. We use our own padding algorithm and CBC is the default + [aesKey bytes], // key bytes + kCCKeySizeAES128, // key length + [iv bytes], // iv bytes (length == block size) + paddedBytes, // raw data + paddedLength, // length of raw data + paddedBytes, // output buffer. overwriting input is OK + paddedLength, // output buffer size + NULL // number of bytes written. not relevant here + ); + + if(res != kCCSuccess) + @throw [NSException exceptionWithName:SPCommonCryptoExceptionName + reason:[NSString stringWithFormat:@"CCCrypt() failed! (CCCryptorStatus=%d)",res] + userInfo:@{@"cryptorStatus":@(res)}]; + + // the return code of CCCrypt() is not always reliable, better check it again + if(memcmp(lenPtr, &bigIntDataLength, 4) == 0) + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Encrypted data is same as plaintext data!" userInfo:nil]; + return [NSData dataWithBytesNoCopy:encryptedBytes length:totalLength]; } - (NSData *)dataDecryptedWithPassword:(NSString *)password { // Create the key from the password hash - unsigned char passwordDigest[20]; - SHA1((const unsigned char *)[password UTF8String], strlen([password UTF8String]), passwordDigest); + NSData *passwordDigest = [[[password dataUsingEncoding:NSUTF8StringEncoding] sha1Hash] subdataWithRange:NSMakeRange(0, kCCKeySizeAES128)]; + + return [self dataDecryptedWithKey:passwordDigest]; - // AES-128-cbc decrypt the data - AES_KEY aesKey; - AES_set_decrypt_key(passwordDigest, 128, &aesKey); +} + +- (NSData *)dataDecryptedWithKey:(NSData *)aesKey +{ + if([aesKey length] != kCCKeySizeAES128) + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Key length invalid. Must be kCCKeySizeAES128 bytes!" userInfo:nil]; + + if([self length] < (2*kCCBlockSizeAES128) || [self length] > UINT32_MAX) + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Length of encrypted NSData must be in range 32 to 2^32!" userInfo:nil]; // Total length = encrypted length + IV - NSInteger totalLength = [self length]; - NSInteger encryptedLength = totalLength - 16; + NSUInteger totalLength = [self length]; + NSUInteger encryptedLength = totalLength - kCCBlockSizeAES128; // >=0 ensured above // Take the IV from the first 128-bit block - unsigned char iv[16]; - memcpy(iv, [self bytes], 16); + unsigned char iv[kCCBlockSizeAES128]; + memcpy(iv, [self bytes], kCCBlockSizeAES128); // Decrypt the data - unsigned char *decryptedBytes = (unsigned char*)malloc(encryptedLength); - AES_cbc_encrypt([self bytes] + 16, decryptedBytes, encryptedLength, &aesKey, iv, AES_DECRYPT); + unsigned char *decryptedBytes = calloc(1,encryptedLength); + + CCCryptorStatus res = CCCrypt( + kCCDecrypt, // operation mode + kCCAlgorithmAES128, // algorithm + 0, // options. We use our own padding algorithm and CBC is the default + [aesKey bytes], // key bytes + kCCKeySizeAES128, // key length + iv, // iv bytes (length == block size) + ([self bytes] + kCCBlockSizeAES128), // raw data + encryptedLength, // length of raw data + decryptedBytes, // output buffer. overwriting input is OK + encryptedLength, // output buffer size + NULL // number of bytes written. not relevant here + ); + + if(res != kCCSuccess) { + @throw [NSException exceptionWithName:SPCommonCryptoExceptionName + reason:[NSString stringWithFormat:@"CCCrypt() failed! (CCCryptorStatus=%d)",res] + userInfo:@{@"cryptorStatus":@(res)}]; + } // If decryption was successful, these blocks will be zeroed if ( *((UInt32*)decryptedBytes + ((encryptedLength / 4) - 4)) || @@ -112,8 +233,14 @@ } // Get the size of the data from the last 32-bit chunk - NSInteger bigIntDataLength = *((UInt32*)decryptedBytes + ((encryptedLength / 4) - 1)); - NSInteger dataLength = NSSwapBigIntToHost((unsigned int)bigIntDataLength); + uint32_t bigIntDataLength = *((UInt32*)decryptedBytes + ((encryptedLength / sizeof(UInt32)) - 1)); + uint32_t dataLength = NSSwapBigIntToHost(bigIntDataLength); + + if(dataLength >= (encryptedLength-sizeof(UInt32))) { //this way dataLength can still reach into padding, but we own that memory anyway. + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:[NSString stringWithFormat:@"dataLength=%u exceeds encryptedLength=%lu! Either the message is incomplete, decrypting resulted in invalid data, or this is a malicious message!",dataLength,encryptedLength] + userInfo:nil]; + } return [NSData dataWithBytesNoCopy:decryptedBytes length:dataLength]; } @@ -311,4 +438,62 @@ 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 - + +uint32_t LimitUInt32(NSUInteger i) { +#if NSUIntegerMax > UINT32_MAX + return (i > UINT32_MAX)? UINT32_MAX : (uint32_t)i; +#else + return i; +#endif +} diff --git a/Source/SPDataImport.m b/Source/SPDataImport.m index aa50a827..1439d7ca 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 <SPMySQL/SPMySQL.h> @@ -159,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 - @@ -383,15 +381,16 @@ // 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; } - fileIsCompressed = [sqlFileHandle isCompressed]; + fileIsCompressed = ([sqlFileHandle compressionFormat] != SPNoCompression); // Grab the file length fileTotalLength = (NSUInteger)[[[[NSFileManager defaultManager] attributesOfItemAtPath:filename error:NULL] objectForKey:NSFileSize] longLongValue]; @@ -401,18 +400,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]; @@ -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]; @@ -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 @@ -667,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" @@ -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; @@ -752,23 +756,25 @@ // 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; 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]; @@ -820,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]; @@ -869,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]; @@ -933,11 +941,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++) { @@ -1180,10 +1190,10 @@ // 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}]; + [[tableDocumentInstance databaseStructureRetrieval] queryDbStructureInBackgroundWithUserInfo:@{@"forceUpdate" : @YES}]; // Select the new table [tablesListInstance selectItemWithName:selectedTableTarget]; @@ -1213,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; @@ -1671,7 +1677,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/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..46db243f 100644 --- a/Source/SPDataStorage.m +++ b/Source/SPDataStorage.m @@ -32,13 +32,18 @@ #import "SPObjectAdditions.h" #import <SPMySQL/SPMySQLStreamingResultStore.h> #include <stdlib.h> +#include <mach/mach_time.h> @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) @@ -57,33 +62,46 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore */ - (void) setDataStorage:(SPMySQLStreamingResultStore *)newDataStorage updatingExisting:(BOOL)updateExistingStore { - NSUInteger i; - editedRowCount = 0; - SPClear(editedRows); - if (unloadedColumns) free(unloadedColumns), unloadedColumns = NULL; - - if (dataStorage) { + SPMySQLStreamingResultStore *oldDataStorage = dataStorage; + if (oldDataStorage) { // If the table is reloading data, link to the current data store for smoother loads if (updateExistingStore) { - [newDataStorage replaceExistingResultStore:dataStorage]; + [newDataStorage replaceExistingResultStore:oldDataStorage]; } - - SPClear(dataStorage); } - dataStorage = [newDataStorage retain]; - [dataStorage setDelegate:self]; + [newDataStorage retain]; - numberOfColumns = [dataStorage numberOfFields]; - editedRows = [NSPointerArray new]; - if ([dataStorage dataDownloaded]) { - [self resultStoreDidFinishLoadingData:dataStorage]; + NSPointerArray *newEditedRows = [[NSPointerArray alloc] init]; + NSUInteger newNumberOfColumns = [newDataStorage numberOfFields]; + BOOL *newUnloadedColumns = calloc(newNumberOfColumns, sizeof(BOOL)); + for (NSUInteger i = 0; i < newNumberOfColumns; i++) { + newUnloadedColumns[i] = NO; } - - unloadedColumns = calloc(numberOfColumns, sizeof(BOOL)); - for (i = 0; i < numberOfColumns; i++) { - unloadedColumns[i] = NO; + + BOOL *oldUnloadedColumns = unloadedColumns; + NSPointerArray *oldEditedRows = editedRows; + @synchronized(self) { + dataStorage = newDataStorage; + numberOfColumns = newNumberOfColumns; + unloadedColumns = newUnloadedColumns; + editedRowCount = 0; + editedRows = newEditedRows; + } + free(oldUnloadedColumns); + [oldEditedRows release]; + [oldDataStorage release]; + + // the only delegate callback is resultStoreDidFinishLoadingData:. + // We can't set the delegate before exchanging the dataStorage ivar since then + // the message would come from an unknown object. + // But if we set it afterwards, we risk losing the callback event (since it could've + // happened in the meantime) - this is what the following if() is for. + [newDataStorage setDelegate:self]; + + if ([newDataStorage dataDownloaded]) { + [self resultStoreDidFinishLoadingData:newDataStorage]; } } @@ -96,28 +114,30 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore */ - (NSMutableArray *) rowContentsAtIndex:(NSUInteger)anIndex { - - // If an edited row exists for the supplied index, return it - if (anIndex < editedRowCount) { - NSMutableArray *editedRow = SPDataStorageGetEditedRow(editedRows, anIndex); - - if (editedRow != NULL) { - return editedRow; + SPNotLoaded *notLoaded = [SPNotLoaded notLoaded]; + @synchronized(self) { + // If an edited row exists for the supplied index, return it + if (anIndex < editedRowCount) { + NSMutableArray *editedRow = SPDataStorageGetEditedRow(editedRows, anIndex); + + if (editedRow != NULL) { + return editedRow; + } } - } - - // Otherwise, prepare to return the underlying storage row - 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]); + + // Otherwise, prepare to return the underlying storage row + NSMutableArray *dataArray = SPMySQLResultStoreGetRow(dataStorage, anIndex); + + // Modify unloaded cells as appropriate + [self _assesUnloadedColumnsIsSet]; + for (NSUInteger i = 0; i < numberOfColumns; i++) { + if (unloadedColumns[i]) { + CFArraySetValueAtIndex((CFMutableArrayRef)dataArray, i, notLoaded); + } } + + return dataArray; } - - return dataArray; } /** @@ -125,28 +145,31 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore */ - (id) cellDataAtRow:(NSUInteger)rowIndex column:(NSUInteger)columnIndex { - // If an edited row exists at the supplied index, return it - if (rowIndex < editedRowCount) { - NSMutableArray *editedRow = SPDataStorageGetEditedRow(editedRows, rowIndex); + SPNotLoaded *notLoaded = [SPNotLoaded notLoaded]; + @synchronized(self) { + // If an edited row exists at the supplied index, return it + if (rowIndex < editedRowCount) { + NSMutableArray *editedRow = SPDataStorageGetEditedRow(editedRows, rowIndex); + + if (editedRow != NULL) { + return CFArrayGetValueAtIndex((CFArrayRef)editedRow, columnIndex); + } + } - if (editedRow != NULL) { - return CFArrayGetValueAtIndex((CFArrayRef)editedRow, columnIndex); + // Throw an exception if the column index is out of bounds + if (columnIndex >= numberOfColumns) { + [NSException raise:NSRangeException format:@"Requested storage column (col %llu) beyond bounds (%llu)", (unsigned long long)columnIndex, (unsigned long long)numberOfColumns]; } - } - // Throw an exception if the column index is out of bounds - if (columnIndex >= numberOfColumns) { - [NSException raise:NSRangeException format:@"Requested storage column (col %llu) beyond bounds (%llu)", (unsigned long long)columnIndex, (unsigned long long)numberOfColumns]; - } + // If the specified column is not loaded, return a SPNotLoaded reference + [self _assesUnloadedColumnsIsSet]; + if (unloadedColumns[columnIndex]) { + return notLoaded; + } - // If the specified column is not loaded, return a SPNotLoaded reference - NSAssert(unloadedColumns != NULL, @"unloadedColumns not loaded!"); - if (unloadedColumns[columnIndex]) { - return [SPNotLoaded notLoaded]; + // Return the content + return SPMySQLResultStoreObjectAtRowAndColumn(dataStorage, rowIndex, columnIndex); } - - // Return the content - return SPMySQLResultStoreObjectAtRowAndColumn(dataStorage, rowIndex, columnIndex); } /** @@ -155,33 +178,35 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore */ - (id) cellPreviewAtRow:(NSUInteger)rowIndex column:(NSUInteger)columnIndex previewLength:(NSUInteger)previewLength { - - // If an edited row exists at the supplied index, return it - if (rowIndex < editedRowCount) { - NSMutableArray *editedRow = SPDataStorageGetEditedRow(editedRows, rowIndex); - - if (editedRow != NULL) { - id anObject = CFArrayGetValueAtIndex((CFArrayRef)editedRow, columnIndex); - if ([anObject isKindOfClass:[NSString class]] && [(NSString *)anObject length] > 150) { - return ([NSString stringWithFormat:@"%@...", [anObject substringToIndex:147]]); + SPNotLoaded *notLoaded = [SPNotLoaded notLoaded]; + @synchronized(self) { + // If an edited row exists at the supplied index, return it + if (rowIndex < editedRowCount) { + NSMutableArray *editedRow = SPDataStorageGetEditedRow(editedRows, rowIndex); + + if (editedRow != NULL) { + id anObject = CFArrayGetValueAtIndex((CFArrayRef)editedRow, columnIndex); + if ([anObject isKindOfClass:[NSString class]] && [(NSString *)anObject length] > 150) { + return ([NSString stringWithFormat:@"%@...", [anObject substringToIndex:147]]); + } + return anObject; } - return anObject; } - } - // Throw an exception if the column index is out of bounds - if (columnIndex >= numberOfColumns) { - [NSException raise:NSRangeException format:@"Requested storage column (col %llu) beyond bounds (%llu)", (unsigned long long)columnIndex, (unsigned long long)numberOfColumns]; - } + // Throw an exception if the column index is out of bounds + if (columnIndex >= numberOfColumns) { + [NSException raise:NSRangeException format:@"Requested storage column (col %llu) beyond bounds (%llu)", (unsigned long long)columnIndex, (unsigned long long)numberOfColumns]; + } - // If the specified column is not loaded, return a SPNotLoaded reference - NSAssert(unloadedColumns != NULL, @"unloadedColumns not loaded!"); - if (unloadedColumns[columnIndex]) { - return [SPNotLoaded notLoaded]; - } + // If the specified column is not loaded, return a SPNotLoaded reference + [self _assesUnloadedColumnsIsSet]; + if (unloadedColumns[columnIndex]) { + return notLoaded; + } - // Return the content - return SPMySQLResultStorePreviewAtRowAndColumn(dataStorage, rowIndex, columnIndex, previewLength); + // Return the content + return SPMySQLResultStorePreviewAtRowAndColumn(dataStorage, rowIndex, columnIndex, previewLength); + } } /** @@ -189,26 +214,29 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore */ - (BOOL) cellIsNullOrUnloadedAtRow:(NSUInteger)rowIndex column:(NSUInteger)columnIndex { - // If an edited row exists at the supplied index, check it for a NULL. - if (rowIndex < editedRowCount) { - NSMutableArray *editedRow = SPDataStorageGetEditedRow(editedRows, rowIndex); + @synchronized(self) { + // If an edited row exists at the supplied index, check it for a NULL. + if (rowIndex < editedRowCount) { + NSMutableArray *editedRow = SPDataStorageGetEditedRow(editedRows, rowIndex); + + if (editedRow != NULL) { + return [(id)CFArrayGetValueAtIndex((CFArrayRef)editedRow, columnIndex) isNSNull]; + } + } - if (editedRow != NULL) { - return [(id)CFArrayGetValueAtIndex((CFArrayRef)editedRow, columnIndex) isNSNull]; + // Throw an exception if the column index is out of bounds + if (columnIndex >= numberOfColumns) { + [NSException raise:NSRangeException format:@"Requested storage column (col %llu) beyond bounds (%llu)", (unsigned long long)columnIndex, (unsigned long long)numberOfColumns]; } - } - // Throw an exception if the column index is out of bounds - if (columnIndex >= numberOfColumns) { - [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; - } + [self _assesUnloadedColumnsIsSet]; + if (unloadedColumns[columnIndex]) { + return YES; + } - return [dataStorage cellIsNullAtRow:rowIndex column:columnIndex]; + return [dataStorage cellIsNullAtRow:rowIndex column:columnIndex]; + } } #pragma mark - @@ -221,25 +249,30 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore */ - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len { - NSMutableArray *targetRow = NULL; - - // If the start index is out of bounds, return 0 to indicate end of results - if (state->state >= SPMySQLResultStoreGetRowCount(dataStorage)) return 0; - - // If an edited row exists for the supplied index, use that; otherwise use the underlying - // storage row - if (state->state < editedRowCount) { - targetRow = SPDataStorageGetEditedRow(editedRows, state->state); - } + NSMutableArray *targetRow = nil; + size_t srcObject; + + SPNotLoaded *notLoaded = [SPNotLoaded notLoaded]; + @synchronized(self) { + srcObject = (size_t)dataStorage ^ (size_t)editedRows ^ editedRowCount; + // If the start index is out of bounds, return 0 to indicate end of results + if (state->state >= SPMySQLResultStoreGetRowCount(dataStorage)) return 0; + + // If an edited row exists for the supplied index, use that; otherwise use the underlying + // storage row + if (state->state < editedRowCount) { + targetRow = SPDataStorageGetEditedRow(editedRows, state->state); + } - if (targetRow == NULL) { - targetRow = SPMySQLResultStoreGetRow(dataStorage, state->state); + if (targetRow == nil) { + 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]); + // Modify unloaded cells as appropriate + [self _assesUnloadedColumnsIsSet]; + for (NSUInteger i = 0; i < numberOfColumns; i++) { + if (unloadedColumns[i]) { + CFArraySetValueAtIndex((CFMutableArrayRef)targetRow, i, notLoaded); + } } } } @@ -249,7 +282,7 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore state->state += 1; state->itemsPtr = stackbuf; - state->mutationsPtr = (unsigned long *)self; + state->mutationsPtr = (unsigned long *)srcObject; return 1; } @@ -264,16 +297,17 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore */ - (void) addRowWithContents:(NSMutableArray *)aRow { + @synchronized(self) { + // Verify the row is of the correct length + [self _checkNewRow:aRow]; - // Verify the row is of the correct length - [self _checkNewRow:aRow]; + // Add the new row to the editable store + [editedRows addPointer:aRow]; + editedRowCount++; - // Add the new row to the editable store - [editedRows addPointer:aRow]; - editedRowCount++; - - // Update the underlying store as well to keep counts correct - [dataStorage addDummyRow]; + // Update the underlying store as well to keep counts correct + [dataStorage addDummyRow]; + } } /** @@ -283,27 +317,29 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore */ - (void) insertRowContents:(NSMutableArray *)aRow atIndex:(NSUInteger)anIndex { - unsigned long long numberOfRows = SPMySQLResultStoreGetRowCount(dataStorage); + @synchronized(self) { + unsigned long long numberOfRows = SPMySQLResultStoreGetRowCount(dataStorage); - // Verify the row is of the correct length - [self _checkNewRow:aRow]; + // Verify the row is of the correct length + [self _checkNewRow:aRow]; - // Throw an exception if the index is out of bounds - if (anIndex > numberOfRows) { - [NSException raise:NSRangeException format:@"Requested storage index (%llu) beyond bounds (%llu)", (unsigned long long)anIndex, numberOfRows]; - } + // Throw an exception if the index is out of bounds + if (anIndex > numberOfRows) { + [NSException raise:NSRangeException format:@"Requested storage index (%llu) beyond bounds (%llu)", (unsigned long long)anIndex, numberOfRows]; + } - // If "inserting" at the end of the array just add a row - if (anIndex == numberOfRows) { - return [self addRowWithContents:aRow]; - } + // If "inserting" at the end of the array just add a row + if (anIndex == numberOfRows) { + return [self addRowWithContents:aRow]; + } - // Add the new row to the editable store - [editedRows insertPointer:aRow atIndex:anIndex]; - editedRowCount++; + // Add the new row to the editable store + [editedRows insertPointer:aRow atIndex:anIndex]; + editedRowCount++; - // Update the underlying store to keep counts and indices correct - [dataStorage insertDummyRowAtIndex:anIndex]; + // Update the underlying store to keep counts and indices correct + [dataStorage insertDummyRowAtIndex:anIndex]; + } } /** @@ -311,8 +347,10 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore */ - (void) replaceRowAtIndex:(NSUInteger)anIndex withRowContents:(NSMutableArray *)aRow { - [self _checkNewRow:aRow]; - [editedRows replacePointerAtIndex:anIndex withPointer:aRow]; + @synchronized(self) { + [self _checkNewRow:aRow]; + [editedRows replacePointerAtIndex:anIndex withPointer:aRow]; + } } /** @@ -320,16 +358,18 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore */ - (void) replaceObjectInRow:(NSUInteger)rowIndex column:(NSUInteger)columnIndex withObject:(id)anObject { - NSMutableArray *editableRow = NULL; + NSMutableArray *editableRow = nil; - if (rowIndex < editedRowCount) { - editableRow = SPDataStorageGetEditedRow(editedRows, rowIndex); - } + @synchronized(self) { + if (rowIndex < editedRowCount) { + editableRow = SPDataStorageGetEditedRow(editedRows, rowIndex); + } - // Make sure that the row in question is editable - if (editableRow == NULL) { - editableRow = [self rowContentsAtIndex:rowIndex]; - [editedRows replacePointerAtIndex:rowIndex withPointer:editableRow]; + // Make sure that the row in question is editable + if (editableRow == nil) { + editableRow = [self rowContentsAtIndex:rowIndex]; + [editedRows replacePointerAtIndex:rowIndex withPointer:editableRow]; + } } // Modify the cell @@ -341,18 +381,19 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore */ - (void) removeRowAtIndex:(NSUInteger)anIndex { + @synchronized(self) { + // Throw an exception if the index is out of bounds + if (anIndex >= SPMySQLResultStoreGetRowCount(dataStorage)) { + [NSException raise:NSRangeException format:@"Requested storage index (%llu) beyond bounds (%llu)", (unsigned long long)anIndex, SPMySQLResultStoreGetRowCount(dataStorage)]; + } - // Throw an exception if the index is out of bounds - if (anIndex >= SPMySQLResultStoreGetRowCount(dataStorage)) { - [NSException raise:NSRangeException format:@"Requested storage index (%llu) beyond bounds (%llu)", (unsigned long long)anIndex, SPMySQLResultStoreGetRowCount(dataStorage)]; - } - - // Remove the row from the edited list and underlying storage - if (anIndex < editedRowCount) { - editedRowCount--; - [editedRows removePointerAtIndex:anIndex]; + // Remove the row from the edited list and underlying storage + if (anIndex < editedRowCount) { + editedRowCount--; + [editedRows removePointerAtIndex:anIndex]; + } + [dataStorage removeRowAtIndex:anIndex]; } - [dataStorage removeRowAtIndex:anIndex]; } /** @@ -361,19 +402,20 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore */ - (void) removeRowsInRange:(NSRange)rangeToRemove { + @synchronized(self) { + // Throw an exception if the range is out of bounds + if (NSMaxRange(rangeToRemove) > SPMySQLResultStoreGetRowCount(dataStorage)) { + [NSException raise:NSRangeException format:@"Requested storage index (%llu) beyond bounds (%llu)", (unsigned long long)(NSMaxRange(rangeToRemove)), SPMySQLResultStoreGetRowCount(dataStorage)]; + } - // Throw an exception if the range is out of bounds - if (NSMaxRange(rangeToRemove) > SPMySQLResultStoreGetRowCount(dataStorage)) { - [NSException raise:NSRangeException format:@"Requested storage index (%llu) beyond bounds (%llu)", (unsigned long long)(NSMaxRange(rangeToRemove)), SPMySQLResultStoreGetRowCount(dataStorage)]; - } - - // Remove the rows from the edited list and underlying storage - NSUInteger i = MIN(editedRowCount, NSMaxRange(rangeToRemove)); - while (--i >= rangeToRemove.location) { - editedRowCount--; - [editedRows removePointerAtIndex:i]; + // Remove the rows from the edited list and underlying storage + NSUInteger i = MIN(editedRowCount, NSMaxRange(rangeToRemove)); + while (--i >= rangeToRemove.location) { + editedRowCount--; + [editedRows removePointerAtIndex:i]; + } + [dataStorage removeRowsInRange:rangeToRemove]; } - [dataStorage removeRowsInRange:rangeToRemove]; } /** @@ -381,9 +423,11 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore */ - (void) removeAllRows { - editedRowCount = 0; - [editedRows setCount:0]; - [dataStorage removeAllRows]; + @synchronized(self) { + editedRowCount = 0; + [editedRows setCount:0]; + [dataStorage removeAllRows]; + } } #pragma mark - Unloaded columns @@ -394,11 +438,13 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore */ - (void) setColumnAsUnloaded:(NSUInteger)columnIndex { - 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]; + @synchronized(self) { + 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]; + } + [self _assesUnloadedColumnsIsSet]; + unloadedColumns[columnIndex] = YES; } - NSAssert(unloadedColumns != NULL, @"unloadedColumns not loaded!"); - unloadedColumns[columnIndex] = YES; } #pragma mark - Basic information @@ -408,7 +454,9 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore */ - (NSUInteger) count { - return (NSUInteger)[dataStorage numberOfRows]; + @synchronized(self) { + return (NSUInteger)[dataStorage numberOfRows]; + } } /** @@ -416,7 +464,9 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore */ - (NSUInteger) columnCount { - return numberOfColumns; + @synchronized(self) { + return numberOfColumns; + } } /** @@ -424,7 +474,9 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore */ - (BOOL) dataDownloaded { - return !dataStorage || [dataStorage dataDownloaded]; + @synchronized(self) { + return !dataStorage || [dataStorage dataDownloaded]; + } } #pragma mark - Delegate callback methods @@ -434,8 +486,10 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore */ - (void)resultStoreDidFinishLoadingData:(SPMySQLStreamingResultStore *)resultStore { - [editedRows setCount:(NSUInteger)[resultStore numberOfRows]]; - editedRowCount = [editedRows count]; + @synchronized(self) { + [editedRows setCount:(NSUInteger)[resultStore numberOfRows]]; + editedRowCount = [editedRows count]; + } } /** @@ -443,7 +497,8 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore */ #pragma mark - -- (id) init { +- (id) init +{ if ((self = [super init])) { dataStorage = nil; editedRows = nil; @@ -451,15 +506,30 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore numberOfColumns = 0; editedRowCount = 0; + + _debugInfo = nil; + _debugTime = mach_absolute_time(); } return self; } -- (void) dealloc { - SPClear(dataStorage); - SPClear(editedRows); - if (unloadedColumns) free(unloadedColumns), unloadedColumns = NULL; - +- (void) dealloc +{ + @synchronized(self) { + SPClear(dataStorage); + SPClear(editedRows); + 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]; } @@ -467,6 +537,7 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore @implementation SPDataStorage (PrivateAPI) +// DO NOT CALL THIS METHOD UNLESS YOU CURRENTLY HAVE A LOCK ON SELF!!! - (void) _checkNewRow:(NSMutableArray *)aRow { if ([aRow count] != numberOfColumns) { @@ -474,5 +545,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); +} diff --git a/Source/SPDatabaseData.h b/Source/SPDatabaseData.h index aaf92f3d..cdc44fa3 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,8 @@ - (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 4856505d..965dfcb5 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,13 +119,18 @@ 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; } - while (c[0].nr != 0); + while (c->nr != 0); } } @@ -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,26 +173,72 @@ 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]; 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 && [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; +} + +/** 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; } /** @@ -283,11 +340,11 @@ NSInteger _sortStorageEngineEntry(NSDictionary *itemOne, NSDictionary *itemTwo, 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]; } @@ -296,16 +353,16 @@ 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", - [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); } } @@ -414,6 +471,9 @@ NSInteger _sortStorageEngineEntry(NSDictionary *itemOne, NSDictionary *itemTwo, [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.h b/Source/SPDatabaseDocument.h index a29f91cc..e36b4a58 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 @@ -358,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 @@ -515,6 +517,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 1793cf60..62fe2357 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -110,6 +110,7 @@ enum { #import "SPCharsetCollationHelper.h" #import "SPGotoDatabaseController.h" +#import "SPFunctions.h" #import <SPMySQL/SPMySQL.h> @@ -301,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]; @@ -544,6 +549,7 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; [tableDumpInstance setConnection:mySQLConnection]; #ifndef SP_CODA [exportControllerInstance setConnection:mySQLConnection]; + [exportControllerInstance setServerSupport:serverSupport]; #endif [tableDataInstance setConnection:mySQLConnection]; [extendedTableInfoInstance setConnection:mySQLConnection]; @@ -804,6 +810,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]; @@ -961,6 +969,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 /** @@ -1027,7 +1069,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 @@ -1081,12 +1123,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 @@ -1109,7 +1149,8 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; dbName = NSArrayObjectAtIndex(eachRow, 0); } - if(![dbName isNSNull]) { + // TODO: there have been crash reports because dbName == nil at this point. When could that happen? + if(dbName && ![dbName isNSNull]) { if(![dbName isEqualToString:selectedDatabase]) { if (selectedDatabase) SPClear(selectedDatabase); selectedDatabase = [[NSString alloc] initWithString:dbName]; @@ -1726,20 +1767,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; } } @@ -2535,10 +2576,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]] + ); } } @@ -3143,8 +3192,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 +3352,7 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; } // For dispatching later - if(![[spf objectForKey:@"format"] isEqualToString:@"connection"]) { + if(![[spf objectForKey:SPFFormatKey] isEqualToString:SPFConnectionContentType]) { NSLog(@"SPF file format is not 'connection'."); [spf release]; return NO; @@ -3352,8 +3401,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:SPFConnectionContentType forKey:SPFFormatKey]; [spfStructure setObject:@"mysql" forKey:@"rdbms_type"]; if([self mySQLVersion]) [spfStructure setObject:[self mySQLVersion] forKey:@"rdbms_version"]; @@ -4632,12 +4681,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; @@ -4654,15 +4708,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"]) { @@ -4773,7 +4832,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]; @@ -4817,12 +4876,12 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; } // If the .spf format is unhandled, error. - if (![[spf objectForKey:@"format"] isEqualToString:@"connection"]) { - NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Warning", @"warning")] + if (![[spf objectForKey:SPFFormatKey] isEqualToString:SPFConnectionContentType]) { + NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Unknown file format", @"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]; @@ -5038,21 +5097,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); @@ -5218,8 +5280,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; } @@ -5264,8 +5329,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; } @@ -5404,8 +5470,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"; @@ -5446,8 +5515,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") + ); } } @@ -5619,16 +5691,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 @@ -5932,7 +6008,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; } @@ -5947,9 +6023,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]; @@ -5964,7 +6042,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; } @@ -5979,9 +6057,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]; @@ -6007,7 +6087,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; } @@ -6023,7 +6103,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; } @@ -6061,7 +6141,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; } @@ -6160,7 +6240,6 @@ 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] ); @@ -6267,17 +6346,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]; } } } @@ -6289,6 +6369,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/SPDatabaseStructure.h b/Source/SPDatabaseStructure.h index b40dd449..05ae9ecc 100644 --- a/Source/SPDatabaseStructure.h +++ b/Source/SPDatabaseStructure.h @@ -51,12 +51,12 @@ // Setup and teardown - (id)initWithDelegate:(SPDatabaseDocument *)theDelegate; - (void)setConnectionToClone:(SPMySQLConnection *)aConnection; -- (void)destroy:(NSNotification *)notification; // Information - (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..b1bed3c9 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,45 +115,29 @@ 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 - #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. @@ -157,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]; @@ -234,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; } } @@ -268,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 @@ -305,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]; @@ -333,8 +275,7 @@ // Set up the database as an empty mutable dictionary ready for tables, and store a reference [queriedStructure setObject:[NSMutableDictionary dictionary] forKey:db_id]; NSMutableDictionary *databaseStructure = [queriedStructure objectForKey:db_id]; - - NSString *currentDatabaseEscaped = [currentDatabase stringByReplacingOccurrencesOfString:@"`" withString:@"``"]; + structureWasUpdated = YES; NSUInteger uniqueCounter = 0; // used to make field data unique SPMySQLResult *theResult; @@ -345,48 +286,16 @@ // 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; + // check the connection. + // also NO if thread is cancelled which is fine, too (same consequence). + if(![self _checkConnection]) { + goto cleanup_thread_and_pool; } - if (![self _ensureConnection]) { - pthread_mutex_unlock(&connectionCheckLock); - pthread_mutex_lock(&threadManagementLock); - [structureRetrievalThreads removeObject:[NSThread currentThread]]; - pthread_mutex_unlock(&threadManagementLock); - - [queryPool release]; - return; - } - 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; } @@ -398,8 +307,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) { @@ -437,34 +346,10 @@ // 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. + // also NO if thread is cancelled which is fine, too (same consequence). + 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) @@ -499,17 +384,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]; } @@ -566,7 +453,7 @@ { [[NSNotificationCenter defaultCenter] removeObserver:self]; - [self destroy:nil]; + [self _destroy:nil]; SPClear(structureRetrievalThreads); pthread_mutex_destroy(&threadManagementLock); @@ -588,6 +475,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. */ - (void)_updateGlobalVariablesWithStructure:(NSDictionary *)aStructure keys:(NSArray *)theKeys @@ -617,20 +515,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 @@ -640,19 +527,31 @@ [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! + * + * WARNING: This method may return NO if the current thread is cancelled! + * You MUST check the isCancelled flag before using the result! + */ +- (BOOL)_ensureConnectionUnsafe { if (!mySQLConnection || !delegate) return NO; // Check the connection state if ([mySQLConnection isConnected] && [mySQLConnection checkConnection]) return YES; + + // the result of checkConnection may be meaningless if the thread was cancelled during execution. (issue #2353) + if([[NSThread currentThread] isCancelled]) return NO; // The connection isn't connected. Check the parent connection state, and if that // also isn't connected, return. @@ -671,4 +570,112 @@ 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 + * + * You MUST check the thread's isCancelled flag before doing other stuff on negative return! + */ +- (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]; // also does a thread canellation check + } + + pthread_mutex_unlock(&connectionCheckLock); + + return connected; // cancelThread → ¬connected +} + @end 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/SPDotExporter.m b/Source/SPDotExporter.m index 5f1dbd42..1353dbaf 100644 --- a/Source/SPDotExporter.m +++ b/Source/SPDotExporter.m @@ -28,6 +28,7 @@ // // More info at <https://github.com/sequelpro/sequelpro> +#import <SPMySQL/SPMySQL.h> #import "SPDotExporter.h" #import "SPFileHandle.h" #import "SPTableData.h" @@ -64,19 +65,12 @@ return self; } -/** - * Start the Dot schema export process. This method is automatically called when an instance of this class - * is placed on an NSOperationQueue. Do not call it directly as there is no manual multithreading. - */ -- (void)main +- (void)exportOperation { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSMutableString *metaString = [NSMutableString string]; // Check that we have all the required info before starting the export if ((![self dotExportTables]) || (![self dotTableData]) || ([[self dotExportTables] count] == 0)) { - [pool release]; return; } @@ -85,6 +79,9 @@ // Mark the process as running [self setExportProcessIsRunning:YES]; + + // we require utf8 + [connection setEncoding:@"utf8"]; [metaString setString:@"// ************************************************************\n"]; [metaString appendString:@"// Generated by: Sequel Pro\n"]; @@ -106,7 +103,7 @@ [metaString appendString:@"\trankdir = LR;\n"]; // Write information to the file - [[self exportOutputFile] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; + [self writeUTF8String:metaString]; NSMutableArray *fkInfo = [[NSMutableArray alloc] init]; @@ -116,7 +113,6 @@ // Check for cancellation flag if ([self isCancelled]) { [fkInfo release]; - [pool release]; return; } @@ -154,9 +150,9 @@ [metaString appendString:@"\t\t\t</TABLE>>\n"]; [metaString appendString:@"\t\t];\n"]; [metaString appendString:@"\t}\n"]; - - [[self exportOutputFile] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; - + + [self writeUTF8String:metaString]; + // Check if any relations are available for the table NSArray *tableConstraints = [tableInfo objectForKey:@"constraints"]; @@ -167,7 +163,6 @@ // Check for cancellation flag if ([self isCancelled]) { [fkInfo release]; - [pool release]; return; } @@ -211,7 +206,7 @@ [metaString appendString:@"}\n"]; // Write information to the file - [[self exportOutputFile] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; + [self writeUTF8String:metaString]; // Write data to disk [[self exportOutputFile] close]; @@ -221,8 +216,6 @@ // Inform the delegate that the export process is complete [delegate performSelectorOnMainThread:@selector(dotExportProcessComplete:) withObject:self waitUntilDone:NO]; - - [pool release]; } #pragma mark - diff --git a/Source/SPDotExporterDelegate.m b/Source/SPDotExporterDelegate.m index b5cc4e5e..47054a1e 100644 --- a/Source/SPDotExporterDelegate.m +++ b/Source/SPDotExporterDelegate.m @@ -30,33 +30,24 @@ #import "SPDotExporterDelegate.h" #import "SPDotExporter.h" -#import "SPDatabaseDocument.h" +#import "SPExportInitializer.h" @implementation SPExportController (SPDotExporterDelegate) - (void)dotExportProcessWillBegin:(SPDotExporter *)exporter { - [[exportProgressTitle onMainThread] setStringValue:NSLocalizedString(@"Exporting Dot File", @"text showing that the application is exporting a Dot file")]; - [[exportProgressText onMainThread] setStringValue:NSLocalizedString(@"Dumping...", @"text showing that app is writing dump")]; - - [[exportProgressTitle onMainThread] displayIfNeeded]; - [[exportProgressText onMainThread] displayIfNeeded]; - [[exportProgressIndicator onMainThread] stopAnimation:self]; - [[exportProgressIndicator onMainThread] setIndeterminate:NO]; + [exportProgressTitle setStringValue:NSLocalizedString(@"Exporting Dot File", @"text showing that the application is exporting a Dot file")]; + [exportProgressText setStringValue:NSLocalizedString(@"Dumping...", @"text showing that app is writing dump")]; + + [exportProgressTitle displayIfNeeded]; + [exportProgressText displayIfNeeded]; + [exportProgressIndicator stopAnimation:self]; + [exportProgressIndicator setIndeterminate:NO]; } - (void)dotExportProcessComplete:(SPDotExporter *)exporter { - [NSApp endSheet:exportProgressWindow returnCode:0]; - [exportProgressWindow orderOut:self]; - - [tableDocumentInstance setQueryMode:SPInterfaceQueryMode]; - - // Restore the connection encoding to it's pre-export value - [tableDocumentInstance setConnectionEncoding:[NSString stringWithFormat:@"%@%@", previousConnectionEncoding, (previousConnectionEncodingViaLatin1) ? @"-" : @""] reloadingViews:NO]; - - // Display Growl notification - [self displayExportFinishedGrowlNotification]; + [self exportEnded]; } - (void)dotExportProcessProgressUpdated:(SPDotExporter *)exporter @@ -76,12 +67,12 @@ - (void)dotExportProcessWillBeginFetchingRelationsData:(SPDotExporter *)exporter { - [[exportProgressText onMainThread] setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Fetching relations data...", @"export label showing app is fetching relations data for a specific table"), currentTableExportIndex, exportTableCount, [exporter dotExportCurrentTable]]]; + [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Fetching relations data...", @"export label showing app is fetching relations data for a specific table"), currentTableExportIndex, exportTableCount, [exporter dotExportCurrentTable]]]; - [[exportProgressText onMainThread] displayIfNeeded]; - [[exportProgressIndicator onMainThread] setIndeterminate:YES]; - [[exportProgressIndicator onMainThread] setUsesThreadedAnimation:YES]; - [[exportProgressIndicator onMainThread] startAnimation:self]; + [exportProgressText displayIfNeeded]; + [exportProgressIndicator setIndeterminate:YES]; + [exportProgressIndicator setUsesThreadedAnimation:YES]; + [exportProgressIndicator startAnimation:self]; } @end diff --git a/Source/SPEditorTokens.l b/Source/SPEditorTokens.l index 2ac99be8..d3fac252 100644 --- a/Source/SPEditorTokens.l +++ b/Source/SPEditorTokens.l @@ -61,7 +61,7 @@ numeric ([+-]?(([0-9]+\.[0-9]+)|([0-9]*\.[0-9]+)|([0-9]+))(e[+-]?[0-9]+)?) ops "+"|"-"|"*"|"/" word [a-z_\.0-9\x80-\xEF@] variable @{1,2}[a-z_\.0-9\x80-\xEF$]+ -keyworda (G(R(OUP{s}BY|ANT(S)?)|E(NERAL|T_FORMAT|OMETRY(COLLECTION)?)|LOBAL)|B(Y(TE)?|TREE|I(GINT|N(LOG|ARY)|T)|O(TH|OL(EAN)?)|E(GIN|TWEEN|FORE)|LOB|ACKUP{s}TABLE)|H(IGH_PRIORITY|O(ST(S)?|UR(_(MI(NUTE|CROSECOND)|SECOND))?)|ELP|A(SH|NDLER|VING))|C(R(OSS|EATE)|H(ECK(SUM)?|A(R(SET|ACTER)?|NGE(D)?|IN))|IPHER|O(M(M(IT(TED)?|ENT)|P(RESSED|LETION|ACT))|N(S(TRAINT(_(SCHEMA|NAME|CATALOG))?|ISTENT)|NECTION|CURRENT|T(RIBUTORS|INUE|AINS)|DITION|VERT)|DE|L(UMN(S|_(NAME|FORMAT))?|LATE)|ALESCE{s}PARTITION)|U(R(RENT_(TIME(STAMP)?|DATE|USER)|SOR(_NAME)?)|BE)|L(IENT|OSE|ASS_ORIGIN)|A(S(CADE(D)?|E)|CHE{s}INDEX|TALOG_NAME|LL))|I(GNORE(_SERVER_IDS)?|MPORT{s}TABLESPACE|S(SUER|OLATION)?|N(S(TALL({s}PLUGIN)?|E(RT(_METHOD)?|NSITIVE))|N(O(BASE|DB)|ER)|T(1|2|8|3|O({s}(DUMP|OUT)FILE)?|4|E(RVAL|GER))?|ITIAL_SIZE|OUT|DEX(ES)?|VOKER|FILE)?|TERATE|O_THREAD|DENTIFIED|F)|D(ROP|YNAMIC|I(RECTORY|S(CARD{s}TABLESPACE|TINCT(ROW)?|K|ABLE{s}KEYS)|V)|O(UBLE)?|U(MPFILE|PLICATE|AL)|E(S(C(RIBE)?|_KEY_FILE)|C(IMAL|LARE)?|TERMINISTIC|F(INER|AULT)|L(ETE|AY(_KEY_WRITE|ED))|ALLOCATE)|A(Y(_(MI(NUTE|CROSECOND)|SECOND|HOUR))?|T(E(TIME)?|A(BASE(S)?|FILE)?)))|JOIN|E(RRORS|X(TEN(T_SIZE|DED)|I(STS|T)|P(LAIN|ANSION)|ECUTE)|SCAPE(D{s}BY)?|N(GINE(S)?|CLOSED{s}BY|D(S)?|UM|ABLE{s}KEYS)|VE(RY|NT)|LSE(IF)?|ACH)|K(ILL({s}(CONNECTION|QUERY))?|EY(S|_BLOCK_SIZE)?)|F(R(OM|AC_SECOND)|I(RST|XED|LE)|O(R(CE|EIGN)?|UND)|U(NCTION|LL(TEXT)?)|ETCH|L(OAT(8|4)?|USH)|A(ST|LSE))|A(G(GREGATE|AINST)|S(C(II)?|ENSITIVE)?|N(Y|D|ALYZE)|C(CESSIBLE|TION)|T|DD|UT(HORS|O(_INCREMENT|EXTEND_SIZE))|VG(_ROW_LENGTH)?|FTER|L(GORITHM|TER|L))) +keyworda (G(R(OUP{s}BY|ANT(S)?)|E(NERAL|T_FORMAT|OMETRY(COLLECTION)?)|LOBAL)|B(Y(TE)?|TREE|I(GINT|N(LOG|ARY)|T)|O(TH|OL(EAN)?)|E(GIN|TWEEN|FORE)|LOB|ACKUP{s}TABLE)|H(IGH_PRIORITY|O(ST(S)?|UR(_(MI(NUTE|CROSECOND)|SECOND))?)|ELP|A(SH|NDLER|VING))|C(R(OSS|EATE)|H(ECK(SUM)?|A(R(SET|ACTER)?|NGE(D)?|IN))|IPHER|O(M(M(IT(TED)?|ENT)|P(RESSED|LETION|ACT))|N(S(TRAINT(_(SCHEMA|NAME|CATALOG))?|ISTENT)|NECTION|CURRENT|T(RIBUTORS|INUE|AINS)|DITION|VERT)|DE|L(UMN(S|_(NAME|FORMAT))?|LATE)|ALESCE{s}PARTITION)|U(R(RENT_(TIME(STAMP)?|DATE|USER)|SOR(_NAME)?)|BE)|L(IENT|OSE|ASS_ORIGIN)|A(S(CADE(D)?|E)|CHE{s}INDEX|TALOG_NAME|LL))|I(GNORE(_SERVER_IDS)?|MPORT{s}TABLESPACE|S(SUER|OLATION)?|N(S(TALL({s}PLUGIN)?|E(RT(_METHOD)?|NSITIVE))|N(O(BASE|DB)|ER)|T(1|2|8|3|O({s}(DUMP|OUT)FILE)?|4|E(RVAL|GER))?|ITIAL_SIZE|OUT|DEX(ES)?|VOKER|FILE)?|TERATE|O_THREAD|DENTIFIED|F)|D(ROP|YNAMIC|I(RECTORY|S(CARD{s}TABLESPACE|TINCT(ROW)?|K|ABLE{s}KEYS)|V)|O(UBLE)?|U(MPFILE|PLICATE|AL)|E(S(C(RIBE)?|_KEY_FILE)|C(IMAL|LARE)?|TERMINISTIC|F(INER|AULT)|L(ETE|AY(_KEY_WRITE|ED))|ALLOCATE)|A(Y(_(MI(NUTE|CROSECOND)|SECOND|HOUR))?|T(E(TIME)?|A(BASE(S)?|FILE)?)))|J(OI|SO)N|E(RRORS|X(TEN(T_SIZE|DED)|I(STS|T)|P(LAIN|ANSION)|ECUTE)|SCAPE(D{s}BY)?|N(GINE(S)?|CLOSED{s}BY|D(S)?|UM|ABLE{s}KEYS)|VE(RY|NT)|LSE(IF)?|ACH)|K(ILL({s}(CONNECTION|QUERY))?|EY(S|_BLOCK_SIZE)?)|F(R(OM|AC_SECOND)|I(RST|XED|LE)|O(R(CE|EIGN)?|UND)|U(NCTION|LL(TEXT)?)|ETCH|L(OAT(8|4)?|USH)|A(ST|LSE))|A(G(GREGATE|AINST)|S(C(II)?|ENSITIVE)?|N(Y|D|ALYZE)|C(CESSIBLE|TION)|T|DD|UT(HORS|O(_INCREMENT|EXTEND_SIZE))|VG(_ROW_LENGTH)?|FTER|L(GORITHM|TER|L))) keywordl (R(TREE|IGHT|O(UTINE|W(S|_FORMAT)?|LL(BACK|UP))|E(GEXP|MOVE{s}PARTITIONING|BUILD{s}PARTITION|S(T(RICT|ORE{s}TABLE)|IGNAL|UME|ET)|NAME|COVER|TURN(S)?|ORGANIZE{s}PARTITION|D(O(_BUFFER_SIZE|FILE)|UNDANT)|P(EAT(ABLE)?|L(ICATION|ACE)|AIR)|VOKE|QUIRE|FERENCES|L(OAD|EASE|AY_(THREAD|LOG_(POS|FILE)))|A(D(S|_(ONLY|WRITE))?|L))|LIKE|ANGE)|M(YSQL_ERRNO|I(GRATE|N(_ROWS|UTE(_(MICROSECOND|SECOND))?)|CROSECOND|DDLEINT)|O(NTH|D(IF(Y|IES)|E)?)|U(TEX|LTI(PO(INT|LYGON)|LINESTRING))|E(RGE|MORY|SSAGE_TEXT|DIUM(BLOB|TEXT|INT)?)|A(X(_(ROWS|SIZE|CONNECTIONS_PER_HOUR|U(SER_CONNECTIONS|PDATES_PER_HOUR)|QUERIES_PER_HOUR)|VALUE)|STER(_(S(SL(_(C(IPHER|ERT|A(PATH)?)|VERIFY_SERVER_CERT|KEY))?|ERVER_ID)|H(OST|EARTBEAT_PERIOD)|CONNECT_RETRY|USER|P(ORT|ASSWORD)|LOG_(POS|FILE)))?|TCH))|N(CHAR|O(NE|_W(RITE_TO_BINLOG|AIT)|T|DEGROUP)?|DB(CLUSTER)?|U(MERIC|LL)|E(XT|W)|VARCHAR|A(ME(S)?|T(IONAL|URAL)))|O(R(DER{s}BY)?|N(({s}DUPLICATE{s}KEY{s}UPDATE)?|E(_SHOT)?|LINE)?|UT(ER|FILE)?|P(TI(MIZE|ON(S|ALLY)?)|EN)|FF(SET|LINE)|WNER|LD_PASSWORD)|P(R(I(MARY|VILEGES)|OCE(SS|DURE{s}(ANALYSE)?)|E(SERVE|CISION|PARE|V))|HASE|O(RT|INT|LYGON)|URGE|A(R(SER|TI(TION(S|ING)?|AL))|SSWORD|CK_KEYS))|QU(ICK|ERY|ARTER)|L(I(MIT|ST|NE(S(TRING)?|AR)|KE)|O(G(S|FILE({s}GROUP))|NG(BLOB|TEXT)?|C(K(S)?|AL(TIME(STAMP)?)?)|OP|W_PRIORITY|AD{s}(DATA|INDEX{s}INTO{s}CACHE|XML))|E(SS|VEL|FT|A(DING|VE(S)?))|A(ST|NGUAGE))) keywords (X(OR|509|A)|S(MALLINT|SL|H(OW({s}(E(NGINE(S)?|RRORS)|M(ASTER|UTEX)|BINLOG|GRANTS|INNODB|P(RIVILEGES|ROFILE(S)?|ROCEDURE{s}CODE)|SLAVE{s}(HOSTS|STATUS)|TRIGGERS|VARIABLES|WARNINGS|(FULL{s})?PROCESSLIST|FIELDS|PLUGIN(S)?|STORAGE{s}ENGINES|TABLE{s}TYPES|CO(LUMNS|LLATION)|BINLOG{s}EVENTS))?|UTDOWN|ARE)|NAPSHOT|CHE(MA(S|_NAME)?|DULE(R)?)|T(R(ING|AIGHT_JOIN)|O(RAGE|P)|A(RT(S|ING{s}BY)?|TUS))|I(GN(ED|AL)|MPLE)|O(ME|NAME|CKET|UNDS)|U(B(CLASS_ORIGIN|JECT|PARTITION(S)?)|SPEND|PER)|P(ECIFIC|ATIAL)|E(R(IAL(IZABLE)?|VER)|SSION|NSITIVE|C(OND(_MICROSECOND)?|URITY)|T({s}(PASSWORD|NAMES|ONE_SHOT))?|PARATOR|LECT)|QL(STATE|_(MAX_JOIN_SIZE|B(IG_(RESULT|SELECTS|TABLES)|UFFER_RESULT)|S(MALL_RESULT|ELECT_LIMIT|LAVE_SKIP_COUNTER|AFE_UPDATES)|NO_CACHE|CA(CHE|LC_FOUND_ROWS)|T(SI_(M(INUTE|ONTH)|SECOND|HOUR|YEAR|DAY|QUARTER|FRAC_SECOND|WEEK)|HREAD)|QUOTE_SHOW_CREATE|WARNINGS|LO(G_(BIN|OFF|UPDATE)|W_PRIORITY_UPDATES)|AUTO_IS_NULL)|EXCEPTION|WARNING)?|L(OW|AVE)|AVEPOINT)|YEAR(_MONTH)?|T(R(IGGER(S)?|U(NCATE|E)|A(NSACTION|ILING))|H(EN|AN)|YPE|I(ME(STAMP(DIFF|ADD)?)?|NY(BLOB|TEXT|INT))|O|E(RMINATED{s}BY|XT|MP(TABLE|ORARY))|ABLE(S(PACE)?|_NAME)?)|ZEROFILL|U(S(ING|E(R(_RESOURCES)?|_FRM)?|AGE)|N(SIGNED|COMMITTED|TIL|I(NSTALL({s}PLUGIN)?|CODE|ON|QUE)|D(O(_BUFFER_SIZE|FILE)?|EFINED)|KNOWN|LOCK)|TC_(TIME(STAMP)?|DATE)|P(GRADE|DATE))|V(IEW|A(R(BINARY|YING|CHAR(ACTER)?|IABLES)|LUE(S)?))|W(R(ITE|APPER)|H(ILE|E(RE|N))|ITH({s}PARSER)?|ORK|EEK|A(RNINGS|IT))) @@ -348,6 +348,7 @@ ISOLATION ISSUER ITERATE JOIN +JSON KEY KEYS KEY_BLOCK_SIZE diff --git a/Source/SPExportController+SharedPrivateAPI.h b/Source/SPExportController+SharedPrivateAPI.h index 067c4645..d8382824 100644 --- a/Source/SPExportController+SharedPrivateAPI.h +++ b/Source/SPExportController+SharedPrivateAPI.h @@ -32,4 +32,5 @@ @interface SPExportController (SharedPrivateAPI) - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo; +- (void)_hideExportProgress; @end diff --git a/Source/SPExportController.h b/Source/SPExportController.h index 188642f4..bef547d8 100644 --- a/Source/SPExportController.h +++ b/Source/SPExportController.h @@ -33,7 +33,8 @@ @class SPCustomQuery; @class SPTablesList; @class SPTableData; -@class SPMySQLConnection; +@class SPMySQLConnection; +@class SPServerSupport; /** * @class SPExportController SPExportController.h @@ -92,7 +93,7 @@ IBOutlet NSButton *exportCustomFilenameViewLabelButton; IBOutlet NSView *exportCustomFilenameView; IBOutlet NSTokenField *exportCustomFilenameTokenField; - IBOutlet NSTokenField *exportCustomFilenameTokensField; + IBOutlet NSTokenField *exportCustomFilenameTokenPool; // SQL IBOutlet NSButton *exportSQLIncludeStructureCheck; @@ -170,6 +171,7 @@ * Database connection */ SPMySQLConnection *connection; + SPServerSupport *serverSupport; /** * Concurrent operation queue @@ -230,6 +232,9 @@ NSInteger heightOffset2; NSUInteger windowMinWidth; NSUInteger windowMinHeigth; + + NSDictionary *localizedTokenNames; + } /** @@ -246,11 +251,20 @@ * @property connection Database connection */ @property(readwrite, assign) SPMySQLConnection *connection; +@property(readwrite, assign) SPServerSupport *serverSupport; - (void)exportTables:(NSArray *)table asFormat:(SPExportType)format usingSource:(SPExportSource)source; - (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 97840171..0cccabb5 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 <SPMySQL/SPMySQL.h> @@ -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; @@ -72,14 +72,15 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; - (void)_resizeWindowForCustomFilenameViewByHeightDelta:(NSInteger)delta; - (void)_resizeWindowForAdvancedOptionsViewByHeightDelta:(NSInteger)delta; -- (void)_waitUntilQueueIsEmpty:(id)sender; -- (void)_queueIsEmpty:(id)sender; +- (void)_waitUntilQueueIsEmptyAfterCancelling:(id)sender; +- (void)_queueIsEmptyAfterCancelling:(id)sender; @end @implementation SPExportController @synthesize connection; +@synthesize serverSupport = serverSupport; @synthesize exportToMultipleFiles; @synthesize exportCancelled; @@ -120,10 +121,20 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; heightOffset1 = 0; heightOffset2 = 0; - windowMinWidth = [[self window] minSize].width; - 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; @@ -138,13 +149,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]; @@ -174,21 +189,32 @@ 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()]; + } + + // initially popuplate the tables list + [self refreshTableList:nil]; - [self _setPreviousExportFilenameAndPath]; + // 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]; - // Select the 'selected tables' source option - [exportInputPopUpButton selectItemAtIndex:source]; - // If tables were supplied, select them if (exportTables) { @@ -219,6 +245,7 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; // Ensure interface validation [self _switchTab]; [self _updateExportAdvancedOptionsLabel]; + [self setExportInput:source]; [NSApp beginSheet:[self window] modalForWindow:[tableDocumentInstance parentWindow] @@ -271,7 +298,7 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; */ - (IBAction)export:(id)sender { - SPExportType selectedExportType = SPSQLExport; + SPExportType selectedExportType = SPAnyExportType; SPExportSource selectedExportSource = SPTableExport; NSArray *selectedTables = [tablesListInstance selectedTableItems]; @@ -294,9 +321,6 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; } [self exportTables:selectedTables asFormat:selectedExportType usingSource:selectedExportSource]; - - // Ensure UI validation - [self switchInput:exportInputPopUpButton]; } /** @@ -325,27 +349,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]]; } /** @@ -368,18 +415,18 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; // Cancel all of the currently running operations [operationQueue cancelAllOperations]; // async call - [NSThread detachNewThreadWithName:SPCtxt(@"SPExportController cancelExport: waiting for empty queue", tableDocumentInstance) target:self selector:@selector(_waitUntilQueueIsEmpty:) object:sender]; + [NSThread detachNewThreadWithName:SPCtxt(@"SPExportController cancelExport: waiting for empty queue", tableDocumentInstance) target:self selector:@selector(_waitUntilQueueIsEmptyAfterCancelling:) object:sender]; } -- (void)_waitUntilQueueIsEmpty:(id)sender +- (void)_waitUntilQueueIsEmptyAfterCancelling:(id)sender { [sender retain]; [operationQueue waitUntilAllOperationsAreFinished]; - [self performSelectorOnMainThread:@selector(_queueIsEmpty:) withObject:sender waitUntilDone:NO]; + [self performSelectorOnMainThread:@selector(_queueIsEmptyAfterCancelling:) withObject:sender waitUntilDone:NO]; [sender release]; } -- (void)_queueIsEmpty:(id)sender +- (void)_queueIsEmptyAfterCancelling:(id)sender { // Loop the cached export file paths and remove them from disk if they exist for (SPExportFile *file in exportFiles) @@ -387,14 +434,11 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; [file delete]; } - // Close the progress sheet - [NSApp endSheet:exportProgressWindow returnCode:0]; - [exportProgressWindow orderOut:self]; - - // Stop the progress indicator - [exportProgressIndicator stopAnimation:self]; - [exportProgressIndicator setUsesThreadedAnimation:NO]; - + [self _hideExportProgress]; + + // Restore the connection encoding to it's pre-export value + [tableDocumentInstance setConnectionEncoding:[NSString stringWithFormat:@"%@%@", previousConnectionEncoding, (previousConnectionEncodingViaLatin1) ? @"-" : @""] reloadingViews:NO]; + // Re-enable the cancel button for future exports [sender setEnabled:YES]; @@ -403,6 +447,17 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; [exporters removeAllObjects]; } +- (void)_hideExportProgress +{ + // Close the progress sheet + [NSApp endSheet:exportProgressWindow returnCode:0]; + [exportProgressWindow orderOut:self]; + + // Stop the progress indicator + [exportProgressIndicator stopAnimation:self]; + [exportProgressIndicator setUsesThreadedAnimation:NO]; +} + /** * Opens the open panel when user selects to change the output path. */ @@ -416,9 +471,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]; + if (returnCode == NSFileHandlingPanelOKButton) { + 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]; } }]; } @@ -655,9 +715,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 - @@ -670,24 +727,8 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; { // 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:SPExportFilenameFormat]; - } - 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:SPExportFilenameFormat]; - } + + [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 @@ -752,11 +793,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)]; @@ -810,9 +851,6 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; [exportDotForceLowerTableNamesCheck setState:(serverLowerCaseTableNameValue == 0)?NSOffState:NSOnState]; } - - [exportCSVNULLValuesAsTextField setStringValue:[prefs stringForKey:SPNullValue]]; - [exportXMLNULLValuesAsTextField setStringValue:[prefs stringForKey:SPNullValue]]; [self _displayExportTypeOptions:(isSQL || isCSV || isXML || isDot)]; [self updateAvailableExportFilenameTokens]; @@ -843,7 +881,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 { @@ -940,28 +978,6 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; } /** - * Sets the previous export filename and path if available. - */ -- (void)_setPreviousExportFilenameAndPath -{ - // Restore the export filename if it exists, and update the display - if ([prefs objectForKey:SPExportFilenameFormat]) { - [exportCustomFilenameTokenField setObjectValue:[NSKeyedUnarchiver unarchiveObjectWithData:[prefs objectForKey:SPExportFilenameFormat]]]; - } - - // If a directory has previously been selected, reselect it - if ([prefs objectForKey:SPExportLastDirectory]) { - [exportPathField setStringValue:[prefs objectForKey:SPExportLastDirectory]]; - } - 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. * * @param uiStateDict A dictionary containing the state of various UI controls. @@ -1065,8 +1081,9 @@ static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; SPClear(exportFiles); SPClear(operationQueue); SPClear(exportFilename); - - if (previousConnectionEncoding) SPClear(previousConnectionEncoding); + SPClear(localizedTokenNames); + SPClear(previousConnectionEncoding); + [self setServerSupport:nil]; [super dealloc]; } diff --git a/Source/SPExportControllerDelegate.m b/Source/SPExportControllerDelegate.m index 43b1804f..0f18ef25 100644 --- a/Source/SPExportControllerDelegate.m +++ b/Source/SPExportControllerDelegate.m @@ -32,6 +32,9 @@ #import "SPExportFilenameUtilities.h" #import "SPExportFileNameTokenObject.h" +static inline BOOL IS_TOKEN(id x); +static inline BOOL IS_STRING(id x); + // 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 stringByReplacingCharactersInSet:[NSCharacterSet newlineCharacterSet] withString:@" "]]; } + + 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,142 @@ } } +#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 + +#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/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/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<NSCoding> { - 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/SPExportFileUtilities.m b/Source/SPExportFileUtilities.m index 0cecad91..23fb2f4a 100644 --- a/Source/SPExportFileUtilities.m +++ b/Source/SPExportFileUtilities.m @@ -102,7 +102,7 @@ SPExportErrorChoice; { NSMutableString *header = [NSMutableString string]; - [header setString:@"<?xml version=\"1.0\"?>\n\n"]; + [header setString:@"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n\n"]; [header appendString:@"<!--\n-\n"]; [header appendString:@"- Sequel Pro XML dump\n"]; [header appendFormat:@"- %@ %@\n-\n", NSLocalizedString(@"Version", @"export header version label"), [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]]; @@ -129,7 +129,7 @@ SPExportErrorChoice; [header appendFormat:@"<%@>\n\n", [[tableDocumentInstance database] HTMLEscapeString]]; } - [file writeData:[header dataUsingEncoding:NSUTF8StringEncoding]]; + [file writeData:[header dataUsingEncoding:NSUTF8StringEncoding]]; } /** @@ -269,9 +269,7 @@ SPExportErrorChoice; } } - // Close the progress sheet - [NSApp endSheet:exportProgressWindow returnCode:0]; - [exportProgressWindow orderOut:self]; + [self _hideExportProgress]; [alert beginSheetModalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:files]; [alert autorelease]; diff --git a/Source/SPExportFilenameUtilities.h b/Source/SPExportFilenameUtilities.h index 7483316e..b0ba9763 100644 --- a/Source/SPExportFilenameUtilities.h +++ b/Source/SPExportFilenameUtilities.h @@ -41,10 +41,11 @@ - (void)updateDisplayedExportFilename; - (void)updateAvailableExportFilenameTokens; -- (id)tokenObjectForString:(NSString *)stringToTokenize; -- (void)tokenizeCustomFilenameTokenField; +- (NSArray *)currentAllowedExportFilenameTokens; - (NSString *)generateDefaultExportFilename; - (NSString *)currentDefaultExportFileExtension; - (NSString *)expandCustomFilenameFormatUsingTableName:(NSString *)table; +- (NSString *)customFilenamePathExtension; +- (BOOL)isTableTokenAllowed; @end diff --git a/Source/SPExportFilenameUtilities.m b/Source/SPExportFilenameUtilities.m index a65b8e53..69b8f786 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,60 @@ } } - 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; - } - } - } - } - - [exportCustomFilenameTokensField 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 ([[exportCustomFilenameTokensField 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; + [exportCustomFilenameTokenPool setObjectValue:exportTokens]; + //update preview name as programmatically changing the exportCustomFilenameTokenField does not fire a notification + [self updateDisplayedExportFilename]; +} - // 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; +- (NSArray *)currentAllowedExportFilenameTokens +{ + NSArray *mixed = [exportCustomFilenameTokenPool objectValue]; + NSMutableArray *tokens = [NSMutableArray arrayWithCapacity:[mixed count]]; // ...or less - 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; - } + for (id obj in mixed) { + if([obj isKindOfClass:[SPExportFileNameTokenObject class]]) [tokens addObject:obj]; } - - // 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]; + return tokens; } /** @@ -343,43 +260,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]]; } } diff --git a/Source/SPExportInitializer.h b/Source/SPExportInitializer.h index d6fc269d..e8cfec73 100644 --- a/Source/SPExportInitializer.h +++ b/Source/SPExportInitializer.h @@ -43,6 +43,7 @@ @interface SPExportController (SPExportInitializer) - (void)startExport; +- (void)exportEnded; - (void)initializeExportUsingSelectedOptions; - (void)exportTables:(NSArray *)exportTables orDataArray:(NSArray *)dataArray; diff --git a/Source/SPExportInitializer.m b/Source/SPExportInitializer.m index 7794d1e4..15e9de66 100644 --- a/Source/SPExportInitializer.m +++ b/Source/SPExportInitializer.m @@ -46,6 +46,7 @@ #import "SPExportFilenameUtilities.h" #import "SPExportFileNameTokenObject.h" #import "SPConnectionControllerDelegateProtocol.h" +#import "SPExportController+SharedPrivateAPI.h" #import <SPMySQL/SPMySQL.h> @@ -65,7 +66,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] @@ -74,14 +75,9 @@ contextInfo:nil]; } - // If using an export type that requires the connection to start in UTF8, cache the current connection - // encoding and then set it here. - if (exportType == SPSQLExport || exportType == SPDotExport) { - previousConnectionEncoding = [[NSString alloc] initWithString:[connection encoding]]; - previousConnectionEncodingViaLatin1 = [connection encodingUsesLatin1Transport]; - - [tableDocumentInstance setConnectionEncoding:@"utf8" reloadingViews:NO]; - } + // cache the current connection encoding so the exporter can do what it wants. + previousConnectionEncoding = [[NSString alloc] initWithString:[connection encoding]]; + previousConnectionEncodingViaLatin1 = [connection encodingUsesLatin1Transport]; // Add the first exporter to the operation queue [operationQueue addOperation:[exporters objectAtIndex:0]]; @@ -92,6 +88,23 @@ } /** + * @see _queueIsEmptyAfterCancelling: + */ +- (void)exportEnded +{ + [self _hideExportProgress]; + + // Restore query mode + [tableDocumentInstance setQueryMode:SPInterfaceQueryMode]; + + // Display Growl notification + [self displayExportFinishedGrowlNotification]; + + // Restore the connection encoding to it's pre-export value + [tableDocumentInstance setConnectionEncoding:[NSString stringWithFormat:@"%@%@", previousConnectionEncoding, (previousConnectionEncodingViaLatin1) ? @"-" : @""] reloadingViews:NO]; +} + +/** * Initializes the export process by analysing the selected criteria. */ - (void)initializeExportUsingSelectedOptions @@ -304,7 +317,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]) { @@ -425,9 +439,10 @@ for (SPExporter *exporter in exporters) { [exporter setConnection:connection]; + [exporter setServerSupport:[self serverSupport]]; [exporter setExportOutputEncoding:[connection stringEncoding]]; [exporter setExportMaxProgress:(NSInteger)[exportProgressIndicator bounds].size.width]; - [exporter setExportUsingLowMemoryBlockingStreaming:[exportProcessLowMemoryButton state]]; + [exporter setExportUsingLowMemoryBlockingStreaming:([exportProcessLowMemoryButton state] == NSOnState)]; [exporter setExportOutputCompressionFormat:(SPFileCompressionFormat)[exportOutputCompressionFormatPopupButton indexOfSelectedItem]]; [exporter setExportOutputCompressFile:([exportOutputCompressionFormatPopupButton indexOfSelectedItem] != SPNoCompression)]; } @@ -504,7 +519,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])]; } @@ -566,7 +581,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])]; } diff --git a/Source/SPExportSettingsPersistence.h b/Source/SPExportSettingsPersistence.h new file mode 100644 index 00000000..41609125 --- /dev/null +++ b/Source/SPExportSettingsPersistence.h @@ -0,0 +1,64 @@ +// +// 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 <https://github.com/sequelpro/sequelpro> + +#import <Foundation/Foundation.h> +#import "SPExportController.h" + +@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 new file mode 100644 index 00000000..29a981af --- /dev/null +++ b/Source/SPExportSettingsPersistence.m @@ -0,0 +1,802 @@ +// +// 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 <https://github.com/sequelpro/sequelpro> + +#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); + +@interface SPExportController (Private) + +- (void)_updateExportAdvancedOptionsLabel; + +@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 - + +@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 +{ + switch (es) { + NAMEOF(SPFilteredExport); + NAMEOF(SPQueryExport); + NAMEOF(SPTableExport); + } + 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) { + NAMEOF(SPSQLExport); + NAMEOF(SPCSVExport); + NAMEOF(SPXMLExport); + NAMEOF(SPDotExport); + NAMEOF(SPPDFExport); + NAMEOF(SPHTMLExport); + NAMEOF(SPExcelExport); + } + 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) { + NAMEOF(SPNoCompression); + NAMEOF(SPGzipCompression); + NAMEOF(SPBzip2Compression); + } + 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) { + NAMEOF(SPXMLExportMySQLFormat); + NAMEOF(SPXMLExportPlainFormat); + } + 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) { + NAMEOF(SPSQLInsertEveryNDataBytes); + NAMEOF(SPSQLInsertEveryNRows); + } + 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 +{ + //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; + + // 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 + 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; +} + +- (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:SPFExportSettingsContentType 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; +} + +- (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); note: the u00A0 is a non-breaking space, do not add more whitespace."),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"]) && [o isKindOfClass:[NSArray class]]) [self setCustomFilenameFromArray:o]; + + return YES; +} + +- (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)}]; + } +} + +- (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 @{ + @"exportToMultipleFiles": IsOn(exportFilePerTableCheck), + @"CSVIncludeFieldNames": IsOn(exportCSVIncludeFieldNamesCheck), + @"CSVFieldsTerminated": [exportCSVFieldsTerminatedField stringValue], + @"CSVFieldsWrapped": [exportCSVFieldsWrappedField stringValue], + @"CSVLinesTerminated": [exportCSVLinesTerminatedField stringValue], + @"CSVFieldsEscaped": [exportCSVFieldsEscapedField stringValue], + @"CSVNULLValuesAsText": [exportCSVNULLValuesAsTextField stringValue] + }; +} + +- (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 @{ + @"exportToMultipleFiles": IsOn(exportFilePerTableCheck), + @"XMLFormat": [[self class] describeXMLExportFormat:(SPXMLExportFormat)[exportXMLFormatPopUpButton indexOfSelectedItem]], + @"XMLOutputIncludeStructure": IsOn(exportXMLIncludeStructure), + @"XMLOutputIncludeContent": IsOn(exportXMLIncludeContent), + @"XMLNULLString": [exportXMLNULLValuesAsTextField stringValue] + }; +} + +- (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); + + NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:@{ + @"SQLIncludeStructure": IsOn(exportSQLIncludeStructureCheck), + @"SQLIncludeContent": IsOn(exportSQLIncludeContentCheck), + @"SQLIncludeErrors": IsOn(exportSQLIncludeErrorsCheck), + @"SQLIncludeDROP": IsOn(exportSQLIncludeDropSyntaxCheck), + @"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; +} + +- (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) { + 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)}]; + } +} + +- (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 + 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; +} + +- (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 + 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; +} + +- (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); + 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; +} + +- (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 - + +NSNumber *IsOn(id obj) +{ + return (([obj state] == NSOnState)? @YES : @NO); +} + +void SetOnOff(NSNumber *ref,id obj) +{ + [obj setState:([ref boolValue] ? NSOnState : NSOffState)]; +} diff --git a/Source/SPExporter.h b/Source/SPExporter.h index 4b5fc7e3..83a519c4 100644 --- a/Source/SPExporter.h +++ b/Source/SPExporter.h @@ -53,11 +53,12 @@ * explicity called. */ -@class SPMySQLConnection, SPExportFile; +@class SPMySQLConnection, SPExportFile, SPServerSupport; @interface SPExporter : NSOperation { SPMySQLConnection *connection; + SPServerSupport *serverSupport; double exportProgressValue; double exportMaxProgress; @@ -81,6 +82,11 @@ @property(readwrite, retain) SPMySQLConnection *connection; /** + * @property serverSupport Information about the features supported by this mysql version + */ +@property(readwrite, retain) SPServerSupport *serverSupport; + +/** * @property exportProgressValue The export's current progress value */ @property(readwrite, assign) double exportProgressValue; @@ -124,4 +130,26 @@ - (void)setExportOutputCompressFile:(BOOL)compress; +#pragma mark Shared Private + +/** + * This is the method you should override in every concrete exporter implementation. + */ +- (void)exportOperation; + +/** + * Write a string to the current output file using the current output encoding + * @param input The string to write + */ +- (void)writeString:(NSString *)input; + +/** + * Write a string to the current output file using UTF-8 encoding + * @param input The string to write + */ +#warning This method mainly exists to shorten some old code which sometimes uses [self exportOutputEncoding] and sometimes NSUTF8StringEncoding. \ + In general there should be no need to have more than one encoding in a file. \ + Someone needs to check if that was an oversight or intentional. +- (void)writeUTF8String:(NSString *)input; + @end diff --git a/Source/SPExporter.m b/Source/SPExporter.m index 195d900d..ff90853c 100644 --- a/Source/SPExporter.m +++ b/Source/SPExporter.m @@ -35,6 +35,7 @@ @implementation SPExporter @synthesize connection; +@synthesize serverSupport = serverSupport; @synthesize exportProgressValue; @synthesize exportProcessIsRunning; @synthesize exportUsingLowMemoryBlockingStreaming; @@ -66,11 +67,31 @@ } /** - * Override NSOperation's main() method. This method should never be called as all subclasses should override it. + * Override NSOperation's main() method. + * This method only creates an autoreleasepool and calls exportOperation */ - (void)main { - [NSException raise:NSInternalInconsistencyException format:@"Cannot call NSOperation's main() method in SPExpoter, must be overriden in a subclass. See SPExporter.h"]; + NSAutoreleasePool *pool = nil; + @try { + pool = [[NSAutoreleasePool alloc] init]; + + [self exportOperation]; + } + @catch(NSException *e) { + [[NSApp onMainThread] reportException:e]; // will be caught by FeedbackReporter + } + @finally { + [pool release]; + } +} + +/** + * This method should never be called as all subclasses should override it. + */ +- (void)exportOperation +{ + [NSException raise:NSInternalInconsistencyException format:@"Cannot call %s, must be overriden in a subclass. See SPExporter.h",__PRETTY_FUNCTION__]; } /** @@ -95,7 +116,17 @@ exportOutputCompressFile = compress; - [[[self exportOutputFile] exportFileHandle] setShouldWriteWithCompressionFormat:(compress) ? [self exportOutputCompressionFormat] : SPNoCompression]; + [[[self exportOutputFile] exportFileHandle] setCompressionFormat:(compress) ? [self exportOutputCompressionFormat] : SPNoCompression]; +} + +- (void)writeString:(NSString *)input +{ + [[self exportOutputFile] writeData:[input dataUsingEncoding:[self exportOutputEncoding]]]; +} + +- (void)writeUTF8String:(NSString *)input +{ + [[self exportOutputFile] writeData:[input dataUsingEncoding:NSUTF8StringEncoding]]; } /** @@ -105,6 +136,7 @@ { if (exportData) SPClear(exportData); if (connection) SPClear(connection); + [self setServerSupport:nil]; if (exportOutputFile) SPClear(exportOutputFile); [super dealloc]; diff --git a/Source/SPExtendedTableInfo.m b/Source/SPExtendedTableInfo.m index d3b332d0..e2f38a69 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; } @@ -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]] + ); } } @@ -220,7 +224,11 @@ static NSString *SPMySQLCommentField = @"Comment"; { [tableRowAutoIncrement setEditable:NO]; - [tableSourceInstance takeAutoIncrementFrom:tableRowAutoIncrement]; + NSNumberFormatter *fmt = [[[NSNumberFormatter alloc] init] autorelease]; + [fmt setNumberStyle:NSNumberFormatterDecimalStyle]; + NSNumber *value = [fmt numberFromString:[tableRowAutoIncrement stringValue]]; + + [tableSourceInstance setAutoIncrementTo:value]; } - (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command @@ -228,6 +236,7 @@ static NSString *SPMySQLCommentField = @"Comment"; // Listen to ESC to abort editing of auto increment input field if (command == @selector(cancelOperation:) && control == tableRowAutoIncrement) { [tableRowAutoIncrement abortEditing]; + [tableRowAutoIncrement setEditable:NO]; return YES; } @@ -546,9 +555,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 +661,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/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. diff --git a/Source/SPFieldMapperController.h b/Source/SPFieldMapperController.h index 76b65c49..7f03ed10 100644 --- a/Source/SPFieldMapperController.h +++ b/Source/SPFieldMapperController.h @@ -161,6 +161,7 @@ - (NSArray*)fieldMappingGlobalValueArray; - (NSArray*)fieldMappingTableDefaultValues; - (BOOL)importFieldNamesHeader; +- (BOOL)hasContentRows; - (BOOL)insertRemainingRowsAfterUpdate; - (BOOL)globalValuesInUsage; - (BOOL)importIntoNewTable; diff --git a/Source/SPFieldMapperController.m b/Source/SPFieldMapperController.m index 9bef9d03..6d5caf41 100644 --- a/Source/SPFieldMapperController.m +++ b/Source/SPFieldMapperController.m @@ -37,11 +37,10 @@ #import "SPCategoryAdditions.h" #import "RegexKitLite.h" #import "SPDatabaseData.h" +#import "SPFunctions.h" #import <SPMySQL/SPMySQL.h> -#define SP_NUMBER_OF_RECORDS_STRING NSLocalizedString(@"%ld of %@%lu records", @"Label showing the index of the selected CSV row") - // Constants static NSString *SPTableViewImportValueColumnID = @"import_value"; static NSString *SPTableViewTypeColumnID = @"type"; @@ -204,8 +203,8 @@ static NSUInteger SPSourceColumnTypeInteger = 1; [advancedUpdateView setHidden:YES]; [advancedInsertView setHidden:YES]; - [self changeHasHeaderCheckbox:self]; [self changeTableTarget:self]; + [self changeHasHeaderCheckbox:self]; [[self window] makeFirstResponder:fieldMapperTableView]; if([fieldMappingTableColumnNames count]) [fieldMapperTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO]; @@ -214,7 +213,8 @@ static NSUInteger SPSourceColumnTypeInteger = 1; [insertNULLValueButton setEnabled:([globalValuesTableView numberOfSelectedRows] == 1)]; [self updateFieldNameAlignment]; - + + [self validateImportButton]; } - (void)dealloc @@ -337,7 +337,18 @@ static NSUInteger SPSourceColumnTypeInteger = 1; - (BOOL)importFieldNamesHeader { - return ([importFieldNamesHeaderSwitch state] == NSOnState)?YES:NO; + if(importFieldNamesHeaderSwitch) { + return ([importFieldNamesHeaderSwitch state] == NSOnState); + } + else { + //this is a provisional field for the initial value of the checkbox until the window is actually loaded + return importFieldNamesHeader; + } +} + +- (BOOL)hasContentRows +{ + return (([fieldMappingImportArray count] - ([self importFieldNamesHeader]? 1 : 0)) > 0); } - (BOOL)insertRemainingRowsAfterUpdate @@ -607,9 +618,7 @@ static NSUInteger SPSourceColumnTypeInteger = 1; fieldMappingCurrentRow = 0; if (fieldMappingArray) SPClear(fieldMappingArray); [self setupFieldMappingArray]; - [rowDownButton setEnabled:NO]; - [rowUpButton setEnabled:([fieldMappingImportArray count] > 1)]; - [recordCountLabel setStringValue:[NSString stringWithFormat:SP_NUMBER_OF_RECORDS_STRING, (long)(fieldMappingCurrentRow+1), fieldMappingImportArrayIsPreview?@"first ":@"", (unsigned long)[fieldMappingImportArray count]]]; + [self updateRowNavigation]; [self updateFieldMappingButtonCell]; [self updateFieldMappingOperatorOptions]; @@ -714,7 +723,7 @@ static NSUInteger SPSourceColumnTypeInteger = 1; - (IBAction)changeFieldAlignment:(id)sender { - if(![fieldMappingImportArray count]) return; + if(![self hasContentRows]) return; NSUInteger i; NSInteger j; @@ -767,12 +776,8 @@ static NSUInteger SPSourceColumnTypeInteger = 1; [self updateFieldMappingButtonCell]; [fieldMapperTableView reloadData]; - - [recordCountLabel setStringValue:[NSString stringWithFormat:SP_NUMBER_OF_RECORDS_STRING, (long)(fieldMappingCurrentRow+1), fieldMappingImportArrayIsPreview?@"first ":@"", (unsigned long)[fieldMappingImportArray count]]]; - - // enable/disable buttons - [rowDownButton setEnabled:(fieldMappingCurrentRow != 0)]; - [rowUpButton setEnabled:(fieldMappingCurrentRow != (NSInteger)([fieldMappingImportArray count]-1))]; + + [self updateRowNavigation]; } - (IBAction)changeHasHeaderCheckbox:(id)sender @@ -780,15 +785,18 @@ static NSUInteger SPSourceColumnTypeInteger = 1; NSInteger i; NSArray *headerRow; - [matchingNameMenuItem setEnabled:([importFieldNamesHeaderSwitch state] == NSOnState)?YES:NO]; + [matchingNameMenuItem setEnabled:[self importFieldNamesHeader]]; // In New Table mode reset new field name according to importFieldNamesHeaderSwitch's state if (newTableMode) { [fieldMappingTableColumnNames removeAllObjects]; - if([importFieldNamesHeaderSwitch state] == NSOnState) { + if([self importFieldNamesHeader]) { headerRow = NSArrayObjectAtIndex(fieldMappingImportArray, 0); for (i = 0; i < numberOfImportColumns; i++) { - [fieldMappingTableColumnNames addObject:NSArrayObjectAtIndex(headerRow, i)]; + id headerCol = NSArrayObjectAtIndex(headerRow, i); + // we don't want a NSNull in the column headers to mess stuff up (issue #2375) + if([headerCol isNSNull]) headerCol = [prefs stringForKey:SPNullValue]; + [fieldMappingTableColumnNames addObject:headerCol]; } } else { for (i = 1; i <= numberOfImportColumns; i++) { @@ -797,6 +805,13 @@ static NSUInteger SPSourceColumnTypeInteger = 1; } [fieldMapperTableView reloadData]; } + + [self updateFieldMappingButtonCell]; + [fieldMapperTableView reloadData]; + + [self updateRowNavigation]; + + [self validateImportButton]; } - (IBAction)goBackToFileChooserFromPathControl:(id)sender @@ -850,7 +865,7 @@ static NSUInteger SPSourceColumnTypeInteger = 1; } // Step through the currently known data and get the types and values - NSUInteger i = (importFieldNamesHeader ? 1 : 0); + NSUInteger i = ([self importFieldNamesHeader] ? 1 : 0); NSArray *row; id col; for ( ; i < [fieldMappingImportArray count]; i++) { @@ -875,12 +890,14 @@ static NSUInteger SPSourceColumnTypeInteger = 1; [fieldMappingTableTypes removeAllObjects]; BOOL serverGreaterThanVersion4 = ([mySQLConnection serverMajorVersion] >= 5) ? YES : NO; - BOOL importFirstRowAsFieldNames = ([importFieldNamesHeaderSwitch state] == NSOnState); - NSString *headerName; + BOOL importFirstRowAsFieldNames = [self importFieldNamesHeader]; + NSArray *headerRow = NSArrayObjectAtIndex(fieldMappingImportArray, 0); for (columnCounter = 0; columnCounter < numberOfImportColumns; columnCounter++) { if (importFirstRowAsFieldNames) { - headerName = NSArrayObjectAtIndex(NSArrayObjectAtIndex(fieldMappingImportArray, 0), columnCounter); + id headerName = NSArrayObjectAtIndex(headerRow, columnCounter); + // we don't want a NSNull in the column headers to mess stuff up (issue #2375) + if([headerName isNSNull]) headerName = [prefs stringForKey:SPNullValue]; [fieldMappingTableColumnNames addObject:headerName]; } else { [fieldMappingTableColumnNames addObject:[NSString stringWithFormat:@"col_%ld", (long)(columnCounter + 1)]]; @@ -914,10 +931,8 @@ static NSUInteger SPSourceColumnTypeInteger = 1; fieldMappingCurrentRow = 0; if (fieldMappingArray) SPClear(fieldMappingArray); [self setupFieldMappingArray]; - [rowDownButton setEnabled:NO]; - [rowUpButton setEnabled:([fieldMappingImportArray count] > 1)]; - [recordCountLabel setStringValue:[NSString stringWithFormat:SP_NUMBER_OF_RECORDS_STRING, (long)(fieldMappingCurrentRow+1), fieldMappingImportArrayIsPreview?@"first ":@"", (unsigned long)[fieldMappingImportArray count]]]; - + [self updateRowNavigation]; + [self updateFieldMappingButtonCell]; [self updateFieldMappingOperatorOptions]; @@ -1043,9 +1058,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) { @@ -1102,8 +1115,8 @@ static NSUInteger SPSourceColumnTypeInteger = 1; // Add column placeholder NSInteger i = 0; - if([fieldMappingImportArray count] && [[fieldMappingImportArray objectAtIndex:0] count]) { - for(id item in [fieldMappingImportArray objectAtIndex:0]) { + if([self hasContentRows]) { + for(id item in [fieldMappingImportArray objectAtIndex:([self importFieldNamesHeader]? 1 : 0)]) { i++; if ([item isNSNull]) { [insertPullDownButton addItemWithTitle:[NSString stringWithFormat:@"%li. <%@>", (long)i, [prefs objectForKey:SPNullValue]]]; @@ -1385,7 +1398,7 @@ static NSUInteger SPSourceColumnTypeInteger = 1; - (void)matchHeaderNames { - if(![fieldMappingImportArray count]) return; + if(![self hasContentRows]) return; NSMutableArray *fileHeaderNames = [NSMutableArray array]; [fileHeaderNames setArray:NSArrayObjectAtIndex(fieldMappingImportArray, 0)]; @@ -1478,9 +1491,9 @@ static NSUInteger SPSourceColumnTypeInteger = 1; if (!fieldMappingArray) { fieldMappingArray = [[NSMutableArray alloc] init]; + NSArray *currentRowValues = NSArrayObjectAtIndex(fieldMappingImportArray, fieldMappingCurrentRow); for (i = 0; i < [fieldMappingTableColumnNames count]; i++) { - if (i < [NSArrayObjectAtIndex(fieldMappingImportArray, fieldMappingCurrentRow) count] - && ![NSArrayObjectAtIndex(NSArrayObjectAtIndex(fieldMappingImportArray, fieldMappingCurrentRow), i) isNSNull]) { + if (i < [currentRowValues count]) { value = i; } else { value = 0; @@ -1499,7 +1512,7 @@ static NSUInteger SPSourceColumnTypeInteger = 1; - (void)updateFieldMappingButtonCell { NSUInteger i; - if([fieldMappingImportArray count] == 0) return; + if(![self hasContentRows]) return; [fieldMappingButtonOptions setArray:[fieldMappingImportArray objectAtIndex:fieldMappingCurrentRow]]; for (i = 0; i < [fieldMappingButtonOptions count]; i++) { if ([[fieldMappingButtonOptions objectAtIndex:i] isNSNull]) @@ -1552,33 +1565,75 @@ static NSUInteger SPSourceColumnTypeInteger = 1; } #endif - // Set matching names only if csv file has an header - if(importFieldNamesHeader && alignment == 2) - [alignByPopup selectItemWithTag:2]; - else if(!importFieldNamesHeader && alignment == 2) - [alignByPopup selectItemWithTag:0]; - else + if(alignment == 2) { + // Set matching names only if csv file has a header + if([self importFieldNamesHeader]) + [alignByPopup selectItemWithTag:2]; + else + [alignByPopup selectItemWithTag:0]; + } + else { [alignByPopup selectItemWithTag:alignment]; + } [self changeFieldAlignment:nil]; } +- (void)updateRowNavigation +{ + int firstRowIsHeader = [self importFieldNamesHeader] ? 1 : 0; + + // if the first row becomes a header row it can no longer be a content row + if(!fieldMappingCurrentRow && firstRowIsHeader && [self hasContentRows]) { + fieldMappingCurrentRow++; + [self updateFieldMappingButtonCell]; + [fieldMapperTableView reloadData]; + } + + NSUInteger countRows = [fieldMappingImportArray count]; + [rowDownButton setEnabled:(fieldMappingCurrentRow > firstRowIsHeader)]; + [rowUpButton setEnabled:(SPIntS2U(fieldMappingCurrentRow) < (countRows - 1))]; + + long displayedCurrentRow = fieldMappingCurrentRow+1-firstRowIsHeader; + unsigned long displayedTotalRows = (countRows? (countRows - firstRowIsHeader) : 0); //avoid negative values on empty array + + NSString *fmt; + if(fieldMappingImportArrayIsPreview) + fmt = NSLocalizedString(@"%ld of first %lu record(s)", @"Label showing the index of the selected CSV row (csv partially loaded)"); + else + fmt = NSLocalizedString(@"%ld of %lu record(s)", @"Label showing the index of the selected CSV row"); + + [recordCountLabel setStringValue:[NSString stringWithFormat:fmt, displayedCurrentRow, displayedTotalRows]]; +} + - (void)validateImportButton { BOOL enableImportButton = YES; - + if (newTableMode) { if (![tablesListInstance isTableNameValid:[newTableNameTextField stringValue] forType:SPTableTypeTable ignoringSelectedTable:NO]) { [importButton setEnabled:NO]; return; } + + BOOL hasImportColumns = NO; for (NSUInteger i = 0; i < [fieldMappingTableColumnNames count]; i++) { - if (![[fieldMappingTableColumnNames objectAtIndex:i] length] && [doImportKey isEqualToNumber:[fieldMappingOperatorArray objectAtIndex:i]]) { + NSString *colName = [fieldMappingTableColumnNames objectAtIndex:i]; + BOOL shouldImport = [doImportKey isEqualToNumber:[fieldMappingOperatorArray objectAtIndex:i]]; + if (shouldImport && ![colName length]) { [importButton setEnabled:NO]; return; } + if(!hasImportColumns && shouldImport) hasImportColumns = YES; + } + + if(!hasImportColumns) { + // new table without any columns is not valid + [importButton setEnabled:NO]; + return; } + for (NSString* fieldType in fieldMappingTableTypes) { if(![fieldType length]) { [importButton setEnabled:NO]; @@ -1586,6 +1641,13 @@ static NSUInteger SPSourceColumnTypeInteger = 1; } } } + else { + // we don't want to create a new table and have no rows to import either => can't import nothing + if(![self hasContentRows]) { + [importButton setEnabled:NO]; + return; + } + } if ([[self selectedImportMethod] isEqualToString:@"UPDATE"]) { enableImportButton = NO; @@ -1606,15 +1668,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; @@ -1700,11 +1768,16 @@ static NSUInteger SPSourceColumnTypeInteger = 1; } else if([importFieldNamesHeaderSwitch state] == NSOffState) { - if([NSArrayObjectAtIndex(fieldMappingArray, rowIndex) unsignedIntegerValue]>=[NSArrayObjectAtIndex(fieldMappingImportArray, 0) count]) - return NSArrayObjectAtIndex(fieldMappingGlobalValues, [NSArrayObjectAtIndex(fieldMappingArray, rowIndex) integerValue]); + NSUInteger colIndex = [NSArrayObjectAtIndex(fieldMappingArray, rowIndex) unsignedIntegerValue]; + NSString *retval; + if(colIndex >= [NSArrayObjectAtIndex(fieldMappingImportArray, 0) count]) + retval = NSArrayObjectAtIndex(fieldMappingGlobalValues, colIndex); else - return NSArrayObjectAtIndex(NSArrayObjectAtIndex(fieldMappingImportArray, fieldMappingCurrentRow), [NSArrayObjectAtIndex(fieldMappingArray, rowIndex) integerValue]); + retval = NSArrayObjectAtIndex(NSArrayObjectAtIndex(fieldMappingImportArray, fieldMappingCurrentRow), colIndex); + + if([retval isNSNull]) retval = NSLocalizedString(@"Value will be imported as MySQL NULL", @"CSV Field Mapping : Table View : Tooltip for fields with NULL value"); + return retval; } } 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 <https://github.com/sequelpro/sequelpro> +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 62204252..49bcd4a4 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,63 @@ 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; + 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]; @@ -204,17 +208,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 +237,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 +258,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"); } } @@ -299,9 +298,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]; @@ -344,16 +344,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 +369,6 @@ #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. */ - (SPFileCompressionFormat)compressionFormat @@ -417,22 +407,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 @@ -469,6 +453,7 @@ if (processingThread) SPClear(processingThread); + free(wrappedFile); free(wrappedFilePath); SPClear(buffer); diff --git a/Source/SPFunctions.h b/Source/SPFunctions.h new file mode 100644 index 00000000..0cda7e36 --- /dev/null +++ b/Source/SPFunctions.h @@ -0,0 +1,51 @@ +// +// 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 <https://github.com/sequelpro/sequelpro> + +/** + * 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)); + +/** + * 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); + +/** + * Convert a signed integer into an unsigned integer or throw an exception if the values don't fit. + * @param i a signed integer + * @return the same value, casted to unsigned integer + */ +NSUInteger SPIntS2U(NSInteger i); diff --git a/Source/SPFunctions.m b/Source/SPFunctions.m new file mode 100644 index 00000000..6f11d236 --- /dev/null +++ b/Source/SPFunctions.m @@ -0,0 +1,78 @@ +// +// 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 <https://github.com/sequelpro/sequelpro> + +#import "SPFunctions.h" +#import <Security/SecRandom.h> +#import "SPOSInfo.h" + +void SPMainQSync(void (^block)(void)) +{ + if(dispatch_get_current_queue() == dispatch_get_main_queue()) { + block(); + } + else { + 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<count; i++) { + int c = fgetc(fp); + if(c == EOF) { // /dev/random should never EOF + errno = ferror(fp); + return -1; + } + buf[i] = c; + } + + fclose(fp); + + return 0; +} + +NSUInteger SPIntS2U(NSInteger i) +{ + if(i < 0) [NSException raise:NSRangeException format:@"NSInteger %ld does not fit in NSUInteger",i]; + + return (NSUInteger)i; +} diff --git a/Source/SPGotoDatabaseController.h b/Source/SPGotoDatabaseController.h index ac838eff..effc70e9 100644 --- a/Source/SPGotoDatabaseController.h +++ b/Source/SPGotoDatabaseController.h @@ -36,7 +36,7 @@ * keyboard-based navigation between databases. The dialog also enables * jumping to a database by C&P-ing its full name. */ -@interface SPGotoDatabaseController : NSWindowController <NSTableViewDataSource,NSControlTextEditingDelegate,NSUserInterfaceValidations> +@interface SPGotoDatabaseController : NSWindowController <NSTableViewDataSource,NSTableViewDelegate,NSControlTextEditingDelegate,NSUserInterfaceValidations> { IBOutlet NSSearchField *searchField; IBOutlet NSButton *okButton; @@ -48,6 +48,8 @@ BOOL isFiltered; BOOL allowCustomNames; + + NSDictionary *highlightAttrs; } /** diff --git a/Source/SPGotoDatabaseController.m b/Source/SPGotoDatabaseController.m index b48b77b7..61ec8962 100644 --- a/Source/SPGotoDatabaseController.m +++ b/Source/SPGotoDatabaseController.m @@ -42,17 +42,43 @@ * It will neither clear the filteredList first, nor change the isFiltered ivar! * Search is case insensitive. */ -- (void)_buildHightlightedFilterList:(NSString *)filter didFindExactMatch:(BOOL *)exactMatch; +- (void)_buildFilterList:(NSString *)filter didFindExactMatch:(BOOL *)exactMatch; - (IBAction)okClicked:(id)sender; - (IBAction)cancelClicked:(id)sender; - (IBAction)searchChanged:(id)sender; - (IBAction)toggleWordSearch:(id)sender; -- (BOOL)qualifiesForWordSearch:(NSString *)s; - (BOOL)qualifiesForWordSearch; //takes s from searchField @end +static BOOL StringQualifiesForWordSearch(NSString *s); + +#pragma mark - + +@interface SPGotoFilteredItem : NSObject { + NSString *string; + NSArray *matches; + BOOL isCustomItem; +} +@property(nonatomic,retain) NSString *string; +@property(nonatomic,retain) NSArray *matches; +@property(nonatomic,assign) BOOL isCustomItem; + ++ (SPGotoFilteredItem *)item; +@end + +@implementation SPGotoFilteredItem + +@synthesize string; +@synthesize matches; +@synthesize isCustomItem; + ++ (SPGotoFilteredItem *)item { return [[[SPGotoFilteredItem alloc] init] autorelease]; } +@end + +#pragma mark - + @implementation SPGotoDatabaseController @synthesize allowCustomNames; @@ -60,9 +86,14 @@ - (id)init { if ((self = [super initWithWindowNibName:@"GotoDatabaseDialog"])) { - unfilteredList = [[NSMutableArray alloc] init]; + unfilteredList = [[NSMutableArray alloc] init]; filteredList = [[NSMutableArray alloc] init]; isFiltered = NO; + highlightAttrs = [@{ + NSBackgroundColorAttributeName: [NSColor colorWithCalibratedRed:249/255.0 green:247/255.0 blue:62/255.0 alpha:0.5], + NSUnderlineColorAttributeName: [NSColor colorWithCalibratedRed:246/255.0 green:189/255.0 blue:85/255.0 alpha:1.0], + NSUnderlineStyleAttributeName: [NSNumber numberWithInt:NSUnderlineStyleThick] + } retain]; [self setAllowCustomNames:YES]; } @@ -107,23 +138,29 @@ BOOL exactMatch = NO; - [self _buildHightlightedFilterList:newFilter didFindExactMatch:&exactMatch]; + [self _buildFilterList:newFilter didFindExactMatch:&exactMatch]; //always add the search string to the end of the list (in case the user //wants to switch to a DB not in the list) unless there was an exact match if ([self allowCustomNames] && !exactMatch) { - NSMutableAttributedString *searchValue = [[NSMutableAttributedString alloc] initWithString:newFilter]; - - [searchValue applyFontTraits:NSItalicFontMask range:NSMakeRange(0, [newFilter length])]; - - [filteredList addObject:[searchValue autorelease]]; + // remove quotes if any + if(StringQualifiesForWordSearch(newFilter)) + newFilter = [newFilter substringWithRange:NSMakeRange(1, [newFilter length]-2)]; + + if([newFilter length]) { + SPGotoFilteredItem *customItem = [SPGotoFilteredItem item]; + [customItem setString:newFilter]; + [customItem setIsCustomItem:YES]; + + [filteredList addObject:customItem]; + } } } [databaseListView reloadData]; // Ensure we have a selection - if ([databaseListView selectedRow] < 0) { + if ([databaseListView selectedRow] < 0 && [self numberOfRowsInTableView:databaseListView]) { [databaseListView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO]; } @@ -160,16 +197,12 @@ id attrValue; if (isFiltered) { - attrValue = [filteredList objectOrNilAtIndex:row]; + attrValue = [(SPGotoFilteredItem *)[filteredList objectOrNilAtIndex:row] string]; } else { attrValue = [unfilteredList objectOrNilAtIndex:row]; } - if ([attrValue isKindOfClass:[NSAttributedString class]]) { - return [attrValue string]; - } - return attrValue; } @@ -199,18 +232,14 @@ #pragma mark - #pragma mark Private -- (void)_buildHightlightedFilterList:(NSString *)filter didFindExactMatch:(BOOL *)exactMatch +- (void)_buildFilterList:(NSString *)filter didFindExactMatch:(BOOL *)exactMatch { - NSDictionary *attrs = [[NSDictionary alloc] initWithObjectsAndKeys: - [NSColor colorWithCalibratedRed:249/255.0 green:247/255.0 blue:62/255.0 alpha:0.5],NSBackgroundColorAttributeName, - [NSColor colorWithCalibratedRed:180/255.0 green:164/255.0 blue:31/255.0 alpha:1.0],NSUnderlineColorAttributeName, - [NSNumber numberWithInt:NSUnderlineStyleSingle],NSUnderlineStyleAttributeName, - nil]; - NSStringCompareOptions opts = NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch|NSWidthInsensitiveSearch; + BOOL useWordSearch = StringQualifiesForWordSearch(filter); + // interpret a quoted string as 'looking for exact submachtes only' - if([self qualifiesForWordSearch:filter]) { + if(useWordSearch) { //remove quotes for matching filter = [filter substringWithRange:NSMakeRange(1, [filter length]-2)]; @@ -227,9 +256,11 @@ } } - NSMutableAttributedString *attrMatch = [[NSMutableAttributedString alloc] initWithString:db]; - [attrMatch setAttributes:attrs range:matchRange]; - [filteredList addObject:[attrMatch autorelease]]; + SPGotoFilteredItem *item = [SPGotoFilteredItem item]; + [item setString:db]; + [item setMatches:@[[NSValue valueWithRange:matchRange]]]; + + [filteredList addObject:item]; } } // default to a per-character search @@ -251,27 +282,39 @@ } } - NSMutableAttributedString *attrMatch = [[NSMutableAttributedString alloc] initWithString:db]; + SPGotoFilteredItem *item = [SPGotoFilteredItem item]; + [item setString:db]; + [item setMatches:matches]; - for (NSValue *matchValue in matches) { - [attrMatch setAttributes:attrs range:[matchValue rangeValue]]; - } - - [filteredList addObject:[attrMatch autorelease]]; + [filteredList addObject:item]; } } - [attrs release]; -} + //sort the filtered list + [filteredList sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { + // word search produces only 1 match, skip. + if(!useWordSearch) { + // First we want to sort by number of match groups. + // Less match groups -> better result: + // Search string: abc + // Matches: schema_abc, tablecloth + // => First only has 1 match group, is more likely to be the desired result + NSUInteger mgc1 = [[(SPGotoFilteredItem *)obj1 matches] count]; + NSUInteger mgc2 = [[(SPGotoFilteredItem *)obj2 matches] count]; + if(mgc1 < mgc2) + return NSOrderedAscending; + if(mgc2 < mgc1) + return NSOrderedDescending; + } + // For strings with the same number of match groups we just sort alphabetically + return [[(SPGotoFilteredItem *)obj1 string] compare:[(SPGotoFilteredItem *)obj2 string]]; + }]; -- (BOOL)qualifiesForWordSearch:(NSString *)s -{ - return (s && ([s length] > 1) && (([s hasPrefix:@"\""] && [s hasSuffix:@"\""]) || ([s hasPrefix:@"'"] && [s hasSuffix:@"'"]))); } - (BOOL)qualifiesForWordSearch { - return [self qualifiesForWordSearch:[searchField stringValue]]; + return StringQualifiesForWordSearch([searchField stringValue]); } #pragma mark - @@ -293,8 +336,54 @@ return [unfilteredList objectAtIndex:rowIndex]; } else { - return [filteredList objectAtIndex:rowIndex]; + return [(SPGotoFilteredItem *)[filteredList objectAtIndex:rowIndex] string]; + } +} + +#pragma mark - +#pragma mark NSTableViewDelegate + +- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + //nothing to do here, unless the list is filtered + if(!isFiltered) return; + + // The styling of source list table views is basically done by Apple by replacing + // the cell's string with an attributedstring. But if the data source were to + // already return an attributedstring, most of the other attributes Apple sets + // would not get applied. So we have to add our attributes after Apple has already + // modified the string returned by the data source. + + id cellValue = [cell objectValue]; + //turn the cell value into something we can work with + NSMutableAttributedString *attrString; + if([cellValue isKindOfClass:[NSMutableAttributedString class]]) { + attrString = cellValue; + } + else if([cellValue isKindOfClass:[NSAttributedString class]]) { + attrString = [[[NSMutableAttributedString alloc] initWithAttributedString:cellValue] autorelease]; + } + else if([cellValue isKindOfClass:[NSString class]]) { + attrString = [[[NSMutableAttributedString alloc] initWithString:cellValue] autorelease]; + } + else { + SPLog(@"Unknown object for cellValue (type=%@)",[cellValue className]); + return; } + + SPGotoFilteredItem *item = [filteredList objectAtIndex:row]; + + if([item isCustomItem]) { + [[attrString mutableString] appendString:@"⁈"]; + [attrString addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:NSMakeRange([attrString length]-1, 1)]; + } + else { + for (NSValue *matchValue in [item matches]) { + [attrString addAttributes:highlightAttrs range:[matchValue rangeValue]]; + } + } + + [cell setObjectValue:attrString]; } #pragma mark - @@ -307,15 +396,30 @@ [cancelButton performClick:control]; return YES; } + + // the keyboard event is the preferable choice as it will also scroll the window + // TODO: check if the other path is ever used + NSEvent *currentEvent = [NSApp currentEvent]; + BOOL isKeyDownEvent = ([currentEvent type] == NSKeyDown); // Arrow down/up will usually go to start/end of the text field. we want to change the selected table row. if (commandSelector == @selector(moveDown:)) { - [databaseListView selectRowIndexes:[NSIndexSet indexSetWithIndex:([databaseListView selectedRow]+1)] byExtendingSelection:NO]; + if(isKeyDownEvent) { + [databaseListView keyDown:currentEvent]; + } + else { + [databaseListView selectRowIndexes:[NSIndexSet indexSetWithIndex:([databaseListView selectedRow]+1)] byExtendingSelection:NO]; + } return YES; } if (commandSelector == @selector(moveUp:)) { - [databaseListView selectRowIndexes:[NSIndexSet indexSetWithIndex:([databaseListView selectedRow]-1)] byExtendingSelection:NO]; + if(isKeyDownEvent) { + [databaseListView keyDown:currentEvent]; + } + else { + [databaseListView selectRowIndexes:[NSIndexSet indexSetWithIndex:([databaseListView selectedRow]-1)] byExtendingSelection:NO]; + } return YES; } @@ -345,8 +449,16 @@ { SPClear(unfilteredList); SPClear(filteredList); + SPClear(highlightAttrs); [super dealloc]; } @end + +#pragma mark - + +BOOL StringQualifiesForWordSearch(NSString *s) +{ + return (s && ([s length] > 1) && (([s hasPrefix:@"\""] && [s hasSuffix:@"\""]) || ([s hasPrefix:@"'"] && [s hasSuffix:@"'"]))); +} 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 <mach/mach_time.h> @@ -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/SPHTMLExporter.m b/Source/SPHTMLExporter.m index 9bcc6616..f82ed472 100644 --- a/Source/SPHTMLExporter.m +++ b/Source/SPHTMLExporter.m @@ -53,15 +53,4 @@ return self; } -/** - * Start the HTML export process. This method is automatically called when an instance of this class - * is placed on an NSOperationQueue. Do not call it directly as there is no manual multithreading. - */ -- (void)main -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - [pool release]; -} - @end diff --git a/Source/SPHTMLExporterDelegate.m b/Source/SPHTMLExporterDelegate.m index 5850849d..0d69f651 100644 --- a/Source/SPHTMLExporterDelegate.m +++ b/Source/SPHTMLExporterDelegate.m @@ -29,6 +29,7 @@ // More info at <https://github.com/sequelpro/sequelpro> #import "SPHTMLExporterDelegate.h" +#import "SPExportInitializer.h" @implementation SPExportController (SPHTMLExporterDelegate) @@ -37,7 +38,8 @@ } - (void)htmlExportProcessComplete:(SPHTMLExporter *)exporter -{ +{ + [self exportEnded]; } - (void)htmlExportProcessWillBeginWritingData:(SPHTMLExporter *)exporter 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 <https://github.com/sequelpro/sequelpro> + +#import <Cocoa/Cocoa.h> + +/** + * 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 <https://github.com/sequelpro/sequelpro> + +#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/SPIndexesController.m b/Source/SPIndexesController.m index 0eca239c..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]; @@ -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 ac7bd390..066f4011 100644 --- a/Source/SPKeychain.m +++ b/Source/SPKeychain.m @@ -31,6 +31,7 @@ #import "SPKeychain.h" #import "SPAlertSheets.h" +#import "SPOSInfo.h" #import <Security/Security.h> #import <CoreFoundation/CoreFoundation.h> @@ -114,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] + ); } } } @@ -212,33 +214,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 +264,6 @@ if (search) CFRelease(search); return (numberOfItemsFound > 0); -#endif } /** @@ -292,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; } @@ -324,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/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/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/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 017efd6e..bf8210ea 100644 --- a/Source/SPObjectAdditions.m +++ b/Source/SPObjectAdditions.m @@ -28,6 +28,8 @@ // // More info at <https://github.com/sequelpro/sequelpro> +#import <objc/runtime.h> + @implementation NSObject (SPObjectAdditions) /** @@ -42,57 +44,45 @@ return (self == null); } -- (void)_scrollViewDidChangeBounds:(id)obj +- (BOOL)isInArray:(NSArray *)list { - NSMutableString *msg = [NSMutableString string]; - - [msg appendFormat:@"%s tripped!\n\n",__PRETTY_FUNCTION__]; - -retryDescribe: - [msg appendFormat:@"passed object (class <%@>): %@\n\n",[obj className],obj]; - - if ([obj isKindOfClass:[NSNotification class]]) { - NSNotification *notif = (NSNotification *)obj; - [msg appendFormat:@"unwrapping NSNotification named '%@' (userInfo=%@)\n\n", - [notif name], - [notif userInfo]]; - obj = [notif object]; - goto retryDescribe; - } + return [list containsObject:self]; +} + +@end + +#pragma mark - + +@interface NSAlert (ApplePrivate) + +- (IBAction)buttonPressed:(id)sender; + +@end + +@implementation NSAlert (SPAlertDebug) + ++ (void)load +{ + static dispatch_once_t onceToken; - if([obj isKindOfClass:[NSView class]]) { - [msg appendString:@"View hierarchy (parents):\n"]; - id parent = obj; - while(parent) { - [msg appendFormat:@"- %p (class <%@>): %@, id=%@, tag=%ld\n", - parent, - [parent className], - parent, - [(NSView *)parent identifier], - [(NSView *)parent tag]]; - parent = [parent superview]; - } - [msg appendString:@"\n"]; + dispatch_once(&onceToken, ^{ + Class alertClass = [self class]; - [msg appendString:@"View hierarchy (own children): \n"]; - for (id child in [(NSView *)obj subviews]) { - [msg appendFormat:@"- %p (class <%@>): %@, id=%@, tag=%ld\n", - child, - [child className], - child, - [(NSView *)child identifier], - [(NSView *)child tag]]; - } - [msg appendString:@"\n"]; - } - - if([obj respondsToSelector:@selector(window)]) { - [msg appendFormat:@"In Window: %@\n\n",[obj window]]; - } - - [msg appendFormat:@"self: %p (class <%@>)\n\n",self,[self className]]; + 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]); - @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:msg userInfo:nil]; + [self sp_buttonPressed:obj]; } @end diff --git a/Source/SPPDFExporter.m b/Source/SPPDFExporter.m index 9d92dc7f..0eda99b1 100644 --- a/Source/SPPDFExporter.m +++ b/Source/SPPDFExporter.m @@ -53,15 +53,4 @@ return self; } -/** - * Start the PDF export process. This method is automatically called when an instance of this class - * is placed on an NSOperationQueue. Do not call it directly as there is no manual multithreading. - */ -- (void)main -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - [pool release]; -} - @end diff --git a/Source/SPPDFExporterDelegate.m b/Source/SPPDFExporterDelegate.m index 017c17f3..b6b6eef7 100644 --- a/Source/SPPDFExporterDelegate.m +++ b/Source/SPPDFExporterDelegate.m @@ -29,6 +29,7 @@ // More info at <https://github.com/sequelpro/sequelpro> #import "SPPDFExporterDelegate.h" +#import "SPExportInitializer.h" @implementation SPExportController (SPPDFExporterDelegate) @@ -37,7 +38,8 @@ } - (void)pdfExportProcessComplete:(SPPDFExporter *)exporter -{ +{ + [self exportEnded]; } - (void)pdfExportProcessWillBeginWritingData:(SPPDFExporter *)exporter 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 <https://github.com/sequelpro/sequelpro> + +#import <Foundation/Foundation.h> + +/** + * 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 <https://github.com/sequelpro/sequelpro> + +#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/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 <https://github.com/sequelpro/sequelpro> + +#import <Cocoa/Cocoa.h> + +/** + * 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 <https://github.com/sequelpro/sequelpro> + +#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/SPPrivilegesMO.h b/Source/SPPrivilegesMO.h new file mode 100644 index 00000000..f58e4f5b --- /dev/null +++ b/Source/SPPrivilegesMO.h @@ -0,0 +1,41 @@ +// +// SPPrivilegesMO.h +// sequel-pro +// +// Created by Max Lohrmann on 17.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 <https://github.com/sequelpro/sequelpro> + +#import <Foundation/Foundation.h> +#import <CoreData/CoreData.h> + +@class SPUserMO; + +@interface SPPrivilegesMO : NSManagedObject + +@property (nonatomic, retain) NSString *db; +@property (nonatomic, retain) SPUserMO *user; + +@end diff --git a/Source/SPPrivilegesMO.m b/Source/SPPrivilegesMO.m new file mode 100644 index 00000000..d306a663 --- /dev/null +++ b/Source/SPPrivilegesMO.m @@ -0,0 +1,50 @@ +// +// SPPrivilegesMO.m +// sequel-pro +// +// Created by Max Lohrmann on 17.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 <https://github.com/sequelpro/sequelpro> + +#import "SPPrivilegesMO.h" +#import "SPUserMO.h" +#import "SPUserManager.h" + + +@implementation SPPrivilegesMO + +@dynamic db; +@dynamic user; + +- (BOOL)validateForUpdate:(NSError **)error +{ + if(![super validateForUpdate:error]) return NO; + + SPUserManager *mgr = [self valueForKey:@"userManager"]; + + return [mgr grantDbPrivilegesWithPrivilege:self]; +} + +@end 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/SPQueryFavoriteManager.m b/Source/SPQueryFavoriteManager.m index 3738fe0d..542184c4 100644 --- a/Source/SPQueryFavoriteManager.m +++ b/Source/SPQueryFavoriteManager.m @@ -488,7 +488,7 @@ #ifndef SP_CODA - (IBAction)showHelp:(id)sender { - [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:NSLocalizedString(@"http://www.sequelpro.com/docs/Query_Favorites", @"Localized help page for query favourites - do not localize if no translated webpage is available")]]; + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:NSLocalizedString(@"http://www.sequelpro.com/docs/Working_with_Query_Favorites", @"Localized help page for query favourites - do not localize if no translated webpage is available")]]; } #endif @@ -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:SPFQueryFavoritesContentType forKey:SPFFormatKey]; [spfdata setObject:@NO forKey:@"encrypted"]; NSIndexSet *indexes = [favoritesTableView selectedRowIndexes]; diff --git a/Source/SPSQLExporter.h b/Source/SPSQLExporter.h index 1d3717f2..079e1412 100644 --- a/Source/SPSQLExporter.h +++ b/Source/SPSQLExporter.h @@ -135,7 +135,7 @@ */ @property(readwrite, assign) SPSQLExportInsertDivider sqlInsertDivider; -- (id)initWithDelegate:(NSObject *)exportDelegate; +- (id)initWithDelegate:(NSObject<SPSQLExporterProtocol> *)exportDelegate; - (BOOL)didExportErrorsOccur; diff --git a/Source/SPSQLExporter.m b/Source/SPSQLExporter.m index f72bb72f..8d53d0b3 100644 --- a/Source/SPSQLExporter.m +++ b/Source/SPSQLExporter.m @@ -84,13 +84,8 @@ return self; } -/** - * Start the SQL export process. This method is automatically called when an instance of this class - * is placed on an NSOperationQueue. Do not call it directly as there is no manual multithreading. - */ -- (void)main +- (void)exportOperation { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; sqlTableDataInstance = [[[SPTableData alloc] init] autorelease]; [sqlTableDataInstance setConnection:connection]; @@ -128,7 +123,6 @@ { [errors release]; [sqlString release]; - [pool release]; return; } @@ -150,7 +144,6 @@ if ([self isCancelled]) { [errors release]; [sqlString release]; - [pool release]; return; } @@ -161,6 +154,7 @@ case SPTableTypeFunc: targetArray = funcs; break; + case SPTableTypeTable: default: targetArray = tables; break; @@ -171,14 +165,16 @@ // If required write the UTF-8 Byte Order Mark (BOM) if ([self sqlOutputIncludeUTF8BOM]) { - [metaString setString:@"\xef\xbb\xbf"]; - [metaString appendString:@"# ************************************************************\n"]; - } - else { - [metaString setString:@"# ************************************************************\n"]; + [metaString appendString:@"\xef\xbb\xbf"]; } + + // we require utf8 + [connection setEncoding:@"utf8"]; + // …but utf8mb4 (aka "really" utf8) would be even better. + BOOL utf8mb4 = [connection setEncoding:@"utf8mb4"]; // Add the dump header to the dump file + [metaString appendString:@"# ************************************************************\n"]; [metaString appendString:@"# Sequel Pro SQL dump\n"]; [metaString appendFormat:@"# %@ %@\n#\n", NSLocalizedString(@"Version", @"export header version label"), [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]]; [metaString appendFormat:@"# %@\n# %@\n#\n", SPLOCALIZEDURL_HOMEPAGE, SPDevURL]; @@ -192,12 +188,21 @@ [metaString appendString:@"/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n"]; [metaString appendString:@"/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n"]; [metaString appendString:@"/*!40101 SET NAMES utf8 */;\n"]; + if(utf8mb4) { + // !! This being outside of a conditional comment is FULLY INTENTIONAL !! + // We *absolutely* want that to fail if the export includes utf8mb4 data, but the server can't handle it. + // MySQL would _normally_ just drop-replace such characters with "?" (a literal questionmark) without any (visible) complaint. + // Since that means irreversible (and often hard to notice) data corruption, + // the user should CONSCIOUSLY make a decision for that to happen! + //TODO we should link to a website explaining the risk here + [metaString appendString:@"SET NAMES utf8mb4;\n"]; + } [metaString appendString:@"/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;\n"]; [metaString appendString:@"/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n"]; [metaString appendString:@"/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n\n\n"]; - - [[self exportOutputFile] writeData:[metaString dataUsingEncoding:[self exportOutputEncoding]]]; + + [self writeString:metaString]; // Loop through the selected tables for (NSArray *table in tables) @@ -206,7 +211,6 @@ if ([self isCancelled]) { [errors release]; [sqlString release]; - [pool release]; return; } @@ -231,7 +235,7 @@ lastProgressValue = 0; // Add the name of table - [[self exportOutputFile] writeData:[[NSString stringWithFormat:@"# %@ %@\n# ------------------------------------------------------------\n\n", NSLocalizedString(@"Dump of table", @"sql export dump of table label"), tableName] dataUsingEncoding:[self exportOutputEncoding]]]; + [self writeString:[NSString stringWithFormat:@"# %@ %@\n# ------------------------------------------------------------\n\n", NSLocalizedString(@"Dump of table", @"sql export dump of table label"), tableName]]; // Determine whether this table is a table or a view via the CREATE TABLE command, and keep the create table syntax queryResult = [connection queryString:[NSString stringWithFormat:@"SHOW CREATE TABLE %@", [tableName backtickQuotedString]]]; @@ -257,15 +261,14 @@ if ([connection queryErrored]) { [errors appendFormat:@"%@\n", [connection lastErrorMessage]]; - [[self exportOutputFile] writeData:[[NSString stringWithFormat:@"# Error: %@\n\n\n", [connection lastErrorMessage]] dataUsingEncoding:NSUTF8StringEncoding]]; + [self writeUTF8String:[NSString stringWithFormat:@"# Error: %@\n\n\n", [connection lastErrorMessage]]]; continue; } // Add a 'DROP TABLE' command if required if (sqlOutputIncludeDropSyntax) { - [[self exportOutputFile] writeData:[[NSString stringWithFormat:@"DROP %@ IF EXISTS %@;\n\n", ((tableType == SPTableTypeTable) ? @"TABLE" : @"VIEW"), [tableName backtickQuotedString]] - dataUsingEncoding:[self exportOutputEncoding]]]; + [self writeString:[NSString stringWithFormat:@"DROP %@ IF EXISTS %@;\n\n", ((tableType == SPTableTypeTable) ? @"TABLE" : @"VIEW"), [tableName backtickQuotedString]]]; } // Add the create syntax for the table if specified in the export dialog @@ -279,9 +282,9 @@ if (![self sqlOutputIncludeAutoIncrement]) { createTableSyntax = [createTableSyntax stringByReplacingOccurrencesOfRegex:[NSString stringWithFormat:@"AUTO_INCREMENT=[0-9]+ "] withString:@""]; } - - [[self exportOutputFile] writeData:[createTableSyntax dataUsingEncoding:NSUTF8StringEncoding]]; - [[self exportOutputFile] writeData:[@";\n\n" dataUsingEncoding:NSUTF8StringEncoding]]; + + [self writeUTF8String:createTableSyntax]; + [self writeUTF8String:@";\n\n"]; } // Add the table content if required @@ -316,9 +319,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; } @@ -339,7 +341,7 @@ if ([connection queryErrored] || ![rowArray count]) { [errors appendFormat:@"%@\n", [connection lastErrorMessage]]; - [[self exportOutputFile] writeData:[[NSString stringWithFormat:@"# Error: %@\n\n\n", [connection lastErrorMessage]] dataUsingEncoding:NSUTF8StringEncoding]]; + [self writeUTF8String:[NSString stringWithFormat:@"# Error: %@\n\n\n", [connection lastErrorMessage]]]; free(useRawDataForColumnAtIndex); free(useRawHexDataForColumnAtIndex); continue; @@ -361,10 +363,10 @@ [metaString setString:@""]; [metaString appendFormat:@"LOCK TABLES %@ WRITE;\n/*!40000 ALTER TABLE %@ DISABLE KEYS */;\n\n", [tableName backtickQuotedString], [tableName backtickQuotedString]]; - [[self exportOutputFile] writeData:[metaString dataUsingEncoding:[self exportOutputEncoding]]]; + [self writeString:metaString]; // Construct the start of the insertion command - [[self exportOutputFile] writeData:[[NSString stringWithFormat:@"INSERT INTO %@ (%@)\nVALUES", [tableName backtickQuotedString], [rawColumnNames componentsJoinedAndBacktickQuoted]] dataUsingEncoding:NSUTF8StringEncoding]]; + [self writeUTF8String:[NSString stringWithFormat:@"INSERT INTO %@ (%@)\nVALUES", [tableName backtickQuotedString], [rawColumnNames componentsJoinedAndBacktickQuoted]]]; // Iterate through the rows to construct a VALUES group for each j = 0, k = 0; @@ -384,7 +386,6 @@ [sqlExportPool release]; [errors release]; [sqlString release]; - [pool release]; free(useRawDataForColumnAtIndex); free(useRawHexDataForColumnAtIndex); @@ -474,6 +475,7 @@ NSString *data = [[NSString alloc] initWithData:object encoding:[self exportOutputEncoding]]; if (data == nil) { +#warning This can corrupt data! Check if this case ever happens and if so, export as hex-string data = [[NSString alloc] initWithData:object encoding:NSASCIIStringEncoding]; } @@ -496,7 +498,7 @@ queryLength += [sqlString length]; // Write this row to the file - [[self exportOutputFile] writeData:[sqlString dataUsingEncoding:NSUTF8StringEncoding]]; + [self writeUTF8String:sqlString]; // Clean autorelease pool if so decided earlier if (cleanAutoReleasePool) { @@ -507,13 +509,13 @@ } // Complete the command - [[self exportOutputFile] writeData:[@";\n\n" dataUsingEncoding:NSUTF8StringEncoding]]; + [self writeUTF8String:@";\n\n"]; // Unlock the table and re-enable keys if supported [metaString setString:@""]; [metaString appendFormat:@"/*!40000 ALTER TABLE %@ ENABLE KEYS */;\nUNLOCK TABLES;\n", [tableName backtickQuotedString]]; - [[self exportOutputFile] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; + [self writeUTF8String:metaString]; // Drain the autorelease pool [sqlExportPool release]; @@ -529,8 +531,7 @@ [errors appendFormat:@"%@\n", [connection lastErrorMessage]]; if ([self sqlOutputIncludeErrors]) { - [[self exportOutputFile] writeData:[[NSString stringWithFormat:@"# Error: %@\n", [connection lastErrorMessage]] - dataUsingEncoding:NSUTF8StringEncoding]]; + [self writeUTF8String:[NSString stringWithFormat:@"# Error: %@\n", [connection lastErrorMessage]]]; } } } @@ -552,7 +553,6 @@ if ([self isCancelled]) { [errors release]; [sqlString release]; - [pool release]; return; } @@ -577,20 +577,20 @@ [metaString appendString:@"DELIMITER ;\n/*!50003 SET SESSION SQL_MODE=@OLD_SQL_MODE */;\n"]; - [[self exportOutputFile] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; + [self writeUTF8String:metaString]; } if ([connection queryErrored]) { [errors appendFormat:@"%@\n", [connection lastErrorMessage]]; if ([self sqlOutputIncludeErrors]) { - [[self exportOutputFile] writeData:[[NSString stringWithFormat:@"# Error: %@\n", [connection lastErrorMessage]] dataUsingEncoding:NSUTF8StringEncoding]]; + [self writeUTF8String:[NSString stringWithFormat:@"# Error: %@\n", [connection lastErrorMessage]]]; } } } - // Add an additional separator between tables - [[self exportOutputFile] writeData:[@"\n\n" dataUsingEncoding:NSUTF8StringEncoding]]; + // Add an additional separat or between tables + [self writeUTF8String:@"\n\n"]; } // Process any deferred views, adding commands to delete the placeholder tables and add the actual views @@ -600,7 +600,6 @@ if ([self isCancelled]) { [errors release]; [sqlString release]; - [pool release]; return; } @@ -610,8 +609,8 @@ [metaString appendFormat:@"# Replace placeholder table for %@ with correct view syntax\n# ------------------------------------------------------------\n\n", tableName]; [metaString appendFormat:@"DROP TABLE %@;\n\n", [tableName backtickQuotedString]]; [metaString appendFormat:@"%@;\n", [viewSyntaxes objectForKey:tableName]]; - - [[self exportOutputFile] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; + + [self writeUTF8String:metaString]; } // Export procedures and functions @@ -621,7 +620,6 @@ if ([self isCancelled]) { [errors release]; [sqlString release]; - [pool release]; return; } @@ -653,7 +651,6 @@ if ([self isCancelled]) { [errors release]; [sqlString release]; - [pool release]; return; } @@ -669,7 +666,6 @@ [proceduresList release]; [errors release]; [sqlString release]; - [pool release]; return; } @@ -716,7 +712,7 @@ [errors appendFormat:@"%@\n", [connection lastErrorMessage]]; if ([self sqlOutputIncludeErrors]) { - [[self exportOutputFile] writeData:[[NSString stringWithFormat:@"# Error: %@\n", [connection lastErrorMessage]] dataUsingEncoding:NSUTF8StringEncoding]]; + [self writeUTF8String:[NSString stringWithFormat:@"# Error: %@\n", [connection lastErrorMessage]]]; } [proceduresList release]; continue; @@ -733,7 +729,7 @@ NSString *errorString = [NSString stringWithFormat:NSLocalizedString(@"Could not export the %@ '%@' because of a permissions error.\n", @"Procedure/function export permission error"), procedureType, procedureName]; [errors appendString:errorString]; if ([self sqlOutputIncludeErrors]) { - [[self exportOutputFile] writeData:[[NSString stringWithFormat:@"# Error: %@\n", errorString] dataUsingEncoding:NSUTF8StringEncoding]]; + [self writeUTF8String:[NSString stringWithFormat:@"# Error: %@\n", errorString]]; } [proceduresList release]; [procedureInfo release]; @@ -758,14 +754,14 @@ [metaString appendString:@"DELIMITER ;\n"]; - [[self exportOutputFile] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; + [self writeUTF8String:metaString]; } if ([connection queryErrored]) { [errors appendFormat:@"%@\n", [connection lastErrorMessage]]; if ([self sqlOutputIncludeErrors]) { - [[self exportOutputFile] writeData:[[NSString stringWithFormat:@"# Error: %@\n", [connection lastErrorMessage]] dataUsingEncoding:NSUTF8StringEncoding]]; + [self writeUTF8String:[NSString stringWithFormat:@"# Error: %@\n", [connection lastErrorMessage]]]; } } } @@ -782,7 +778,7 @@ [metaString appendString:@"/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n"]; // Write footer-type information to the file - [[self exportOutputFile] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; + [self writeUTF8String:metaString]; // Set export errors [self setSqlExportErrors:errors]; @@ -798,8 +794,6 @@ // Inform the delegate that the export process is complete [delegate performSelectorOnMainThread:@selector(sqlExportProcessComplete:) withObject:self waitUntilDone:NO]; - - [pool release]; } /** @@ -809,7 +803,7 @@ */ - (BOOL)didExportErrorsOccur { - return [[self sqlExportErrors] length]; + return ([[self sqlExportErrors] length] != 0); } /** @@ -885,8 +879,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/SPSQLExporterDelegate.m b/Source/SPSQLExporterDelegate.m index c450c42a..09e87cf8 100644 --- a/Source/SPSQLExporterDelegate.m +++ b/Source/SPSQLExporterDelegate.m @@ -31,31 +31,22 @@ #import "SPSQLExporterDelegate.h" #import "SPSQLExporter.h" #import "SPDatabaseDocument.h" +#import "SPExportInitializer.h" @implementation SPExportController (SPSQLExporterDelegate) - (void)sqlExportProcessWillBegin:(SPSQLExporter *)exporter { - [[exportProgressTitle onMainThread] setStringValue:NSLocalizedString(@"Exporting SQL", @"text showing that the application is exporting SQL")]; - [[exportProgressText onMainThread] setStringValue:NSLocalizedString(@"Dumping...", @"text showing that app is writing dump")]; + [exportProgressTitle setStringValue:NSLocalizedString(@"Exporting SQL", @"text showing that the application is exporting SQL")]; + [exportProgressText setStringValue:NSLocalizedString(@"Dumping...", @"text showing that app is writing dump")]; - [[exportProgressTitle onMainThread] displayIfNeeded]; - [[exportProgressText onMainThread] displayIfNeeded]; + [exportProgressTitle displayIfNeeded]; + [exportProgressText displayIfNeeded]; } - (void)sqlExportProcessComplete:(SPSQLExporter *)exporter { - [exportProgressIndicator stopAnimation:self]; - [NSApp endSheet:exportProgressWindow returnCode:0]; - [exportProgressWindow orderOut:self]; - - [tableDocumentInstance setQueryMode:SPInterfaceQueryMode]; - - // Restore the connection encoding to it's pre-export value - [tableDocumentInstance setConnectionEncoding:[NSString stringWithFormat:@"%@%@", previousConnectionEncoding, (previousConnectionEncodingViaLatin1) ? @"-" : @""] reloadingViews:NO]; - - // Display Growl notification - [self displayExportFinishedGrowlNotification]; + [self exportEnded]; // Check for errors and display the errors sheet if necessary if ([exporter didExportErrorsOccur]) { 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]; 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 <https://github.com/sequelpro/sequelpro> + +#import <Cocoa/Cocoa.h> + +@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 <https://github.com/sequelpro/sequelpro> + +#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/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]; diff --git a/Source/SPStringAdditions.h b/Source/SPStringAdditions.h index 5f1c0e6d..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; @@ -88,6 +95,10 @@ static inline id NSMutableAttributedStringAttributeAtIndex(NSMutableAttributedSt * Namely the following options will be applied when matching: * NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch|NSWidthInsensitiveSearch * Additionaly this method might match even when it should not. + * A regular substring test is always included. Therefore looking e.g. for "abc" in + * "axbxabc" would match as (axbx,"abc") and NOT as ("a",x,"b",xab,"c"). + * Partial submatches will likewise be optimized to return as few matches as possible. + * E.g. ".123" in "a._1_12_123" will return (a,".",_1_12_,"123") NOT (a,".",_,"1",_1,"2",_12,"3") * * @param other String to match against self * @param submatches Pass the pointer to a variable that will be set to an NSArray * diff --git a/Source/SPStringAdditions.m b/Source/SPStringAdditions.m index 26de14d7..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. */ @@ -535,9 +573,39 @@ static NSInteger _smallestOf(NSInteger a, NSInteger b, NSInteger c); tmpMatchStore[mergeTarget] = this; } } + matchCount = mergeTarget+1; + + //Next we want to merge non-adjacent matches that could be adjacent. Example: + // Haystack: "central_private_rabbit_park" + // Needle: "centralpark" + // Unoptimized: "central_private_rabbit_park" + // ^^^^^^^ ^ ^ ^ ^ = 5 + // Desired: "central_private_rabbit_park" + // ^^^^^^^ ^^^^ = 2 + // + // This time we start from the end (object K) and check if object K-1 can + // actually be placed directly in front of K and if so, merge them both into + // a new K-1 and shift down K+1 to K, K+2 to K+1, ... + for (NSUInteger k = matchCount - 1; k > 0; k--) { + NSRange my = tmpMatchStore[k]; + NSRange prev = tmpMatchStore[k-1]; + NSString *prevMatch = [self substringWithRange:prev]; + NSRange left = NSMakeRange(my.location - prev.length, prev.length); + NSString *myLeftSide = [self substringWithRange:left]; + if([prevMatch compare:myLeftSide options:opts] == NSOrderedSame) { + //yay, let's merge them + tmpMatchStore[k-1] = NSMakeRange(left.location, my.length+prev.length); + //we now have to shift down k+1 to k, k+2 to k+1, ... + for (NSUInteger n = k+1; n < matchCount; n++) { + tmpMatchStore[n-1] = tmpMatchStore[n]; + } + //merging means one match less in total + matchCount--; + } + } - NSMutableArray *combinedArray = [NSMutableArray arrayWithCapacity:mergeTarget+1]; - for (NSUInteger j = 0; j <= mergeTarget; j++) { + NSMutableArray *combinedArray = [NSMutableArray arrayWithCapacity:matchCount]; + for (NSUInteger j = 0; j < matchCount; j++) { [combinedArray addObject:[NSValue valueWithRange:tmpMatchStore[j]]]; } 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 0be238ba..ed01703f 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; } @@ -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 @@ -407,9 +406,6 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper // Reset table key store for use in argumentForRow: if (keys) SPClear(keys); - // Reset data column store - [dataColumns removeAllObjects]; - // Check the supplied table name. If it matches the old one, a reload is being performed; // reload the data in-place to maintain table state if possible. if ([selectedTable isEqualToString:newTableName]) { @@ -433,13 +429,18 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper [tableContentView deselectAll:self]; // Restore the table content view to the top left + // Note: This may cause the table view to reload it's data! [tableContentView scrollRowToVisible:0]; [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; } + + // Reset data column store + [dataColumns removeAllObjects]; // If no table has been supplied, reset the view to a blank table and disabled elements. if (!newTableName) { @@ -565,8 +566,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"]]; @@ -986,12 +986,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"), [tableDocumentInstance parentWindow], errorDetail); } #ifndef SP_CODA // Filter task came from filter table @@ -1124,8 +1125,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 +1579,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]; @@ -1809,9 +1816,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; @@ -1819,8 +1824,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:@""] @@ -1869,7 +1873,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; } @@ -2793,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]; } } } @@ -2851,8 +2860,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(); } @@ -2933,7 +2945,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; } @@ -3073,8 +3085,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 @""; @@ -3145,10 +3160,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; } } @@ -3315,12 +3328,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 @@ -3363,10 +3374,10 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper } } } - - if(fieldEditor) { - SPClear(fieldEditor); - } + + // this is a delegate method of the field editor controller. calling release + // now would risk a dealloc while it is still our parent on the stack: + [fieldEditor autorelease], fieldEditor = nil; [[tableContentView window] makeFirstResponder:tableContentView]; @@ -3416,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]; } } @@ -3440,8 +3451,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]; @@ -3453,8 +3467,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(); } @@ -3465,9 +3482,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]; @@ -3974,17 +3993,18 @@ 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]; - maxNumRowsIsEstimate = NO; - checkStatusCount = YES; - + maxNumRows = [rows integerValue]; + maxNumRowsIsEstimate = NO; + checkStatusCount = YES; + } // Otherwise, use the estimate count - } else { - maxNumRows = [[tableDataInstance statusValueForKey:@"Rows"] integerValue]; - maxNumRowsIsEstimate = YES; - checkStatusCount = YES; - } + else { + maxNumRows = (rows && ![rows isNSNull])? [rows integerValue] : 0; + maxNumRowsIsEstimate = YES; + checkStatusCount = YES; + } } // Check whether the estimated count requires updating, ie if the retrieved count exceeds it @@ -4085,7 +4105,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/SPTableContentDelegate.m b/Source/SPTableContentDelegate.m index 01252d4e..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]; } } } @@ -235,6 +235,11 @@ #endif if (tableView == tableContentView) { + // Nothing is editable while the field editor is running. + // This guards against a special case where accessibility services might + // check if a table field is editable while the sheet is running. + if (fieldEditor) return NO; + // Ensure that row is editable since it could contain "(not loaded)" columns together with // issue that the table has no primary key NSString *wherePart = [NSString stringWithString:[self argumentForRow:[tableContentView selectedRow]]]; @@ -250,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; } @@ -303,8 +311,6 @@ fieldEncoding = [columnDefinition objectForKey:@"charset_name"]; } - if (fieldEditor) SPClear(fieldEditor); - fieldEditor = [[SPFieldEditorController alloc] init]; [fieldEditor setEditedFieldInfo:[NSDictionary dictionaryWithObjectsAndKeys: @@ -709,8 +715,8 @@ /** * If the user selected a table cell which is a blob field and tried to edit it - * cancel the fieldEditor, display the field editor sheet instead for editing - * and re-enable the fieldEditor after editing. + * cancel the inline edit, display the field editor sheet instead for editing + * and re-enable inline editing after closing the sheet. */ - (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)aFieldEditor { @@ -767,6 +773,7 @@ // Cancel editing [control abortEditing]; + NSAssert(fieldEditor == nil, @"Method should not to be called while a field editor sheet is open!"); // Call the field editor sheet [self tableView:tableContentView shouldEditTableColumn:NSArrayObjectAtIndex([tableContentView tableColumns], column) row:row]; diff --git a/Source/SPTableCopy.m b/Source/SPTableCopy.m index cd3fc42e..07f03846 100644 --- a/Source/SPTableCopy.m +++ b/Source/SPTableCopy.m @@ -44,9 +44,9 @@ - (BOOL)copyTable:(NSString *)tableName from:(NSString *)sourceDB to:(NSString *)targetDB { NSString *createTableResult = [self _createTableStatementFor:tableName inDatabase:sourceDB]; - NSMutableString *createTableStatement = [[NSMutableString alloc] initWithString:createTableResult]; - if ([[createTableStatement substringToIndex:12] isEqualToString:@"CREATE TABLE"]) { + if ([createTableResult hasPrefix:@"CREATE TABLE"]) { + NSMutableString *createTableStatement = [[NSMutableString alloc] initWithString:createTableResult]; // Add the target DB name and the separator dot after "CREATE TABLE ". [createTableStatement insertString:@"." atIndex:13]; @@ -59,8 +59,6 @@ return ![connection queryErrored]; } - [createTableStatement release]; - return NO; } @@ -151,7 +149,10 @@ SPMySQLResult *theResult = [connection queryString:showCreateTableStatment]; - return [theResult numberOfRows] > 0 ? [[theResult getRowAsArray] objectAtIndex:1] : @""; + if([theResult numberOfRows] > 0) return [[theResult getRowAsArray] objectAtIndex:1]; + + NSLog(@"query <%@> failed to return the expected result.\n Error state: %@ (%lu)",showCreateTableStatment,[connection lastErrorMessage],[connection lastErrorID]); + return nil; } @end diff --git a/Source/SPTableData.m b/Source/SPTableData.m index 27dd4ea1..689ecc8f 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 @@ -490,7 +482,6 @@ SPOnewayAlertSheet( NSLocalizedString(@"Error retrieving table information", @"error retrieving table information message"), - nil, [NSApp mainWindow], errorMessage ); @@ -515,7 +506,6 @@ 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") ); @@ -525,27 +515,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]]; @@ -557,7 +566,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 /* @@ -568,15 +577,13 @@ returningInclusively: NO ignoringQuotedStrings: NO]; if(fieldName == nil || [fieldName length] == 0) { -#warning NSAlert from background thread! (This whole function needs improvement) NSBeep(); - NSAlert *alert = [[NSAlert alloc] init]; - [alert addButtonWithTitle:NSLocalizedString(@"OK", @"OK button")]; - [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"“%@” couldn't be parsed. You can edit the column setup but the column will not be shown in the Content view; please report this issue to the Sequel Pro team using the Help menu item.", @"“%@” couldn't be parsed. You can edit the column setup but the column will not be shown in the Content view; please report this issue to the Sequel Pro team using the Help menu item."), fieldsParser]]; - [alert setMessageText:NSLocalizedString(@"Error while parsing CREATE TABLE syntax",@"error while parsing CREATE TABLE syntax")]; - [alert setAlertStyle:NSCriticalAlertStyle]; - [alert runModal]; - [alert release]; + SPOnewayAlertSheetWithStyle( + NSLocalizedString(@"Error while parsing CREATE TABLE syntax",@"error while parsing CREATE TABLE syntax"), + nil, + nil, + [NSString stringWithFormat:NSLocalizedString(@"“%@” couldn't be parsed. You can edit the column setup but the column will not be shown in the Content view; please report this issue to the Sequel Pro team using the Help menu item.", @"“%@” couldn't be parsed. You can edit the column setup but the column will not be shown in the Content view; please report this issue to the Sequel Pro team using the Help menu item."), fieldsParser], + NSCriticalAlertStyle); continue; } //if the next character is again a backtick, we stumbled across an escaped backtick. we have to continue parsing. @@ -734,11 +741,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 +759,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 +776,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 +804,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 +814,6 @@ [encodingString release]; [tableColumns release]; - if (changeEncoding) [mySQLConnection restoreStoredEncoding]; - return tableData; } @@ -847,7 +853,6 @@ 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]] ); @@ -859,12 +864,17 @@ // 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]) { 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") ); @@ -883,7 +893,6 @@ 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]] ); @@ -994,9 +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]] + [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]; } @@ -1033,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]] + ); + } } } @@ -1081,7 +1099,6 @@ 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]] ); @@ -1250,7 +1267,8 @@ } else if ([detailString isEqualToString:@"ENUM"] || [detailString isEqualToString:@"SET"]) { [fieldDetails setObject:@"enum" forKey:@"typegrouping"]; } else if ([detailString isEqualToString:@"TINYTEXT"] || [detailString isEqualToString:@"TEXT"] - || [detailString isEqualToString:@"MEDIUMTEXT"] || [detailString isEqualToString:@"LONGTEXT"]) { + || [detailString isEqualToString:@"MEDIUMTEXT"] || [detailString isEqualToString:@"LONGTEXT"] + || [detailString isEqualToString:@"JSON"]) { // JSON is seen as a text type by us, but works a bit different (e.g. encoding is always "utf8mb4") [fieldDetails setObject:@"textdata" forKey:@"typegrouping"]; } else if ([detailString isEqualToString:@"POINT"] || [detailString isEqualToString:@"GEOMETRY"] || [detailString isEqualToString:@"LINESTRING"] || [detailString isEqualToString:@"POLYGON"] @@ -1342,7 +1360,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/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..1767d3b0 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,24 @@ // 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]]]]; + 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: %@", @"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]]]]]; } @@ -390,11 +399,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/SPTableRelations.m b/Source/SPTableRelations.m index 134aa2fb..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 @@ -526,10 +540,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.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 <NSTableViewDelegate, NSTableViewDataSource, NSComboBoxCellDataSource> @@ -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 1e0c9f5e..6e94bf88 100644 --- a/Source/SPTableStructure.m +++ b/Source/SPTableStructure.m @@ -46,15 +46,53 @@ #import "SPTableStructureLoading.h" #import "SPThreadAdditions.h" #import "SPServerSupport.h" +#import "SPExtendedTableInfo.h" #import <SPMySQL/SPMySQL.h> 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; +- (NSString *)_buildPartialColumnDefinitionString:(NSDictionary *)theRow; @end @@ -156,6 +194,7 @@ static NSString *SPRemoveFieldAndForeignKey = @"SPRemoveFieldAndForeignKey"; SPMySQLLongBlobType, SPMySQLBinaryType, SPMySQLVarBinaryType, + SPMySQLJsonType, SPMySQLEnumType, SPMySQLSetType, @"--------", @@ -252,8 +291,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 @@ -630,11 +669,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 @@ -740,6 +779,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; @@ -748,27 +910,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"]) { @@ -809,31 +960,29 @@ static NSString *SPRemoveFieldAndForeignKey = @"SPRemoveFieldAndForeignKey"; if(!specialFieldTypes) { - if ([fieldValidation isFieldTypeString:theRowType]) { + if ([theRowType isEqualToString:@"JSON"]) { + // we "see" JSON as a string, but it is not internally to MySQL and so doesn't allow CHARACTER SET/BINARY/COLLATE either. + } + else 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"])) { @@ -856,39 +1005,49 @@ static NSString *SPRemoveFieldAndForeignKey = @"SPRemoveFieldAndForeignKey"; // Don't provide any defaults for auto-increment fields if (![theRowExtra isEqualToString:@"AUTO_INCREMENT"]) { - + NSArray *matches; + NSString *defaultValue = [theRow objectForKey:@"default"]; // If a NULL value has been specified, and NULL is allowed, specify DEFAULT NULL - if ([[theRow objectForKey:@"default"] isEqualToString:[prefs objectForKey:SPNullValue]]) + if ([defaultValue isEqualToString:[prefs objectForKey:SPNullValue]]) { if ([[theRow objectForKey:@"null"] integerValue] == 1) { [queryString appendString:@"\n DEFAULT NULL"]; } } // 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"]) + else if ([theRowType isInArray:@[@"TIMESTAMP",@"DATETIME"]] && + [(matches = [[defaultValue 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) 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"])]; + } } // 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. - else if ([(NSString *)[theRow objectForKey:@"default"] length] && [theRowType isEqualToString:@"BIT"]) { - [queryString appendFormat:@"\n DEFAULT %@", [theRow objectForKey:@"default"]]; + else if ([defaultValue length] && [theRowType isEqualToString:@"BIT"]) { + [queryString appendFormat:@"\n DEFAULT %@", defaultValue]; } // Suppress appending DEFAULT clause for any numerics, date, time fields if default is empty to avoid error messages; - // also don't specify a default for TEXT/BLOB or geometry fields to avoid strict mode errors - else if (![(NSString *)[theRow objectForKey:@"default"] length] && ([fieldValidation isFieldTypeNumeric:theRowType] || [fieldValidation isFieldTypeDate:theRowType] || [theRowType hasSuffix:@"TEXT"] || [theRowType hasSuffix:@"BLOB"] || [fieldValidation isFieldTypeGeometry:theRowType])) { + // also don't specify a default for TEXT/BLOB, JSON or geometry fields to avoid strict mode errors + else if (![defaultValue length] && ([fieldValidation isFieldTypeNumeric:theRowType] || [fieldValidation isFieldTypeDate:theRowType] || [theRowType hasSuffix:@"TEXT"] || [theRowType hasSuffix:@"BLOB"] || [theRowType isEqualToString:@"JSON"] || [fieldValidation isFieldTypeGeometry:theRowType])) { ; } // Otherwise, use the provided default else { - [queryString appendFormat:@"\n DEFAULT %@", [mySQLConnection escapeAndQuoteString:[theRow objectForKey:@"default"]]]; + [queryString appendFormat:@"\n DEFAULT %@", [mySQLConnection escapeAndQuoteString:defaultValue]]; } } - if (![theRowExtra isEqualToString:@""] && ![theRowExtra isEqualToString:@"NONE"]) { + 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"]]; + } } } @@ -897,126 +1056,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 - [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]]; - - 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 - 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]]); - - 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:), nil, - [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, - [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 */ @@ -1070,9 +1115,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"]); } /** @@ -1493,4 +1536,302 @@ 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( + SPMySQLJsonType, + @"JSON", + NSLocalizedString(@"Limited to @@max_allowed_packet", @"range for json type"), + NSLocalizedString(@"A data type that validates JSON data on INSERT and internally stores it in a binary format that is both, more compact and faster to access than textual JSON.\nAvailable from MySQL 5.7.8.", @"description of json") + ), + 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. The value is not affected by any time zone setting. 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. The value is not affected by any time zone setting. 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 values displayed/stored are affected by the connection's @@time_zone setting.\nThe 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. The value is not affected by any time zone setting. 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. values 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 c027d339..755cc2b8 100644 --- a/Source/SPTableStructureDelegate.m +++ b/Source/SPTableStructureDelegate.m @@ -37,12 +37,36 @@ #import "SPTableFieldValidation.h" #import "SPTableStructureLoading.h" #import "SPServerSupport.h" +#import "SPTablesList.h" +#import "SPPillAttachmentCell.h" +#import "SPIdMenu.h" +#import "SPComboBoxCell.h" #import <SPMySQL/SPMySQL.h> +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; +- (NSString *)_buildPartialColumnDefinitionString:(NSDictionary *)theRow; + +- (void)_displayFieldTypeHelpIfPossible:(SPComboBoxCell *)cell; @end @@ -60,57 +84,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"]) { - NSInteger idx = 0; - - if ((idx = [[NSArrayObjectAtIndex(tableFields, rowIndex) objectForKey:@"encoding"] integerValue]) > 0 && idx < [encodingPopupCell numberOfItems]) { - NSString *enc = [[encodingPopupCell itemAtIndex:idx] title]; - - NSUInteger start = [enc rangeOfString:@"("].location + 1; - - collations = [databaseDataInstance getDatabaseCollationsForEncoding:[enc substringWithRange:NSMakeRange(start, [enc length] - start - 1)]]; - } - 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]] : @[]; - } - - [[tableColumn dataCell] removeAllItems]; - - if ([collations count] > 0) { - NSString *defaultCollation = [[tableDataInstance statusValues] objectForKey:@"collation"]; - - if (!defaultCollation) { - defaultCollation = [databaseDataInstance getDatabaseDefaultCollation]; - } - - [[tableColumn dataCell] addItemWithTitle:@""]; + NSString *tableEncoding = [tableDataInstance tableEncoding]; + 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"]; + //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 length]) { + collations = [databaseDataInstance getDatabaseCollationsForEncoding:columnEncoding]; + + if ([collations count] > 0) { + NSString *tableCollation = [[tableDataInstance statusValues] objectForKey:@"Collation"]; + + if (![tableCollation length]) { + tableCollation = [databaseDataInstance getDefaultCollationForEncoding:tableEncoding]; + } - BOOL useMonospacedFont = [prefs boolForKey:SPUseMonospacedFonts]; - CGFloat monospacedFontSize = [prefs floatForKey:SPMonospacedFontSize] > 0 ? [prefs floatForKey:SPMonospacedFontSize] : [NSFont smallSystemFontSize]; - - // Populate collation popup button - for (NSDictionary *collation in collations) - { - NSString *collationName = [collation objectForKey:@"COLLATION_NAME"]; - - [[tableColumn dataCell] addItemWithTitle:collationName]; - - // If this matches the table's collation, draw in gray - if ([collationName length] && [collationName isEqualToString:defaultCollation]) { - 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]; + 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]]; + } } + + // the popup cell is subclassed to take the representedObject instead of the item index + return columnCollation; } } + + return nil; + } + else if ([[tableColumn identifier] isEqualToString:@"encoding"]) { + // the encoding menu was already configured during setTableDetails: + NSString *columnEncoding = [rowData objectForKey:@"encodingName"]; + + if([columnEncoding length]) { + NSInteger idx = [encodingPopupCell indexOfItemWithRepresentedObject:columnEncoding]; + if(idx > 0) return @(idx); + } + + return @0; } else if ([[tableColumn identifier] isEqualToString:@"Extra"]) { id dataCell = [tableColumn dataCell]; @@ -126,34 +161,51 @@ } } - 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 { - // 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]) { - [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"]) { + //the popup button is subclassed to return the representedObject instead of the item index + NSString *newCollation = anObject; + + 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]; } } @@ -205,9 +257,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"]; @@ -223,10 +274,10 @@ [tableSourceView reloadData]; } + return; } - else { - [currentRow setObject:(anObject) ? anObject : @"" forKey:[aTableColumn identifier]]; - } + + [currentRow setObject:(anObject) ? anObject : @"" forKey:[aTableColumn identifier]]; } /** @@ -255,7 +306,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; } @@ -271,7 +322,7 @@ - (NSDragOperation)tableView:(NSTableView*)tableView validateDrop:(id <NSDraggingInfo>)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; @@ -305,104 +356,30 @@ 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 = @""; - - 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)]; - - [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]; - } + 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 [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]; @@ -439,10 +416,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; @@ -539,10 +514,9 @@ [self cancelRowEditing]; return YES; - } - else { - return NO; } + + return NO; } /** @@ -559,20 +533,21 @@ } else { // Validate cell against current field type - NSString *rowType = @""; + NSString *rowType; NSDictionary *row = NSArrayObjectAtIndex(tableFields, rowIndex); if ((rowType = [row objectForKey:@"type"])) { rowType = [[rowType stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString]; } - // Only string fields allow encoding settings + // Only string fields allow encoding settings, but JSON only uses utf8mb4 if (([[tableColumn identifier] isEqualToString:@"encoding"])) { - [aCell setEnabled:([fieldValidation isFieldTypeString:rowType] && [[tableDocumentInstance serverSupport] supportsPost41CharacterSetHandling])]; + [aCell setEnabled:(![rowType isEqualToString:@"JSON"] && [fieldValidation isFieldTypeString:rowType] && [[tableDocumentInstance serverSupport] supportsPost41CharacterSetHandling])]; } // Only string fields allow collation settings and string field is not set to BINARY since BINARY sets the collation to *_bin else if ([[tableColumn identifier] isEqualToString:@"collation"]) { + // JSON always uses utf8mb4_bin which is already covered by this logic [aCell setEnabled:([fieldValidation isFieldTypeString:rowType] && [[row objectForKey:@"binary"] integerValue] == 0 && [[tableDocumentInstance serverSupport] supportsPost41CharacterSetHandling])]; } @@ -583,12 +558,13 @@ // Check if BINARY is allowed else if ([[tableColumn identifier] isEqualToString:@"binary"]) { - [aCell setEnabled:([fieldValidation isFieldTypeAllowBinary:rowType])]; + // JSON always uses utf8mb4_bin + [aCell setEnabled:(![rowType isEqualToString:@"JSON"] && [fieldValidation isFieldTypeAllowBinary:rowType])]; } - // TEXT, BLOB, and GEOMETRY fields don't allow a DEFAULT + // TEXT, BLOB, GEOMETRY and JSON fields don't allow a DEFAULT else if ([[tableColumn identifier] isEqualToString:@"default"]) { - [aCell setEnabled:([rowType hasSuffix:@"TEXT"] || [rowType hasSuffix:@"BLOB"] || [fieldValidation isFieldTypeGeometry:rowType]) ? NO : YES]; + [aCell setEnabled:([rowType hasSuffix:@"TEXT"] || [rowType hasSuffix:@"BLOB"] || [rowType isEqualToString:@"JSON"] || [fieldValidation isFieldTypeGeometry:rowType]) ? NO : YES]; } // Check allow NULL @@ -598,10 +574,11 @@ [[[tableDataInstance statusValueForKey:@"Engine"] uppercaseString] isEqualToString:@"CSV"]) ? NO : YES]; } - // TEXT, BLOB, date, and GEOMETRY fields don't allow a length + // TEXT, BLOB, date, GEOMETRY and JSON fields don't allow a length else if ([[tableColumn identifier] isEqualToString:@"length"]) { [aCell setEnabled:([rowType hasSuffix:@"TEXT"] || - [rowType hasSuffix:@"BLOB"] || + [rowType hasSuffix:@"BLOB"] || + [rowType isEqualToString:@"JSON"] || ([fieldValidation isFieldTypeDate:rowType] && ![[tableDocumentInstance serverSupport] supportsFractionalSeconds] && ![rowType isEqualToString:@"YEAR"]) || [fieldValidation isFieldTypeGeometry:rowType]) ? NO : YES]; } @@ -674,9 +651,217 @@ 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 @""; } +- (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]; + [cell scrollItemAtIndexToTop: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]]; + + NSRect rect = [as boundingRectWithSize:NSMakeSize([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) + +- (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 + for(NSMenuItem *item in [menu itemArray]) { + if([item attributedTitle]) { + [item setAttributedTitle:nil]; + } + } + + NSDictionary *rowData = NSArrayObjectAtIndex(tableFields, [tableSourceView selectedRow]); + + if([[menu menuId] 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 menuId] 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/Source/SPTableStructureLoading.m b/Source/SPTableStructureLoading.m index 5a1e8e10..b7f8e748 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 <SPMySQL/SPMySQL.h> @@ -77,10 +79,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; @@ -114,115 +117,97 @@ // 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]) { - - 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]; - } - } - 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:[NSNumber numberWithInteger:(selectedIndex + 1)] forKey:@"encoding"]; - break; + if([type isEqualToString:@"JSON"]) { + // MySQL 5.7 manual: + // "MySQL handles strings used in JSON context using the utf8mb4 character set and utf8mb4_bin collation. + // Strings in other character set are converted to utf8mb4 as necessary." + [theField setObject:@"utf8mb4" forKey:@"encodingName"]; + [theField setObject:@"utf8mb4_bin" forKey:@"collationName"]; + [theField setObject:@1 forKey:@"binary"]; + } + else if ([fieldValidation isFieldTypeString:type]) { + // 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. + 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"]; - - // 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"]]; @@ -250,9 +235,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"]; } } @@ -287,10 +278,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/SPTableTriggers.m b/Source/SPTableTriggers.m index 179e509e..96d2fb40 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; @@ -199,7 +213,7 @@ static const NSString *SPTriggerSQLMode = @"TriggerSQLMode"; 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]]); @@ -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; } @@ -256,7 +281,7 @@ static const NSString *SPTriggerSQLMode = @"TriggerSQLMode"; 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]); } @@ -400,11 +425,11 @@ static const NSString *SPTriggerSQLMode = @"TriggerSQLMode"; 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; } @@ -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; +} diff --git a/Source/SPTableView.m b/Source/SPTableView.m index 89550dd8..84e43a26 100644 --- a/Source/SPTableView.m +++ b/Source/SPTableView.m @@ -61,13 +61,18 @@ return self; } +- (void)dealloc +{ + [super dealloc]; +} + - (void) awakeFromNib { [super setDoubleAction:@selector(_doubleClickAction)]; if ([NSTableView instancesRespondToSelector:@selector(awakeFromNib)]) { [super awakeFromNib]; -} + } } #pragma mark - @@ -88,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]; } /** @@ -194,8 +201,9 @@ (![[[[self delegate] class] description] isEqualToString:@"SPConnectionController"])) { // Ensure that editing is permitted - if (![[self delegate] tableView:self shouldEditTableColumn:[[self tableColumns] objectAtIndex:0] row:[self selectedRow]]) return; - + if(![[self delegate] respondsToSelector:@selector(tableView:shouldEditTableColumn:row:)]) return; // disallow by default + if(![[self delegate] tableView:self shouldEditTableColumn:[[self tableColumns] objectAtIndex:0] row:[self selectedRow]]) return; + // Trigger a cell edit [self editColumn:0 row:[self selectedRow] withEvent:nil select:YES]; diff --git a/Source/SPTablesList.m b/Source/SPTablesList.m index 34a14082..92f7cae5 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}]; } /** @@ -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 @@ -1577,7 +1578,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 +1603,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 @@ -1611,7 +1612,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 @@ -1634,7 +1635,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{ @@ -2266,10 +2267,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 +2415,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 @@ -2452,7 +2447,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 +2492,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 +2544,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 +2560,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 +2588,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") + ); } } @@ -2630,7 +2635,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 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/SPTextView.h b/Source/SPTextView.h index 07e42e43..4289bd40 100644 --- a/Source/SPTextView.h +++ b/Source/SPTextView.h @@ -38,6 +38,18 @@ @class SPCopyTable; @class NoodleLineNumberView; +typedef struct { + NSInteger location; // snippet location + NSInteger length; // snippet length + NSInteger task; // snippet task : -1 not valid, 0 select snippet +} SnippetControlInfo; + +typedef struct { + NSInteger snippet; // mirrored snippet index + NSInteger location; // mirrored snippet location + NSInteger length; // mirrored snippet length +} MirrorControlInfo; + @interface SPTextView : NSTextView <NSTextStorageDelegate> { IBOutlet SPDatabaseDocument *tableDocumentInstance; @@ -71,8 +83,8 @@ SPMySQLConnection *mySQLConnection; NSInteger mySQLmajorVersion; - NSInteger snippetControlArray[20][3]; - NSInteger snippetMirroredControlArray[20][3]; + SnippetControlInfo snippetControlArray[20]; + MirrorControlInfo snippetMirroredControlArray[20]; NSInteger snippetControlCounter; NSInteger snippetControlMax; NSInteger currentSnippetIndex; diff --git a/Source/SPTextView.m b/Source/SPTextView.m index 12d6ed13..3c6d7b53 100644 --- a/Source/SPTextView.m +++ b/Source/SPTextView.m @@ -1552,18 +1552,20 @@ static inline NSPoint SPPointOnLine(NSPoint a, NSPoint b, CGFloat t) { return NS NSInteger i, j, k, deltaLength; NSRange mirroredRange; + SnippetControlInfo *currentSnippetRef = &snippetControlArray[currentSnippetIndex]; // Go through each defined mirrored snippet and update it for(i=0; i<=mirroredCounter; i++) { - if(snippetMirroredControlArray[i][0] == currentSnippetIndex) { + MirrorControlInfo *mirrorRef = &snippetMirroredControlArray[i]; + if(mirrorRef->snippet == currentSnippetIndex) { - deltaLength = snippetControlArray[currentSnippetIndex][1]-snippetMirroredControlArray[i][2]; + deltaLength = currentSnippetRef->length - mirrorRef->length; - mirroredRange = NSMakeRange(snippetMirroredControlArray[i][1], snippetMirroredControlArray[i][2]); + mirroredRange = NSMakeRange(mirrorRef->location, mirrorRef->length); NSString *mirroredString = nil; // For safety reasons @try{ - mirroredString = [[self string] substringWithRange:NSMakeRange(snippetControlArray[currentSnippetIndex][0], snippetControlArray[currentSnippetIndex][1])]; + mirroredString = [[self string] substringWithRange:NSMakeRange(currentSnippetRef->location, currentSnippetRef->length)]; } @catch(id ae) { NSLog(@"Error while parsing for mirrored snippets. %@", [ae description]); @@ -1576,26 +1578,26 @@ static inline NSPoint SPPointOnLine(NSPoint a, NSPoint b, CGFloat t) { return NS [self shouldChangeTextInRange:mirroredRange replacementString:mirroredString]; [self replaceCharactersInRange:mirroredRange withString:mirroredString]; - snippetMirroredControlArray[i][2] = snippetControlArray[currentSnippetIndex][1]; + mirrorRef->length = currentSnippetRef->length; // If a completion list is open adjust the theCharRange and theParseRange if a mirrored snippet // was updated which is located before the initial position - if(completionIsOpen && snippetMirroredControlArray[i][1] < (NSInteger)completionParseRangeLocation) + if(completionIsOpen && mirrorRef->location < (NSInteger)completionParseRangeLocation) [completionPopup adjustWorkingRangeByDelta:deltaLength]; // Adjust all other snippets accordingly for(j=0; j<=snippetControlMax; j++) { - if(snippetControlArray[j][0] > -1) { - if(snippetControlArray[j][0]+snippetControlArray[j][1]>=snippetMirroredControlArray[i][1]) { - snippetControlArray[j][0] += deltaLength; + if(snippetControlArray[j].location > -1) { + if(snippetControlArray[j].location+snippetControlArray[j].length >= mirrorRef->location) { + snippetControlArray[j].location += deltaLength; } } } // Adjust all mirrored snippets accordingly for(k=0; k<=mirroredCounter; k++) { if(i != k) { - if(snippetMirroredControlArray[k][1] > snippetMirroredControlArray[i][1]) { - snippetMirroredControlArray[k][1] += deltaLength; + if(snippetMirroredControlArray[k].location > mirrorRef->location) { + snippetMirroredControlArray[k].location += deltaLength; } } } @@ -1625,15 +1627,16 @@ static inline NSPoint SPPointOnLine(NSPoint a, NSPoint b, CGFloat t) { return NS // Place the caret at the end of the query favorite snippet // and finish snippet editing if(currentSnippetIndex == snippetControlMax) { - [self setSelectedRange:NSMakeRange(snippetControlArray[snippetControlMax][0] + snippetControlArray[snippetControlMax][1], 0)]; + [self setSelectedRange:NSMakeRange(snippetControlArray[snippetControlMax].location + snippetControlArray[snippetControlMax].length, 0)]; [self endSnippetSession]; return; } - if(currentSnippetIndex >= 0 && currentSnippetIndex < 20) { - if(snippetControlArray[currentSnippetIndex][2] == 0) { + if(currentSnippetIndex >= 0 && currentSnippetIndex < COUNT_OF(snippetControlArray)) { + SnippetControlInfo *currentSnippetRef = &snippetControlArray[currentSnippetIndex]; + if(currentSnippetRef->task == 0) { - NSRange r1 = NSMakeRange(snippetControlArray[currentSnippetIndex][0], snippetControlArray[currentSnippetIndex][1]); + NSRange r1 = NSMakeRange(currentSnippetRef->location, currentSnippetRef->length); NSRange r2; // Ensure the selection for nested snippets if it is at very end of the text buffer @@ -1718,13 +1721,9 @@ static inline NSPoint SPPointOnLine(NSPoint a, NSPoint b, CGFloat t) { return NS mirroredCounter = -1; // reset snippet array - for(i=0; i<20; i++) { - snippetControlArray[i][0] = -1; // snippet location - snippetControlArray[i][1] = -1; // snippet length - snippetControlArray[i][2] = -1; // snippet task : -1 not valid, 0 select snippet - snippetMirroredControlArray[i][0] = -1; // mirrored snippet index - snippetMirroredControlArray[i][1] = -1; // mirrored snippet location - snippetMirroredControlArray[i][2] = -1; // mirrored snippet length + for(i=0; i<COUNT_OF(snippetControlArray); i++) { + snippetControlArray[i] = (SnippetControlInfo){ -1, -1, -1}; + snippetMirroredControlArray[i] = (MirrorControlInfo){-1, -1, -1}; } if(theSnippet == nil || ![theSnippet length]) return; @@ -1844,8 +1843,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:@""]; @@ -1858,24 +1860,25 @@ static inline NSPoint SPPointOnLine(NSPoint a, NSPoint b, CGFloat t) { return NS [snip flushCachedRegexData]; // Store found snippet range - snippetControlArray[snipCnt][0] = snipRange.location + targetRange.location; - snippetControlArray[snipCnt][1] = [theHintString length]; - snippetControlArray[snipCnt][2] = 0; + snippetControlArray[snipCnt].location = snipRange.location + targetRange.location; + snippetControlArray[snipCnt].length = [theHintString length]; + snippetControlArray[snipCnt].task = 0; [theHintString release]; // Adjust successive snippets - for(i=0; i<20; i++) - if(snippetControlArray[i][0] > -1 && i != snipCnt && snippetControlArray[i][0] > snippetControlArray[snipCnt][0]) - snippetControlArray[i][0] -= 3+((snipCnt>9)?2:1); + for(i=0; i<COUNT_OF(snippetControlArray); i++) + if(snippetControlArray[i].location > -1 && i != snipCnt && snippetControlArray[i].location > snippetControlArray[snipCnt].location) + snippetControlArray[i].location -= 3+((snipCnt>9)?2:1); } // Parse for mirrored snippets while([snip isMatchedByRegex:mirror_re]) { mirroredCounter++; - if(mirroredCounter > 19) { - NSLog(@"Only 20 mirrored snippet placeholders allowed."); + if(mirroredCounter >= COUNT_OF(snippetMirroredControlArray)) { + NSLog(@"Only %lu mirrored snippet placeholders allowed.",COUNT_OF(snippetMirroredControlArray)); + mirroredCounter--; //go back by one or the code below will do an out-of-bounds array access NSBeep(); break; } else { @@ -1894,14 +1897,14 @@ static inline NSPoint SPPointOnLine(NSPoint a, NSPoint b, CGFloat t) { return NS [snip flushCachedRegexData]; // Store found mirrored snippet range - snippetMirroredControlArray[mirroredCounter][0] = snipCnt; - snippetMirroredControlArray[mirroredCounter][1] = snipRange.location + targetRange.location; - snippetMirroredControlArray[mirroredCounter][2] = 0; + snippetMirroredControlArray[mirroredCounter].snippet = snipCnt; + snippetMirroredControlArray[mirroredCounter].location = snipRange.location + targetRange.location; + snippetMirroredControlArray[mirroredCounter].length = 0; // Adjust successive snippets - for(i=0; i<20; i++) - if(snippetControlArray[i][0] > -1 && snippetControlArray[i][0] > snippetMirroredControlArray[mirroredCounter][1]) - snippetControlArray[i][0] -= 1+((snipCnt>9)?2:1); + for(i=0; i<COUNT_OF(snippetControlArray); i++) + if(snippetControlArray[i].location > -1 && snippetControlArray[i].location > snippetMirroredControlArray[mirroredCounter].location) + snippetControlArray[i].location -= 1+((snipCnt>9)?2:1); [snip flushCachedRegexData]; } @@ -1909,28 +1912,29 @@ static inline NSPoint SPPointOnLine(NSPoint a, NSPoint b, CGFloat t) { return NS // Preset mirrored snippets with according snippet content if(mirroredCounter > -1) { for(i=0; i<=mirroredCounter; i++) { - if(snippetControlArray[snippetMirroredControlArray[i][0]][0] > -1 && snippetControlArray[snippetMirroredControlArray[i][0]][1] > 0) { - [snip replaceCharactersInRange:NSMakeRange(snippetMirroredControlArray[i][1]-targetRange.location, snippetMirroredControlArray[i][2]) - withString:[snip substringWithRange:NSMakeRange(snippetControlArray[snippetMirroredControlArray[i][0]][0]-targetRange.location, snippetControlArray[snippetMirroredControlArray[i][0]][1])]]; - snippetMirroredControlArray[i][2] = snippetControlArray[snippetMirroredControlArray[i][0]][1]; + MirrorControlInfo *mirrorRef = &snippetMirroredControlArray[i]; + SnippetControlInfo *snippetRef = &snippetControlArray[mirrorRef->snippet]; + if(snippetRef->location > -1 && snippetRef->length > 0) { + NSRange copyToRange = NSMakeRange(mirrorRef->location-targetRange.location, mirrorRef->length); + NSRange copyFromRange = NSMakeRange(snippetRef->location-targetRange.location, snippetRef->length); + [snip replaceCharactersInRange:copyToRange withString:[snip substringWithRange:copyFromRange]]; + mirrorRef->length = snippetRef->length; } // Adjust successive snippets - for(j=0; j<20; j++) - if(snippetControlArray[j][0] > -1 && snippetControlArray[j][0] > snippetMirroredControlArray[i][1]) - snippetControlArray[j][0] += snippetControlArray[snippetMirroredControlArray[i][0]][1]; + for(j=0; j<COUNT_OF(snippetControlArray); j++) + if(snippetControlArray[j].location > -1 && snippetControlArray[j].location > mirrorRef->location) + snippetControlArray[j].location += snippetRef->length; // Adjust successive mirrored snippets for(j=0; j<=mirroredCounter; j++) - if(snippetMirroredControlArray[j][1] > snippetMirroredControlArray[i][1]) - snippetMirroredControlArray[j][1] += snippetControlArray[snippetMirroredControlArray[i][0]][1]; + if(snippetMirroredControlArray[j].location > mirrorRef->location) + snippetMirroredControlArray[j].location += snippetRef->length; } } if(snippetControlCounter > -1) { // Store the end for tab out snippetControlMax++; - snippetControlArray[snippetControlMax][0] = targetRange.location + [snip length]; - snippetControlArray[snippetControlMax][1] = 0; - snippetControlArray[snippetControlMax][2] = 0; + snippetControlArray[snippetControlMax] = (SnippetControlInfo){targetRange.location + [snip length], 0, 0}; } // unescape escaped snippets and re-adjust successive snippet locations : \${1:a} → ${1:a} @@ -1942,13 +1946,13 @@ static inline NSPoint SPPointOnLine(NSPoint a, NSPoint b, CGFloat t) { return NS NSInteger loc = escapeRange.location + targetRange.location; [snip flushCachedRegexData]; for(i=0; i<=snippetControlMax; i++) - if(snippetControlArray[i][0] > -1 && snippetControlArray[i][0] > loc) - snippetControlArray[i][0]--; + if(snippetControlArray[i].location > -1 && snippetControlArray[i].location > loc) + snippetControlArray[i].location--; // Adjust mirrored snippets if(mirroredCounter > -1) for(i=0; i<=mirroredCounter; i++) - if(snippetMirroredControlArray[i][0] > -1 && snippetMirroredControlArray[i][1] > loc) - snippetMirroredControlArray[i][1]--; + if(snippetMirroredControlArray[i].snippet > -1 && snippetMirroredControlArray[i].location > loc) + snippetMirroredControlArray[i].location--; } } @@ -1977,8 +1981,8 @@ static inline NSPoint SPPointOnLine(NSPoint a, NSPoint b, CGFloat t) { return NS if(snippetControlCounter > -1) { // Find and select first defined snippet currentSnippetIndex = 0; - // Look for next defined snippet since snippet numbers must not serial like 1, 5, and 12 e.g. - while(snippetControlArray[currentSnippetIndex][0] == -1 && currentSnippetIndex < 20) + // Look for next defined snippet since snippet numbers might not be serial like 1, 5, and 12 e.g. + while(snippetControlArray[currentSnippetIndex].location == -1 && currentSnippetIndex < COUNT_OF(snippetControlArray)) currentSnippetIndex++; [self selectCurrentSnippet]; } @@ -2023,9 +2027,9 @@ static inline NSPoint SPPointOnLine(NSPoint a, NSPoint b, CGFloat t) { return NS for(i=0; i<=snippetControlMax; i++) { j++; foundSnippetIndices[j] = 0; - if(snippetControlArray[i][0] != -1 - && caretPos >= snippetControlArray[i][0] - && caretPos <= snippetControlArray[i][0] + snippetControlArray[i][1]) { + if(snippetControlArray[i].location != -1 + && caretPos >= snippetControlArray[i].location + && caretPos <= snippetControlArray[i].location + snippetControlArray[i].length) { foundSnippetIndices[j] = 1; if(i == currentSnippetIndex) @@ -2046,11 +2050,11 @@ static inline NSPoint SPPointOnLine(NSPoint a, NSPoint b, CGFloat t) { return NS if(foundSnippetIndices[i] == 1) { if(curIndex == -1) { curIndex = i; - smallestLength = snippetControlArray[i][1]; + smallestLength = snippetControlArray[i].length; } else { - if(smallestLength > snippetControlArray[i][1]) { + if(smallestLength > snippetControlArray[i].length) { curIndex = i; - smallestLength = snippetControlArray[i][1]; + smallestLength = snippetControlArray[i].length; } } } @@ -2189,13 +2193,13 @@ static inline NSPoint SPPointOnLine(NSPoint a, NSPoint b, CGFloat t) { return NS currentSnippetIndex--; - // Look for previous defined snippet since snippet numbers must not serial like 1, 5, and 12 e.g. - while(snippetControlArray[currentSnippetIndex][0] == -1 && currentSnippetIndex > -2) + // Look for previous defined snippet since snippet numbers might not be serial like 1, 5, and 12 e.g. + while(snippetControlArray[currentSnippetIndex].location == -1 && currentSnippetIndex > -2) currentSnippetIndex--; if(currentSnippetIndex < 0) { currentSnippetIndex = 0; - while(snippetControlArray[currentSnippetIndex][0] == -1 && currentSnippetIndex < 20) + while(snippetControlArray[currentSnippetIndex].location == -1 && currentSnippetIndex < COUNT_OF(snippetControlArray)) currentSnippetIndex++; NSBeep(); } @@ -2207,8 +2211,8 @@ static inline NSPoint SPPointOnLine(NSPoint a, NSPoint b, CGFloat t) { return NS currentSnippetIndex++; - // Look for next defined snippet since snippet numbers must not serial like 1, 5, and 12 e.g. - while(snippetControlArray[currentSnippetIndex][0] == -1 && currentSnippetIndex < 20) + // Look for next defined snippet since snippet numbers might not be serial like 1, 5, and 12 e.g. + while(snippetControlArray[currentSnippetIndex].location == -1 && currentSnippetIndex < COUNT_OF(snippetControlArray)) currentSnippetIndex++; if(currentSnippetIndex > snippetControlMax) { // for safety reasons @@ -2975,7 +2979,7 @@ static inline NSPoint SPPointOnLine(NSPoint a, NSPoint b, CGFloat t) { return NS // Is the caret still inside a snippet if([self checkForCaretInsideSnippet]) { for(NSInteger i=0; i<snippetControlMax; i++) { - if(snippetControlArray[i][0] > -1) { + if(snippetControlArray[i].location > -1) { // choose the colors for the snippet parts if(i == currentSnippetIndex) { [[NSColor colorWithCalibratedRed:1.0f green:0.6f blue:0.0f alpha:0.4f] setFill]; @@ -2984,7 +2988,7 @@ static inline NSPoint SPPointOnLine(NSPoint a, NSPoint b, CGFloat t) { return NS [[NSColor colorWithCalibratedRed:1.0f green:0.8f blue:0.2f alpha:0.2f] setFill]; [[NSColor colorWithCalibratedRed:1.0f green:0.8f blue:0.2f alpha:0.5f] setStroke]; } - NSBezierPath *snippetPath = [self roundedBezierPathAroundRange: NSMakeRange(snippetControlArray[i][0],snippetControlArray[i][1]) ]; + NSBezierPath *snippetPath = [self roundedBezierPathAroundRange: NSMakeRange(snippetControlArray[i].location,snippetControlArray[i].length) ]; [snippetPath fill]; [snippetPath stroke]; } @@ -3203,18 +3207,16 @@ static inline NSPoint SPPointOnLine(NSPoint a, NSPoint b, CGFloat t) { return NS */ - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { - - // Enable or disable the search in the MySQL help menu item depending on whether there is a + // Enable or disable the search in the MySQL help menu item depending on whether there is a // selection and whether it is a reasonable length. if ([menuItem action] == @selector(showMySQLHelpForCurrentWord:)) { - if ([self selectedRange].length > 0) { - [menuItem setTitle:NSLocalizedString(@"MySQL Help for Selection", @"MySQL Help for Selection")]; - } else { - [menuItem setTitle: NSLocalizedString(@"MySQL Help for Word", @"MySQL Help for Word")]; - } - + if ([self selectedRange].length > 0) { + [menuItem setTitle:NSLocalizedString(@"MySQL Help for Selection", @"MySQL Help for Selection")]; + } else { + [menuItem setTitle: NSLocalizedString(@"MySQL Help for Word", @"MySQL Help for Word")]; + } NSUInteger stringSize = [self getRangeForCurrentWord].length; - return (stringSize || stringSize > 64); + return (0 < stringSize && stringSize < 65); // 1 ≤ stringSize ≤ 64 } // Enable Copy as RTF if something is selected if ([menuItem action] == @selector(copyAsRTF)) { @@ -3231,7 +3233,7 @@ static inline NSPoint SPPointOnLine(NSPoint a, NSPoint b, CGFloat t) { return NS return NO; } - return YES; + return [super validateMenuItem:menuItem]; } /** @@ -3333,20 +3335,19 @@ static inline NSPoint SPPointOnLine(NSPoint a, NSPoint b, CGFloat t) { return NS // Re-calculate snippet ranges if snippet session is active if(snippetControlCounter > -1 && !snippetWasJustInserted && !isProcessingMirroredSnippets) { // Remove any fully nested snippets relative to the current snippet which was edited - NSInteger currentSnippetLocation = snippetControlArray[currentSnippetIndex][0]; - NSInteger currentSnippetMaxRange = snippetControlArray[currentSnippetIndex][0] + snippetControlArray[currentSnippetIndex][1]; + SnippetControlInfo *currentSnippetRef = &snippetControlArray[currentSnippetIndex]; + NSInteger currentSnippetLocation = currentSnippetRef->location; + NSInteger currentSnippetMaxRange = currentSnippetRef->location + currentSnippetRef->length; NSInteger i; for(i=0; i<snippetControlMax; i++) { - if(snippetControlArray[i][0] > -1 + if(snippetControlArray[i].location > -1 && i != currentSnippetIndex - && snippetControlArray[i][0] >= currentSnippetLocation - && snippetControlArray[i][0] <= currentSnippetMaxRange - && snippetControlArray[i][0] + snippetControlArray[i][1] >= currentSnippetLocation - && snippetControlArray[i][0] + snippetControlArray[i][1] <= currentSnippetMaxRange + && snippetControlArray[i].location >= currentSnippetLocation + && snippetControlArray[i].location <= currentSnippetMaxRange + && snippetControlArray[i].location + snippetControlArray[i].length >= currentSnippetLocation + && snippetControlArray[i].location + snippetControlArray[i].length <= currentSnippetMaxRange ) { - snippetControlArray[i][0] = -1; - snippetControlArray[i][1] = -1; - snippetControlArray[i][2] = -1; + snippetControlArray[i] = (SnippetControlInfo){-1, -1, -1}; } } @@ -3354,26 +3355,26 @@ static inline NSPoint SPPointOnLine(NSPoint a, NSPoint b, CGFloat t) { return NS NSUInteger changeInLength = [textStore changeInLength]; // Adjust length change to current snippet - snippetControlArray[currentSnippetIndex][1] += changeInLength; + currentSnippetRef->length += changeInLength; // If length < 0 break snippet input - if(snippetControlArray[currentSnippetIndex][1] < 0) { + if(currentSnippetRef->length < 0) { [self endSnippetSession]; } else { // Adjust start position of snippets after caret position for(i=0; i<=snippetControlMax; i++) { - if(snippetControlArray[i][0] > -1 && i != currentSnippetIndex) { - if(editStartPosition < snippetControlArray[i][0]) { - snippetControlArray[i][0] += changeInLength; - } else if(editStartPosition >= snippetControlArray[i][0] && editStartPosition <= snippetControlArray[i][0] + snippetControlArray[i][1]) { - snippetControlArray[i][1] += changeInLength; + if(snippetControlArray[i].location > -1 && i != currentSnippetIndex) { + if(editStartPosition < snippetControlArray[i].location) { + snippetControlArray[i].location += changeInLength; + } else if(editStartPosition >= snippetControlArray[i].location && editStartPosition <= snippetControlArray[i].location + snippetControlArray[i].length) { + snippetControlArray[i].length += changeInLength; } } } // Adjust start position of mirrored snippets after caret position if(mirroredCounter > -1) for(i=0; i<=mirroredCounter; i++) { - if(editStartPosition < snippetMirroredControlArray[i][1]) { - snippetMirroredControlArray[i][1] += changeInLength; + if(editStartPosition < snippetMirroredControlArray[i].location) { + snippetMirroredControlArray[i].location += changeInLength; } } } diff --git a/Source/SPTextViewAdditions.m b/Source/SPTextViewAdditions.m index 56103a53..88096a8f 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] + ); } } @@ -843,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]]; 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/Source/SPUserMO.h b/Source/SPUserMO.h index 99a6f7d4..11cc79ce 100644 --- a/Source/SPUserMO.h +++ b/Source/SPUserMO.h @@ -30,18 +30,18 @@ #import <CoreData/CoreData.h> -@interface NSManagedObject (CoreDataGeneratedAccessors) +@interface SPUserMO : NSManagedObject @property (nonatomic, retain) NSString *user; @property (nonatomic, retain) NSString *host; -@property (nonatomic, retain) NSManagedObject *parent; +@property (nonatomic, retain) SPUserMO *parent; @property (nonatomic, retain) NSSet *children; - (NSString *)displayName; - (void)setDisplayName:(NSString *)value; // Access to-many relationship via -[NSObject mutableSetValueForKey:] -- (void)addChildrenObject:(NSManagedObject *)value; -- (void)removeChildrenObject:(NSManagedObject *)value; +- (void)addChildrenObject:(SPUserMO *)value; +- (void)removeChildrenObject:(SPUserMO *)value; @end diff --git a/Source/SPUserMO.m b/Source/SPUserMO.m index e9254f7e..e3f6f3bf 100644 --- a/Source/SPUserMO.m +++ b/Source/SPUserMO.m @@ -29,13 +29,14 @@ // More info at <https://github.com/sequelpro/sequelpro> #import "SPUserMO.h" +#import "SPUserManager.h" static NSString *SPUserMOParentKey = @"parent"; static NSString *SPUserMOUserKey = @"user"; static NSString *SPUserMOHostKey = @"host"; static NSString *SPUserMOChildrenKey = @"children"; -@implementation NSManagedObject (CoreDataGeneratedAccessors) +@implementation SPUserMO @dynamic user; @dynamic host; @@ -64,7 +65,7 @@ static NSString *SPUserMOChildrenKey = @"children"; } } -- (void)addChildrenObject:(NSManagedObject *)value +- (void)addChildrenObject:(SPUserMO *)value { NSSet *changedObjects = [[NSSet alloc] initWithObjects:&value count:1]; @@ -77,7 +78,7 @@ static NSString *SPUserMOChildrenKey = @"children"; value.user = self.user; } -- (void)removeChildrenObject:(NSManagedObject *)value +- (void)removeChildrenObject:(SPUserMO *)value { NSSet *changedObjects = [[NSSet alloc] initWithObjects:&value count:1]; @@ -88,4 +89,31 @@ static NSString *SPUserMOChildrenKey = @"children"; [changedObjects release]; } +- (BOOL)validateForInsert:(NSError **)error +{ + if(![super validateForInsert:error]) return NO; + + SPUserManager *mgr = [self valueForKey:@"userManager"]; + + return [mgr insertUser:self]; +} + +- (BOOL)validateForDelete:(NSError **)error +{ + if(![super validateForDelete:error]) return NO; + + SPUserManager *mgr = [self valueForKey:@"userManager"]; + + return [mgr deleteUser:self]; +} + +- (BOOL)validateForUpdate:(NSError **)error +{ + if(![super validateForUpdate:error]) return NO; + + SPUserManager *mgr = [self valueForKey:@"userManager"]; + + return [mgr updateUser:self]; +} + @end diff --git a/Source/SPUserManager.h b/Source/SPUserManager.h index 0058620a..6338145f 100644 --- a/Source/SPUserManager.h +++ b/Source/SPUserManager.h @@ -32,6 +32,8 @@ @class SPMySQLConnection; @class SPSplitView; @class SPDatabaseDocument; +@class SPUserMO; +@class SPPrivilegesMO; @interface SPUserManager : NSWindowController { @@ -78,6 +80,9 @@ BOOL isSaving; BOOL isInitializing; NSMutableString *errorsString; + + // MySQL 5.7.6 removes the "Password" columns and only uses the "plugin"+"authentication_string" columns + BOOL requiresPost576PasswordHandling; } @property (nonatomic, retain) SPMySQLConnection *connection; @@ -92,6 +97,7 @@ @property (nonatomic, retain) NSMutableArray *schemas; @property (nonatomic, retain) NSMutableArray *grantedSchemaPrivs; @property (nonatomic, retain) NSMutableArray *availablePrivs; +@property (nonatomic, readonly) BOOL isInitializing; // Add/Remove users - (IBAction)addUser:(id)sender; @@ -116,13 +122,12 @@ - (IBAction)refresh:(id)sender; // Core Data notifications -- (void)contextDidSave:(NSNotification *)notification; -- (BOOL)insertUsers:(NSArray *)insertedUsers; -- (BOOL)deleteUsers:(NSArray *)deletedUsers; -- (BOOL)updateUsers:(NSArray *)updatedUsers; -- (BOOL)updateResourcesForUser:(NSManagedObject *)user; -- (BOOL)grantPrivilegesToUser:(NSManagedObject *)user; -- (BOOL)grantDbPrivilegesWithPrivilege:(NSManagedObject *)user; +- (BOOL)insertUser:(SPUserMO *)user; +- (BOOL)deleteUser:(SPUserMO *)user; +- (BOOL)updateUser:(SPUserMO *)user; +- (BOOL)updateResourcesForUser:(SPUserMO *)user; +- (BOOL)grantPrivilegesToUser:(SPUserMO *)user; +- (BOOL)grantDbPrivilegesWithPrivilege:(SPPrivilegesMO *)user; // External /** diff --git a/Source/SPUserManager.m b/Source/SPUserManager.m index e3f611c3..12da17fb 100644 --- a/Source/SPUserManager.m +++ b/Source/SPUserManager.m @@ -30,6 +30,7 @@ #import "SPUserManager.h" #import "SPUserMO.h" +#import "SPPrivilegesMO.h" #import "ImageAndTextCell.h" #import "SPGrowlController.h" #import "SPConnectionController.h" @@ -49,19 +50,20 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; - (void)_initializeUsers; - (void)_selectParentFromSelection; - (NSArray *)_fetchUserWithUserName:(NSString *)username; -- (NSManagedObject *)_createNewSPUser; -- (void)_grantPrivileges:(NSArray *)thePrivileges onDatabase:(NSString *)aDatabase forUser:(NSString *)aUser host:(NSString *)aHost; -- (void)_revokePrivileges:(NSArray *)thePrivileges onDatabase:(NSString *)aDatabase forUser:(NSString *)aUser host:(NSString *)aHost; +- (SPUserMO *)_createNewSPUser; +- (BOOL)_grantPrivileges:(NSArray *)thePrivileges onDatabase:(NSString *)aDatabase forUser:(NSString *)aUser host:(NSString *)aHost; +- (BOOL)_revokePrivileges:(NSArray *)thePrivileges onDatabase:(NSString *)aDatabase forUser:(NSString *)aUser host:(NSString *)aHost; - (BOOL)_checkAndDisplayMySqlError; - (void)_clearData; - (void)_initializeChild:(NSManagedObject *)child withItem:(NSDictionary *)item; -- (void)_initializeSchemaPrivsForChild:(NSManagedObject *)child fromData:(NSArray *)dataForUser; +- (void)_initializeSchemaPrivsForChild:(SPUserMO *)child fromData:(NSArray *)dataForUser; - (void)_initializeSchemaPrivs; - (NSArray *)_fetchPrivsWithUser:(NSString *)username schema:(NSString *)selectedSchema host:(NSString *)host; - (void)_setSchemaPrivValues:(NSArray *)objects enabled:(BOOL)enabled; - (void)_initializeAvailablePrivs; -- (void)_renameUserFrom:(NSString *)originalUser host:(NSString *)originalHost to:(NSString *)newUser host:(NSString *)newHost; +- (BOOL)_renameUserFrom:(NSString *)originalUser host:(NSString *)originalHost to:(NSString *)newUser host:(NSString *)newHost; - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void*)context; +- (void)contextWillSave:(NSNotification *)notice; @end @@ -78,6 +80,7 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; @synthesize availablePrivs; @synthesize treeSortDescriptors; @synthesize serverSupport; +@synthesize isInitializing = isInitializing; #pragma mark - #pragma mark Initialisation @@ -91,13 +94,13 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; // is derived automatically. While most keys can be automatically converted without // any difficulty, some keys differ slightly in mysql column storage to GRANT syntax; // this dictionary provides mappings for those values to ensure consistency. - privColumnToGrantMap = [[NSDictionary alloc] initWithObjectsAndKeys: - @"Grant_option_priv", @"Grant_priv", - @"Show_databases_priv", @"Show_db_priv", - @"Create_temporary_tables_priv", @"Create_tmp_table_priv", - @"Replication_slave_priv", @"Repl_slave_priv", - @"Replication_client_priv", @"Repl_client_priv", - nil]; + privColumnToGrantMap = [@{ + @"Grant_priv": @"Grant_option_priv", + @"Show_db_priv": @"Show_databases_priv", + @"Create_tmp_table_priv": @"Create_temporary_tables_priv", + @"Repl_slave_priv": @"Replication_slave_priv", + @"Repl_client_priv": @"Replication_client_priv", + } retain]; schemas = [[NSMutableArray alloc] init]; availablePrivs = [[NSMutableArray alloc] init]; @@ -154,8 +157,11 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; NSMutableArray *usersResultArray = [NSMutableArray array]; // Select users from the mysql.user table - SPMySQLResult *result = [[self connection] queryString:@"SELECT * FROM mysql.user ORDER BY user"]; + SPMySQLResult *result = [connection queryString:@"SELECT * FROM mysql.user ORDER BY user"]; [result setReturnDataAsStrings:YES]; + //TODO: improve user feedback + NSAssert(([[result fieldNames] firstObjectCommonWithArray:@[@"Password",@"authentication_string"]] != nil), @"Resultset from mysql.user contains neither 'Password' nor 'authentication_string' column!?"); + requiresPost576PasswordHandling = ![[result fieldNames] containsObject:@"Password"]; [usersResultArray addObjectsFromArray:[result getAllRows]]; [self _initializeTree:usersResultArray]; @@ -168,7 +174,7 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; // Attempt to obtain user privileges if supported if ([serverSupport supportsShowPrivileges]) { - result = [[self connection] queryString:@"SHOW PRIVILEGES"]; + result = [connection queryString:@"SHOW PRIVILEGES"]; [result setReturnDataAsStrings:YES]; } @@ -188,7 +194,7 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; } // If that fails, base privilege support on the mysql.users columns else { - result = [[self connection] queryString:@"SHOW COLUMNS FROM mysql.user"]; + result = [connection queryString:@"SHOW COLUMNS FROM mysql.user"]; [result setReturnDataAsStrings:YES]; @@ -219,7 +225,7 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; // Retrieve all the user data in order to be able to initialise the schema privs for each child, // copying into a dictionary keyed by user, each with all the host rows. NSMutableDictionary *schemaPrivilegeData = [NSMutableDictionary dictionary]; - SPMySQLResult *queryResults = [[self connection] queryString:@"SELECT * FROM mysql.db"]; + SPMySQLResult *queryResults = [connection queryString:@"SELECT * FROM mysql.db"]; [queryResults setReturnDataAsStrings:YES]; for (NSDictionary *privRow in queryResults) { if (![schemaPrivilegeData objectForKey:[privRow objectForKey:@"User"]]) { @@ -241,11 +247,11 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; // for each user currently in the database. for (NSUInteger i = 0; i < [items count]; i++) { - NSString *username = [[items objectAtIndex:i] objectForKey:@"User"]; - NSArray *parentResults = [[self _fetchUserWithUserName:username] retain]; NSDictionary *item = [items objectAtIndex:i]; - NSManagedObject *parent; - NSManagedObject *child; + NSString *username = [item objectForKey:@"User"]; + NSArray *parentResults = [[self _fetchUserWithUserName:username] retain]; + SPUserMO *parent; + SPUserMO *child; // Check to make sure if we already have added the parent if (parentResults != nil && [parentResults count] > 0) { @@ -263,8 +269,16 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; // original values for comparison purposes [parent setPrimitiveValue:username forKey:@"user"]; [parent setPrimitiveValue:username forKey:@"originaluser"]; - [parent setPrimitiveValue:[item objectForKey:@"Password"] forKey:@"password"]; - [parent setPrimitiveValue:[item objectForKey:@"Password"] forKey:@"originalpassword"]; + if(requiresPost576PasswordHandling) { + [parent setPrimitiveValue:[item objectForKey:@"plugin"] forKey:@"plugin"]; + NSString *pwHash = [item objectForKey:@"authentication_string"]; + [parent setPrimitiveValue:pwHash forKey:@"authentication_string"]; + if([pwHash length]) [parent setPrimitiveValue:@"sequelpro_dummy_password" forKey:@"password"]; // for the UI dialog + } + else { + [parent setPrimitiveValue:[item objectForKey:@"Password"] forKey:@"password"]; + [parent setPrimitiveValue:[item objectForKey:@"Password"] forKey:@"originalpassword"]; + } } // Setup the NSManagedObject with values from the dictionary @@ -357,7 +371,7 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; NSNumber *value = [NSNumber numberWithInteger:[[item objectForKey:key] integerValue]]; [child setValue:value forKey:key]; } - else if (![key isEqualToString:@"User"] && ![key isEqualToString:@"Password"]) + else if (![key isInArray:@[@"User",@"Password",@"plugin",@"authentication_string"]]) { NSString *value = [item objectForKey:key]; [child setValue:value forKey:key]; @@ -373,7 +387,7 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; * Assumes that the child has already been initialized with values from the * global user table. */ -- (void)_initializeSchemaPrivsForChild:(NSManagedObject *)child fromData:(NSArray *)dataForUser +- (void)_initializeSchemaPrivsForChild:(SPUserMO *)child fromData:(NSArray *)dataForUser { NSMutableSet *privs = [child mutableSetValueForKey:@"schema_privileges"]; @@ -388,7 +402,7 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; continue; } - NSManagedObject *dbPriv = [NSEntityDescription insertNewObjectForEntityForName:@"Privileges" inManagedObjectContext:[self managedObjectContext]]; + SPPrivilegesMO *dbPriv = [NSEntityDescription insertNewObjectForEntityForName:@"Privileges" inManagedObjectContext:[self managedObjectContext]]; for (NSString *key in rowDict) { @@ -421,10 +435,9 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; */ - (NSManagedObjectModel *)managedObjectModel { - if (managedObjectModel != nil) return managedObjectModel; - - managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain]; - + if (!managedObjectModel) { + managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain]; + } return managedObjectModel; } @@ -438,11 +451,11 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; { if (persistentStoreCoordinator != nil) return persistentStoreCoordinator; - NSError *error; + NSError *error = nil; persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]]; - if (![persistentStoreCoordinator addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:&error]) { + if (![persistentStoreCoordinator addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:&error] && error) { [NSApp presentError:error]; } @@ -464,10 +477,10 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; [managedObjectContext setPersistentStoreCoordinator:coordinator]; } - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(contextDidSave:) - name:NSManagedObjectContextDidSaveNotification - object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(contextWillSave:) + name:NSManagedObjectContextWillSaveNotification + object:managedObjectContext]; return managedObjectContext; } @@ -543,7 +556,7 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; if (error) [errorsString appendString:[error localizedDescription]]; - [[self connection] queryString:@"FLUSH PRIVILEGES"]; + [connection queryString:@"FLUSH PRIVILEGES"]; // Display any errors if ([errorsString length]) { @@ -618,8 +631,8 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; } } - NSManagedObject *newItem = [self _createNewSPUser]; - NSManagedObject *newChild = [self _createNewSPUser]; + SPUserMO *newItem = [self _createNewSPUser]; + SPUserMO *newChild = [self _createNewSPUser]; [newChild setValue:@"localhost" forKey:@"host"]; [newItem addChildrenObject:newChild]; @@ -684,8 +697,8 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; { // Set the username on the child so that it's accessabile when building // the drop sql command - NSManagedObject *child = [[treeController selectedObjects] objectAtIndex:0]; - NSManagedObject *parent = [child valueForKey:@"parent"]; + SPUserMO *child = [[treeController selectedObjects] objectAtIndex:0]; + SPUserMO *parent = [child parent]; [child setPrimitiveValue:[[child valueForKey:@"parent"] valueForKey:@"user"] forKey:@"user"]; @@ -693,9 +706,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") + ); } } @@ -799,11 +814,11 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; NSArray *userArray = [[self managedObjectContext] executeFetchRequest:request error:nil]; - for (NSManagedObject *user in userArray) + for (SPUserMO *user in userArray) { if (![user parent]) { [user setPrimitiveValue:[user valueForKey:@"user"] forKey:@"originaluser"]; - [user setPrimitiveValue:[user valueForKey:@"password"] forKey:@"originalpassword"]; + if(!requiresPost576PasswordHandling) [user setPrimitiveValue:[user valueForKey:@"password"] forKey:@"originalpassword"]; } } } @@ -919,34 +934,17 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; /** * This notification is called when the managedObjectContext save happens. - * This takes the inserted, updated, and deleted arrays and applies them to - * the database. + * + * This will link this class to any newly created objects, so when they do their + * -validateFor(Insert|Update|Delete): call later, they can forward it to this class. */ -- (void)contextDidSave:(NSNotification *)notification -{ - NSManagedObjectContext *notificationContext = (NSManagedObjectContext *)[notification object]; - - // If there are multiple user manager windows open, it's possible to get this - // notification from foreign windows. Ignore those notifications. - if (notificationContext != [self managedObjectContext]) return; - - if (!isInitializing) - { - NSArray *updated = [[notification userInfo] valueForKey:NSUpdatedObjectsKey]; - NSArray *inserted = [[notification userInfo] valueForKey:NSInsertedObjectsKey]; - NSArray *deleted = [[notification userInfo] valueForKey:NSDeletedObjectsKey]; - - if ([inserted count] > 0) { - [self insertUsers:inserted]; - } - - if ([updated count] > 0) { - [self updateUsers:updated]; +- (void)contextWillSave:(NSNotification *)notice +{ + //new objects don't yet know about us (this will also be called the first time an object is loaded from the db) + for (NSManagedObject *o in [managedObjectContext insertedObjects]) { + if([o isKindOfClass:[SPUserMO class]] || [o isKindOfClass:[SPPrivilegesMO class]]) { + [o setValue:self forKey:@"userManager"]; } - - if ([deleted count] > 0) { - [self deleteUsers:deleted]; - } } } @@ -955,37 +953,50 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; if (!isInitializing) [outlineView reloadData]; } -/** - * Updates the supplied array of users. - */ -- (BOOL)updateUsers:(NSArray *)updatedUsers +- (BOOL)updateUser:(SPUserMO *)user { - for (NSManagedObject *user in updatedUsers) - { - if ([[[user entity] name] isEqualToString:@"Privileges"]) { - [self grantDbPrivilegesWithPrivilege:user]; + if (![user parent]) { + NSArray *hosts = [user valueForKey:@"children"]; + + // If the user has been changed, update the username on all hosts. + // Don't check for errors, as some hosts may be new. + if (![[user valueForKey:@"user"] isEqualToString:[user valueForKey:@"originaluser"]]) { + + for (SPUserMO *child in hosts) + { + [self _renameUserFrom:[user valueForKey:@"originaluser"] + host:[child valueForKey:@"originalhost"] ? [child valueForKey:@"originalhost"] : [child host] + to:[user valueForKey:@"user"] + host:[child host]]; + } } - // If the parent user has changed, either the username or password have been edited. - else if (![user parent]) { - NSArray *hosts = [user valueForKey:@"children"]; - - // If the user has been changed, update the username on all hosts. - // Don't check for errors, as some hosts may be new. - if (![[user valueForKey:@"user"] isEqualToString:[user valueForKey:@"originaluser"]]) { - - for (NSManagedObject *child in hosts) + + // If the password has been changed, use the same password on all hosts + if(requiresPost576PasswordHandling) { + // the UI password field is bound to the password field, so this is still where the new plaintext value comes from + NSString *newPass = [[user changedValues] objectForKey:@"password"]; + if(newPass) { + // 5.7.6+ can update all users at once + NSMutableString *alterStmt = [NSMutableString stringWithString:@"ALTER USER "]; + BOOL first = YES; + for (SPUserMO *child in hosts) { - [self _renameUserFrom:[user valueForKey:@"originaluser"] - host:[child valueForKey:@"originalhost"] ? [child valueForKey:@"originalhost"] : [child host] - to:[user valueForKey:@"user"] - host:[child host]]; + if(!first) [alterStmt appendString:@", "]; + [alterStmt appendFormat:@"%@@%@ IDENTIFIED WITH %@ BY %@", //note: "BY" -> plaintext, "AS" -> hash + [[user valueForKey:@"user"] tickQuotedString], + [[child host] tickQuotedString], + [[user valueForKey:@"plugin"] tickQuotedString], + (![newPass isNSNull] && [newPass length]) ? [newPass tickQuotedString] : @"''"]; + first = NO; } + [connection queryString:alterStmt]; + if(![self _checkAndDisplayMySqlError]) return NO; } - - // If the password has been changed, use the same password on all hosts + } + else { if (![[user valueForKey:@"password"] isEqualToString:[user valueForKey:@"originalpassword"]]) { - for (NSManagedObject *child in hosts) + for (SPUserMO *child in hosts) { NSString *changePasswordStatement = [NSString stringWithFormat: @"SET PASSWORD FOR %@@%@ = PASSWORD(%@)", @@ -993,136 +1004,146 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; [[child host] tickQuotedString], ([user valueForKey:@"password"]) ? [[user valueForKey:@"password"] tickQuotedString] : @"''"]; - [[self connection] queryString:changePasswordStatement]; - [self _checkAndDisplayMySqlError]; + [connection queryString:changePasswordStatement]; + if(![self _checkAndDisplayMySqlError]) return NO; } } - } - else { - // If the hostname has changed, remane the detail before editing details - if (![[user valueForKey:@"host"] isEqualToString:[user valueForKey:@"originalhost"]]) { - - [self _renameUserFrom:[[user parent] valueForKey:@"originaluser"] - host:[user valueForKey:@"originalhost"] - to:[[user parent] valueForKey:@"user"] - host:[user valueForKey:@"host"]]; - } - - if ([serverSupport supportsUserMaxVars]) [self updateResourcesForUser:user]; + } + } + else { + // If the hostname has changed, remane the detail before editing details + if (![[user valueForKey:@"host"] isEqualToString:[user valueForKey:@"originalhost"]]) { - [self grantPrivilegesToUser:user]; + [self _renameUserFrom:[[user parent] valueForKey:@"originaluser"] + host:[user valueForKey:@"originalhost"] + to:[[user parent] valueForKey:@"user"] + host:[user valueForKey:@"host"]]; } + + if ([serverSupport supportsUserMaxVars]) { + if(![self updateResourcesForUser:user]) return NO; + } + + if(![self grantPrivilegesToUser:user]) return NO; } return YES; } -- (BOOL)deleteUsers:(NSArray *)deletedUsers +- (BOOL)deleteUser:(SPUserMO *)user { - NSMutableString *droppedUsers = [NSMutableString string]; - - for (NSManagedObject *user in deletedUsers) - { - if (![[[user entity] name] isEqualToString:@"Privileges"] && ([user valueForKey:@"host"] != nil)) - { - [droppedUsers appendFormat:@"%@@%@, ", [[user valueForKey:@"user"] tickQuotedString], [[user valueForKey:@"host"] tickQuotedString]]; - } + // users without hosts are for display only + if(isInitializing || ![user valueForKey:@"host"]) return YES; + + NSString *droppedUser = [NSString stringWithFormat:@"%@@%@", [[user valueForKey:@"user"] tickQuotedString], [[user valueForKey:@"host"] tickQuotedString]]; + + // Before MySQL 5.0.2 DROP USER just removed users with no privileges, so revoke + // all their privileges first. Also, REVOKE ALL PRIVILEGES was added in MySQL 4.1.2, so use the + // old multiple query approach (damn, I wish there were only one MySQL version!). + if (![serverSupport supportsFullDropUser]) { + [connection queryString:[NSString stringWithFormat:@"REVOKE ALL PRIVILEGES ON *.* FROM %@", droppedUser]]; + if(![self _checkAndDisplayMySqlError]) return NO; + [connection queryString:[NSString stringWithFormat:@"REVOKE GRANT OPTION ON *.* FROM %@", droppedUser]]; + if(![self _checkAndDisplayMySqlError]) return NO; + } + + // DROP USER was added in MySQL 4.1.1 + if ([serverSupport supportsDropUser]) { + [connection queryString:[NSString stringWithFormat:@"DROP USER %@", droppedUser]]; } + // Otherwise manually remove the user rows from the mysql.user table + else { + [connection queryString:[NSString stringWithFormat:@"DELETE FROM mysql.user WHERE User = %@ and Host = %@", [[user valueForKey:@"user"] tickQuotedString], [[user valueForKey:@"host"] tickQuotedString]]]; + } + + return [self _checkAndDisplayMySqlError]; +} - if ([droppedUsers length] > 2) { - droppedUsers = [[droppedUsers substringToIndex:([droppedUsers length] - 2)] mutableCopy]; +- (BOOL)insertUser:(SPUserMO *)user +{ + //this is also called during the initialize phase. we don't want to write to the db there. + if(isInitializing) return YES; + + NSString *createStatement = nil; + + // Note that if the database does not support the use of the CREATE USER statment, then + // we must resort to using GRANT. Doing so means we must specify the privileges and the database + // for which these apply, so make them as restrictive as possible, but then revoke them to get the + // same affect as CREATE USER. That is, a new user with no privleges. + NSString *host = [[user valueForKey:@"host"] tickQuotedString]; + + if ([user parent] && [[user parent] valueForKey:@"user"] && ([[user parent] valueForKey:@"password"] || [[user parent] valueForKey:@"authentication_string"])) { - // Before MySQL 5.0.2 DROP USER just removed users with no privileges, so revoke - // all their privileges first. Also, REVOKE ALL PRIVILEGES was added in MySQL 4.1.2, so use the - // old multiple query approach (damn, I wish there were only one MySQL version!). - if (![serverSupport supportsFullDropUser]) { - [connection queryString:[NSString stringWithFormat:@"REVOKE ALL PRIVILEGES ON *.* FROM %@", droppedUsers]]; - [connection queryString:[NSString stringWithFormat:@"REVOKE GRANT OPTION ON *.* FROM %@", droppedUsers]]; - } + NSString *username = [[[user parent] valueForKey:@"user"] tickQuotedString]; - // DROP USER was added in MySQL 4.1.1 - if ([serverSupport supportsDropUser]) { - [[self connection] queryString:[NSString stringWithFormat:@"DROP USER %@", droppedUsers]]; + NSString *idString; + if(requiresPost576PasswordHandling) { + //copy the hash from the parent. if the parent password changes at the same time, updateUser: will take care of it afterwards + NSString *plugin = [[[user parent] valueForKey:@"plugin"] tickQuotedString]; + NSString *hash = [[[user parent] valueForKey:@"authentication_string"] tickQuotedString]; + idString = [NSString stringWithFormat:@"IDENTIFIED WITH %@ AS %@",plugin,hash]; } - // Otherwise manually remove the user rows from the mysql.user table else { - NSArray *users = [droppedUsers componentsSeparatedByString:@", "]; - - for (NSString *user in users) - { - NSArray *userDetails = [user componentsSeparatedByString:@"@"]; - - [connection queryString:[NSString stringWithFormat:@"DELETE FROM mysql.user WHERE User = %@ and Host = %@", [userDetails objectAtIndex:0], [userDetails objectAtIndex:1]]]; + BOOL passwordIsHash; + NSString *password; + // there are three situations to cover here: + // 1) host added, parent user unchanged + // 2) host added, parent user password changed + // 3) host added, parent user is new + if([[user parent] valueForKey:@"originaluser"]) { + // 1 & 2: If the parent user already exists we always use the old password hash. + // This works because -updateUser: will be called after -insertUser: and update the password for this host, anyway. + passwordIsHash = YES; + password = [[[user parent] valueForKey:@"originalpassword"] tickQuotedString]; + } + else { + // 3: If the user is new, we take the plaintext password value from the UI + passwordIsHash = NO; + password = [[[user parent] valueForKey:@"password"] tickQuotedString]; } + idString = [NSString stringWithFormat:@"IDENTIFIED BY %@%@",(passwordIsHash? @"PASSWORD " : @""), password]; } - [droppedUsers release]; + createStatement = ([serverSupport supportsCreateUser]) ? + [NSString stringWithFormat:@"CREATE USER %@@%@ %@", username, host, idString] : + [NSString stringWithFormat:@"GRANT SELECT ON mysql.* TO %@@%@ %@", username, host, idString]; } - - return YES; -} - -/** - * Inserts (creates) the supplied users in the database. - */ -- (BOOL)insertUsers:(NSArray *)insertedUsers -{ - for (NSManagedObject *user in insertedUsers) - { - if ([[[user entity] name] isEqualToString:@"Privileges"]) continue; + else if ([user parent] && [[user parent] valueForKey:@"user"]) { - NSString *createStatement = nil; + NSString *username = [[[user parent] valueForKey:@"user"] tickQuotedString]; - // Note that if the database does not support the use of the CREATE USER statment, then - // we must resort to using GRANT. Doing so means we must specify the privileges and the database - // for which these apply, so make them as restrictive as possible, but then revoke them to get the - // same affect as CREATE USER. That is, a new user with no privleges. - NSString *host = [[user valueForKey:@"host"] tickQuotedString]; + createStatement = ([serverSupport supportsCreateUser]) ? + [NSString stringWithFormat:@"CREATE USER %@@%@", username, host] : + [NSString stringWithFormat:@"GRANT SELECT ON mysql.* TO %@@%@", username, host]; + } + + if (createStatement) { - if ([user parent] && [[user parent] valueForKey:@"user"] && [[user parent] valueForKey:@"password"]) { - - NSString *username = [[[user parent] valueForKey:@"user"] tickQuotedString]; - NSString *password = [[[user parent] valueForKey:@"password"] tickQuotedString]; - - createStatement = ([serverSupport supportsCreateUser]) ? - [NSString stringWithFormat:@"CREATE USER %@@%@ IDENTIFIED BY %@%@", username, host, [[user parent] valueForKey:@"originaluser"]?@"PASSWORD ":@"", password] : - [NSString stringWithFormat:@"GRANT SELECT ON mysql.* TO %@@%@ IDENTIFIED BY %@%@", username, host, [[user parent] valueForKey:@"originaluser"]?@"PASSWORD ":@"", password]; - } - else if ([user parent] && [[user parent] valueForKey:@"user"]) { - - NSString *username = [[[user parent] valueForKey:@"user"] tickQuotedString]; + // Create user in database + [connection queryString:createStatement]; + + if ([self _checkAndDisplayMySqlError]) { + if ([serverSupport supportsUserMaxVars]) { + if(![self updateResourcesForUser:user]) return NO; + } + // If we created the user with the GRANT statment (MySQL < 5), then revoke the + // privileges we gave the new user. + else { + [connection queryString:[NSString stringWithFormat:@"REVOKE SELECT ON mysql.* FROM %@@%@", [[[user parent] valueForKey:@"user"] tickQuotedString], host]]; - createStatement = ([serverSupport supportsCreateUser]) ? - [NSString stringWithFormat:@"CREATE USER %@@%@", username, host] : - [NSString stringWithFormat:@"GRANT SELECT ON mysql.* TO %@@%@", username, host]; - } - - if (createStatement) { - - // Create user in database - [connection queryString:createStatement]; - - if ([self _checkAndDisplayMySqlError]) { - if ([serverSupport supportsUserMaxVars]) [self updateResourcesForUser:user]; + if (![self _checkAndDisplayMySqlError]) return NO; + } - // If we created the user with the GRANT statment (MySQL < 5), then revoke the - // privileges we gave the new user. - if (![serverSupport supportsUserMaxVars]) { - [connection queryString:[NSString stringWithFormat:@"REVOKE SELECT ON mysql.* FROM %@@%@", [[[user parent] valueForKey:@"user"] tickQuotedString], host]]; - } - - [self grantPrivilegesToUser:user]; - } - } + return [self grantPrivilegesToUser:user]; + } } - - return YES; + return NO; } /** * Grant or revoke DB privileges for the supplied user. */ -- (BOOL)grantDbPrivilegesWithPrivilege:(NSManagedObject *)schemaPriv +- (BOOL)grantDbPrivilegesWithPrivilege:(SPPrivilegesMO *)schemaPriv { NSMutableArray *grantPrivileges = [NSMutableArray array]; NSMutableArray *revokePrivileges = [NSMutableArray array]; @@ -1135,7 +1156,7 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; [[schemaPriv valueForKeyPath:@"user.host"] tickQuotedString], [dbName tickQuotedString]]; - NSArray *matchingUsers = [[self connection] getAllRowsFromQuery:statement]; + NSArray *matchingUsers = [connection getAllRowsFromQuery:statement]; for (NSString *key in [self privsSupportedByServer]) { @@ -1158,16 +1179,16 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; } // Grant privileges - [self _grantPrivileges:grantPrivileges + if(![self _grantPrivileges:grantPrivileges onDatabase:dbName forUser:[schemaPriv valueForKeyPath:@"user.parent.user"] - host:[schemaPriv valueForKeyPath:@"user.host"]]; + host:[schemaPriv valueForKeyPath:@"user.host"]]) return NO; // Revoke privileges - [self _revokePrivileges:revokePrivileges + if(![self _revokePrivileges:revokePrivileges onDatabase:dbName forUser:[schemaPriv valueForKeyPath:@"user.parent.user"] - host:[schemaPriv valueForKeyPath:@"user.host"]]; + host:[schemaPriv valueForKeyPath:@"user.host"]]) return NO; return YES; } @@ -1175,7 +1196,7 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; /** * Update resource limites for given user */ -- (BOOL)updateResourcesForUser:(NSManagedObject *)user +- (BOOL)updateResourcesForUser:(SPUserMO *)user { if ([user valueForKey:@"parent"] != nil) { NSString *updateResourcesStatement = [NSString stringWithFormat: @@ -1186,8 +1207,8 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; [[[user valueForKey:@"parent"] valueForKey:@"user"] tickQuotedString], [[user valueForKey:@"host"] tickQuotedString]]; - [[self connection] queryString:updateResourcesStatement]; - [self _checkAndDisplayMySqlError]; + [connection queryString:updateResourcesStatement]; + return [self _checkAndDisplayMySqlError]; } return YES; @@ -1196,7 +1217,7 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; /** * Grant or revoke privileges for the supplied user. */ -- (BOOL)grantPrivilegesToUser:(NSManagedObject *)user +- (BOOL)grantPrivilegesToUser:(SPUserMO *)user { if ([user valueForKey:@"parent"] != nil) { @@ -1223,21 +1244,21 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; } // Grant privileges - [self _grantPrivileges:grantPrivileges + if(![self _grantPrivileges:grantPrivileges onDatabase:nil forUser:[[user parent] valueForKey:@"user"] - host:[user valueForKey:@"host"]]; + host:[user valueForKey:@"host"]]) return NO; // Revoke privileges - [self _revokePrivileges:revokePrivileges + if(![self _revokePrivileges:revokePrivileges onDatabase:nil forUser:[[user parent] valueForKey:@"user"] - host:[user valueForKey:@"host"]]; + host:[user valueForKey:@"host"]]) return NO; } - for (NSManagedObject *priv in [user valueForKey:@"schema_privileges"]) + for (SPPrivilegesMO *priv in [user valueForKey:@"schema_privileges"]) { - [self grantDbPrivilegesWithPrivilege:priv]; + if(![self grantDbPrivilegesWithPrivilege:priv]) return NO; } return YES; @@ -1306,7 +1327,7 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; /** * Creates a new NSManagedObject and inserts it into the managedObjectContext. */ -- (NSManagedObject *)_createNewSPUser +- (SPUserMO *)_createNewSPUser { return [NSEntityDescription insertNewObjectForEntityForName:@"SPUser" inManagedObjectContext:[self managedObjectContext]]; } @@ -1314,9 +1335,9 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; /** * Grant the supplied privileges to the specified user and host */ -- (void)_grantPrivileges:(NSArray *)thePrivileges onDatabase:(NSString *)aDatabase forUser:(NSString *)aUser host:(NSString *)aHost +- (BOOL)_grantPrivileges:(NSArray *)thePrivileges onDatabase:(NSString *)aDatabase forUser:(NSString *)aUser host:(NSString *)aHost { - if (![thePrivileges count]) return; + if (![thePrivileges count]) return YES; NSString *grantStatement; @@ -1335,17 +1356,17 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; [aHost tickQuotedString]]; } - [[self connection] queryString:grantStatement]; - [self _checkAndDisplayMySqlError]; + [connection queryString:grantStatement]; + return [self _checkAndDisplayMySqlError]; } /** * Revoke the supplied privileges from the specified user and host */ -- (void)_revokePrivileges:(NSArray *)thePrivileges onDatabase:(NSString *)aDatabase forUser:(NSString *)aUser host:(NSString *)aHost +- (BOOL)_revokePrivileges:(NSArray *)thePrivileges onDatabase:(NSString *)aDatabase forUser:(NSString *)aUser host:(NSString *)aHost { - if (![thePrivileges count]) return; + if (![thePrivileges count]) return YES; NSString *revokeStatement; @@ -1356,8 +1377,8 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; [aUser tickQuotedString], [aHost tickQuotedString]]; - [[self connection] queryString:revokeStatement]; - [self _checkAndDisplayMySqlError]; + [connection queryString:revokeStatement]; + if(![self _checkAndDisplayMySqlError]) return NO; revokeStatement = [NSString stringWithFormat:@"REVOKE GRANT OPTION ON %@.* FROM %@@%@", aDatabase?[aDatabase backtickQuotedString]:@"*", @@ -1372,8 +1393,8 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; [aHost tickQuotedString]]; } - [[self connection] queryString:revokeStatement]; - [self _checkAndDisplayMySqlError]; + [connection queryString:revokeStatement]; + return [self _checkAndDisplayMySqlError]; } /** @@ -1381,14 +1402,16 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; */ - (BOOL)_checkAndDisplayMySqlError { - if ([[self connection] queryErrored]) { + if ([connection queryErrored]) { if (isSaving) { - [errorsString appendFormat:@"%@\n", [[self connection] lastErrorMessage]]; + [errorsString appendFormat:@"%@\n", [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"), [connection lastErrorMessage]] + ); } return NO; @@ -1408,7 +1431,7 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; * @param newUser The user's new user name * @param newHost The user's new host */ -- (void)_renameUserFrom:(NSString *)originalUser host:(NSString *)originalHost to:(NSString *)newUser host:(NSString *)newHost +- (BOOL)_renameUserFrom:(NSString *)originalUser host:(NSString *)originalHost to:(NSString *)newUser host:(NSString *)newHost { NSString *renameQuery = nil; @@ -1450,7 +1473,10 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; if (renameQuery) { [connection queryString:renameQuery]; + return [self _checkAndDisplayMySqlError]; } + + return YES; } #pragma mark - diff --git a/Source/SPUserManagerDelegate.m b/Source/SPUserManagerDelegate.m index ac9e0675..cefe7f2e 100644 --- a/Source/SPUserManagerDelegate.m +++ b/Source/SPUserManagerDelegate.m @@ -65,7 +65,7 @@ static NSString *SPSchemaPrivilegesTabIdentifier = @"Schema Privileges"; [self _initializeAvailablePrivs]; if ([[treeController selectedObjects] count] > 0 && [[schemasTableView selectedRowIndexes] count] > 0) { - NSManagedObject *user = [[treeController selectedObjects] objectAtIndex:0]; + SPUserMO *user = [[treeController selectedObjects] objectAtIndex:0]; // Check to see if the user host node was selected if ([user valueForKey:@"host"]) { @@ -127,7 +127,7 @@ static NSString *SPSchemaPrivilegesTabIdentifier = @"Schema Privileges"; // If the schema has permissions set, highlight with a yellow background BOOL enabledPermissions = NO; - NSManagedObject *user = [[treeController selectedObjects] objectAtIndex:0]; + SPUserMO *user = [[treeController selectedObjects] objectAtIndex:0]; NSArray *results = [self _fetchPrivsWithUser:[[user parent] valueForKey:@"user"] schema:[schemaName stringByReplacingOccurrencesOfString:@"_" withString:@"\\_"] host:[user valueForKey:@"host"]]; @@ -233,9 +233,8 @@ static NSString *SPSchemaPrivilegesTabIdentifier = @"Schema Privileges"; if ([cell isKindOfClass:[ImageAndTextCell class]]) { // Determines which Image to display depending on parent or child object - NSImage *image = [[NSImage imageNamed:[(NSManagedObject *)[item representedObject] parent] ? NSImageNameNetwork : NSImageNameUser] retain]; + NSImage *image = [[NSImage imageNamed:[(SPUserMO *)[item representedObject] parent] ? NSImageNameNetwork : NSImageNameUser] retain]; - [image setScalesWhenResized:YES]; [image setSize:(NSSize){16, 16}]; [(ImageAndTextCell *)cell setImage:image]; [image release]; diff --git a/Source/SPWindowController.h b/Source/SPWindowController.h index c99a2ff2..528c1c84 100644 --- a/Source/SPWindowController.h +++ b/Source/SPWindowController.h @@ -49,7 +49,19 @@ // Database connection management - (IBAction)addNewConnection:(id)sender; - (IBAction)moveSelectedTabInNewWindow:(id)sender; + +- (SPDatabaseDocument *)addNewConnection; + +/** + * @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..963919d0 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]; @@ -103,6 +104,11 @@ enum { */ - (IBAction)addNewConnection:(id)sender { + [self addNewConnection]; +} + +- (SPDatabaseDocument *)addNewConnection +{ // Create a new database connection view SPDatabaseDocument *newTableDocument = [[SPDatabaseDocument alloc] init]; @@ -124,8 +130,8 @@ enum { // Bind the tab bar's progress display to the document [self _updateProgressIndicatorForItem:newItem]; - - [newTableDocument release]; + + return [newTableDocument autorelease]; } /** @@ -141,7 +147,7 @@ enum { */ - (void)updateSelectedTableDocument { - selectedTableDocument = [[tabView selectedTabViewItem] identifier]; + [self _switchOutSelectedTableDocument:[[tabView selectedTabViewItem] identifier]]; [selectedTableDocument didBecomeActiveTabInWindow]; } @@ -388,7 +394,7 @@ enum { */ - (BOOL)respondsToSelector:(SEL)theSelector { - return ([super respondsToSelector:theSelector] || (selectedTableDocument && [selectedTableDocument respondsToSelector:theSelector])); + return ([super respondsToSelector:theSelector] || [selectedTableDocument respondsToSelector:theSelector]); } /** @@ -529,10 +535,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]; 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,7 +149,7 @@ [frontState setObject:@YES forKey:@"auto_connect"]; // Set the connection on the new tab - [[self frontDocument] setState:frontState]; + [newConnection setState:frontState]; } /** @@ -138,12 +157,17 @@ */ - (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; } diff --git a/Source/SPXMLExporter.m b/Source/SPXMLExporter.m index dd4e9223..e1142d3a 100644 --- a/Source/SPXMLExporter.m +++ b/Source/SPXMLExporter.m @@ -63,14 +63,8 @@ return self; } -/** - * Start the XML export process. This method is automatically called when an instance of this class - * is placed on an NSOperationQueue. Do not call it directly as there is no manual multithreading. - */ -- (void)main +- (void)exportOperation { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - BOOL isTableExport = NO; NSArray *xmlRow = nil; @@ -96,7 +90,6 @@ (([self xmlFormat] == SPXMLExportMySQLFormat) && ((![self xmlOutputIncludeStructure]) && (![self xmlOutputIncludeContent]))) || (([self xmlFormat] == SPXMLExportPlainFormat) && (![self xmlNULLString]))) { - [pool release]; return; } @@ -164,7 +157,7 @@ [xmlString appendFormat:@"\t<table_data name=\"%@\">\n\n", [self xmlTableName]]; } - [[self exportOutputFile] writeData:[xmlString dataUsingEncoding:[self exportOutputEncoding]]]; + [self writeString:xmlString]; } else { totalRows = [[self xmlDataArray] count]; @@ -186,7 +179,7 @@ // If required, write an opening tag in the form of the table name if ([self xmlFormat] == SPXMLExportPlainFormat) { - [[self exportOutputFile] writeData:[[NSString stringWithFormat:@"\t<%@>\n", ([self xmlTableName]) ? [[self xmlTableName] HTMLEscapeString] : @"custom"] dataUsingEncoding:[self exportOutputEncoding]]]; + [self writeString:[NSString stringWithFormat:@"\t<%@>\n", ([self xmlTableName]) ? [[self xmlTableName] HTMLEscapeString] : @"custom"]]; } // Set up the starting row, which is 0 for streaming result sets and @@ -213,7 +206,6 @@ } [xmlExportPool release]; - [pool release]; return; } @@ -245,7 +237,6 @@ } [xmlExportPool release]; - [pool release]; return; } @@ -304,7 +295,7 @@ currentPoolDataLength += [xmlString length]; // Write the row to the filehandle - [[self exportOutputFile] writeData:[xmlString dataUsingEncoding:[self exportOutputEncoding]]]; + [self writeString:xmlString]; // Update the progress counter and progress bar currentRowIndex++; @@ -333,10 +324,10 @@ } if (([self xmlFormat] == SPXMLExportMySQLFormat) && isTableExport) { - [[self exportOutputFile] writeData:[@"\t</table_data>\n\n" dataUsingEncoding:[self exportOutputEncoding]]]; + [self writeString:@"\t</table_data>\n\n"]; } else if ([self xmlFormat] == SPXMLExportPlainFormat) { - [[self exportOutputFile] writeData:[[NSString stringWithFormat:@"\t</%@>\n\n", ([self xmlTableName]) ? [[self xmlTableName] HTMLEscapeString] : @"custom"] dataUsingEncoding:[self exportOutputEncoding]]]; + [self writeString:[NSString stringWithFormat:@"\t</%@>\n\n", ([self xmlTableName]) ? [[self xmlTableName] HTMLEscapeString] : @"custom"]]; } [xmlExportPool release]; @@ -350,8 +341,6 @@ // Inform the delegate that the export process is complete [delegate performSelectorOnMainThread:@selector(xmlExportProcessComplete:) withObject:self waitUntilDone:NO]; - - [pool release]; } #pragma mark - diff --git a/Source/SPXMLExporterDelegate.m b/Source/SPXMLExporterDelegate.m index c414d195..80d5d0ca 100644 --- a/Source/SPXMLExporterDelegate.m +++ b/Source/SPXMLExporterDelegate.m @@ -32,6 +32,7 @@ #import "SPXMLExporter.h" #import "SPDatabaseDocument.h" #import "SPExportFile.h" +#import "SPExportInitializer.h" #import <SPMySQL/SPMySQL.h> @@ -39,25 +40,25 @@ - (void)xmlExportProcessWillBegin:(SPXMLExporter *)exporter { - [[exportProgressText onMainThread] displayIfNeeded]; - - [[exportProgressIndicator onMainThread] setIndeterminate:YES]; - [[exportProgressIndicator onMainThread] setUsesThreadedAnimation:YES]; - [[exportProgressIndicator onMainThread] startAnimation:self]; - + [exportProgressText displayIfNeeded]; + + [exportProgressIndicator setIndeterminate:YES]; + [exportProgressIndicator setUsesThreadedAnimation:YES]; + [exportProgressIndicator startAnimation:self]; + // Only update the progress text if this is a table export if (exportSource == SPTableExport) { - + // Update the current table export index currentTableExportIndex = (exportTableCount - [exporters count]); - - [[exportProgressText onMainThread] setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Fetching data...", @"export label showing that the app is fetching data for a specific table"), currentTableExportIndex, exportTableCount, [exporter xmlTableName]]]; + + [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Fetching data...", @"export label showing that the app is fetching data for a specific table"), currentTableExportIndex, exportTableCount, [exporter xmlTableName]]]; } else { - [[exportProgressText onMainThread] setStringValue:NSLocalizedString(@"Fetching data...", @"export label showing that the app is fetching data")]; + [exportProgressText setStringValue:NSLocalizedString(@"Fetching data...", @"export label showing that the app is fetching data")]; } - - [[exportProgressText onMainThread] displayIfNeeded]; + + [exportProgressText displayIfNeeded]; } - (void)xmlExportProcessComplete:(SPXMLExporter *)exporter @@ -103,14 +104,7 @@ [[exporter exportOutputFile] writeData:[string dataUsingEncoding:[connection stringEncoding]]]; [[exporter exportOutputFile] close]; - [NSApp endSheet:exportProgressWindow returnCode:0]; - [exportProgressWindow orderOut:self]; - - // Restore query mode - [tableDocumentInstance setQueryMode:SPInterfaceQueryMode]; - - // Display Growl notification - [self displayExportFinishedGrowlNotification]; + [self exportEnded]; } } @@ -123,18 +117,18 @@ { // Only update the progress text if this is a table export if (exportSource == SPTableExport) { - [[exportProgressText onMainThread] setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Writing data...", @"export label showing app if writing data for a specific table"), currentTableExportIndex, exportTableCount, [exporter xmlTableName]]]; + [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Writing data...", @"export label showing app if writing data for a specific table"), currentTableExportIndex, exportTableCount, [exporter xmlTableName]]]; } else { - [[exportProgressText onMainThread] setStringValue:NSLocalizedString(@"Writing data...", @"export label showing app is writing data")]; + [exportProgressText setStringValue:NSLocalizedString(@"Writing data...", @"export label showing app is writing data")]; } - - [[exportProgressText onMainThread] displayIfNeeded]; - - [[exportProgressIndicator onMainThread] stopAnimation:self]; - [[exportProgressIndicator onMainThread] setUsesThreadedAnimation:NO]; - [[exportProgressIndicator onMainThread] setIndeterminate:NO]; - [[exportProgressIndicator onMainThread] setDoubleValue:0]; + + [exportProgressText displayIfNeeded]; + + [exportProgressIndicator stopAnimation:self]; + [exportProgressIndicator setUsesThreadedAnimation:NO]; + [exportProgressIndicator setIndeterminate:NO]; + [exportProgressIndicator setDoubleValue:0]; } @end 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/UnitTests/SPDataAdditionsTests.m b/UnitTests/SPDataAdditionsTests.m new file mode 100644 index 00000000..4769e499 --- /dev/null +++ b/UnitTests/SPDataAdditionsTests.m @@ -0,0 +1,387 @@ +// +// SPDataAdditionsTests.m +// sequel-pro +// +// Created by Max Lohrmann on 13.09.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 <https://github.com/sequelpro/sequelpro> + +#import <Cocoa/Cocoa.h> +#import <XCTest/XCTest.h> +#import "SPDataAdditions.h" +#import <errno.h> + +@interface SPDataAdditionsTests : XCTestCase + +- (void)testSha1Hash; +- (void)testDataEncryptedWithPassword; +- (void)testDataEncryptedWithKeyIV; +- (void)testDataDecryptedWithPassword; +- (void)testDataDecryptedWithKey; +- (void)testEnumerateLinesBreakingAt_withBlock; + +@end + +@implementation SPDataAdditionsTests + +- (void)testSha1Hash +{ + //simple straight forward case + { + NSString *input = @"Hello World!"; + unsigned char bytes[] = {0x2e,0xf7,0xbd,0xe6,0x08,0xce,0x54,0x04,0xe9,0x7d,0x5f,0x04,0x2f,0x95,0xf8,0x9f,0x1c,0x23,0x28,0x71}; + + XCTAssertTrue(memcmp([[[input dataUsingEncoding:NSUTF8StringEncoding] sha1Hash] bytes], bytes, 20) == 0, @"SHA1 simple hash from ASCII text"); + } + // 16MB of all 8bit values + { + int bufSz = 16*1024*1024; + unsigned char *buf = malloc(bufSz); + for (int i = 0; i < bufSz; i++) { + buf[i] = (i % 0xff); + } + NSData *input = [NSData dataWithBytesNoCopy:buf length:bufSz]; + NSString *result = @"25E05EB8E9E2B06036DF4026630FE01A19BF0F16"; + + XCTAssertEqualObjects([[input sha1Hash] dataToHexString], result, @"SHA1 hash from full ASCII range"); + } + // empty hash + { + NSData *input = [NSData data]; + NSString *result = @"DA39A3EE5E6B4B0D3255BFEF95601890AFD80709"; + + XCTAssertEqualObjects([[input sha1Hash] dataToHexString], result, @"SHA1 hash from empty data"); + } + // test with > 4GB data (other code path) + // HFS+ does not support sparse files, so enable this one only if you have enough disk space. + {/* + // not everyone has 4GB RAM to spare and even then we probably won't be able to get + // them en-block, so we'll just use a file and mmap() to simulate that. + NSString *fileNameTpl = [NSTemporaryDirectory() stringByAppendingPathComponent:@"sha1test.XXXXXX"]; + STAssertNotNil(fileNameTpl, @"No temporary directory available!?"); + const char *cFileNameTpl = [fileNameTpl fileSystemRepresentation]; + char *cFileName = malloc(strlen(cFileNameTpl)+1); + strcpy(cFileName, cFileNameTpl); + if(mkstemp(cFileName) == -1) + STFail(@"could not create temporary filename. errno=%d",errno); + + FILE *fp = fopen(cFileName, "w+"); + fputc(1, fp); + fseek(fp, UINT32_MAX, SEEK_CUR); + fputc(2, fp); + fflush(fp); + fclose(fp); + + NSString *fileName = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:cFileName length:strlen(cFileName)]; + + NSData *input = [NSData dataWithContentsOfFile:fileName]; + NSString *result = @"A31A151AFC12B0D66A4DBE917CB55CEAA0AD639E"; + + STAssertEqualObjects([[input sha1Hash] dataToHexString], result, @"SHA1 hash > 4gb data"); + + unlink(cFileName); + free(cFileName); + */} + //utf8 string input + { + NSData *input = [@"føöbärbãz" dataUsingEncoding:NSUTF8StringEncoding]; + NSString *result = @"8A8B6142281950CBB9B01C9DF0DADB0BDAE2D0E1"; + + XCTAssertEqualObjects([[input sha1Hash] dataToHexString], result, @"SHA1 hash of UTF-8 string"); + } + +} + +- (void)testDataEncryptedWithPassword +{ + //this method generates random data, so we can only test it by doing a full round-trip + NSData *raw = [@"foo bar baz!" dataUsingEncoding:NSASCIIStringEncoding]; + NSString *password = @"123456"; + + NSData *encrypted = [raw dataEncryptedWithPassword:password]; + //check that our encrypted data is not the plaintext data + NSData *encCore = [encrypted subdataWithRange:NSMakeRange(16, [raw length])]; + XCTAssertFalse([encCore isEqualToData:raw], @"encrypted equal to plain text!"); + + //decrypt again and verify + NSData *decrypted = [encrypted dataDecryptedWithPassword:password]; + XCTAssertEqualObjects(decrypted, raw, @"decrypted data not equal to plaintext data!"); +} + +- (void)testDataEncryptedWithKeyIV +{ + NSData *iv = [@"0123456789ABCDEF" dataUsingEncoding:NSASCIIStringEncoding]; + NSData *raw = [@" " dataUsingEncoding:NSASCIIStringEncoding]; + // ^^^^^^^^^^^^^^^^ spaces because their pattern is easily recognizable in hexdumps + + unsigned char keyRaw[] = {0xda,0x39,0xa3,0xee,0x5e,0x6b,0x4b,0x0d,0x32,0x55,0xbf,0xef,0x95,0x60,0x18,0x90,0xaf,0xd8,0x07,0x09}; // sha1("") + NSData *key = [NSData dataWithBytes:keyRaw length:16]; + + //argument tests: + //key too short + { + @try { + [raw dataEncryptedWithKey:[@"password" dataUsingEncoding:NSASCIIStringEncoding] IV:iv]; + XCTFail(@"Password should not be a valid key!"); + } + @catch (NSException *exception) { + //expected + } + } + //iv too short + { + @try { + [raw dataEncryptedWithKey:key IV:[NSData data]]; + XCTFail(@"Empty IV should throw exception!"); + } + @catch (NSException *exception) { + // expected + } + } + //simple test: encrypting empty + { + NSData *enc = [[NSData data] dataEncryptedWithKey:key IV:iv]; + unsigned char expect[] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, + 0x42, 0x43, 0x44, 0x45, 0x46, 0x50, 0xc9, 0xca, 0x75, 0x14, 0xd3, + 0x6e, 0xec, 0x9e, 0xc6, 0x4c, 0x25, 0x02, 0x33, 0xdd, 0x86, 0x00, + 0x02, 0x5c, 0x2c, 0xf9, 0xa5, 0x22, 0x79, 0xa4, 0x14, 0x61, 0x90, + 0x1d, 0x9f, 0x0c, 0x7a + }; // reference data generated with OpenSSL + NSData *expData = [NSData dataWithBytesNoCopy:expect length:sizeof(expect) freeWhenDone:NO]; + XCTAssertEqualObjects(enc, expData, @"Encryption of empty data"); + } + //simple encryption test + { + NSData *enc = [raw dataEncryptedWithKey:key IV:iv]; + unsigned char expect[] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, + 0x42, 0x43, 0x44, 0x45, 0x46, 0xd3, 0x58, 0x30, 0x95, 0x6d, 0x7f, + 0xf5, 0x1e, 0x18, 0xb0, 0xbc, 0x1f, 0xb3, 0xe4, 0x52, 0xb1, 0x75, + 0x4c, 0xc3, 0x52, 0xd0, 0x93, 0xad, 0xff, 0x36, 0x4a, 0xae, 0xbe, + 0x60, 0x32, 0xdd, 0x71, 0xef, 0xce, 0x2e, 0x8b, 0x09, 0xcb, 0x9a, + 0x44, 0x32, 0xb3, 0xda, 0x42, 0x58, 0x29, 0x78, 0xc3 + }; // reference data generated with OpenSSL + NSData *expData = [NSData dataWithBytesNoCopy:expect length:sizeof(expect) freeWhenDone:NO]; + XCTAssertEqualObjects(enc, expData, @"Simple encryption test"); + } +} + +- (void)testDataDecryptedWithPassword +{ + //see test above + NSData *raw = [@" " dataUsingEncoding:NSASCIIStringEncoding]; + unsigned char encrypted[] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, + 0x42, 0x43, 0x44, 0x45, 0x46, 0xd3, 0x58, 0x30, 0x95, 0x6d, 0x7f, + 0xf5, 0x1e, 0x18, 0xb0, 0xbc, 0x1f, 0xb3, 0xe4, 0x52, 0xb1, 0x75, + 0x4c, 0xc3, 0x52, 0xd0, 0x93, 0xad, 0xff, 0x36, 0x4a, 0xae, 0xbe, + 0x60, 0x32, 0xdd, 0x71, 0xef, 0xce, 0x2e, 0x8b, 0x09, 0xcb, 0x9a, + 0x44, 0x32, 0xb3, 0xda, 0x42, 0x58, 0x29, 0x78, 0xc3 + }; + NSData *encData = [NSData dataWithBytesNoCopy:encrypted length:sizeof(encrypted) freeWhenDone:NO]; + + NSData *decrypted = [encData dataDecryptedWithPassword:@""]; + + XCTAssertEqualObjects(decrypted, raw, @"Decrypt simple data encrypted with empty password"); +} + +- (void)testDataDecryptedWithKey +{ + NSData *raw = [@" " dataUsingEncoding:NSASCIIStringEncoding]; + + unsigned char keyRaw[] = {0xda,0x39,0xa3,0xee,0x5e,0x6b,0x4b,0x0d,0x32,0x55,0xbf,0xef,0x95,0x60,0x18,0x90,0xaf,0xd8,0x07,0x09}; // sha1("") + NSData *key = [NSData dataWithBytes:keyRaw length:16]; + + unsigned char encrypted[] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, + 0x42, 0x43, 0x44, 0x45, 0x46, 0xd3, 0x58, 0x30, 0x95, 0x6d, 0x7f, + 0xf5, 0x1e, 0x18, 0xb0, 0xbc, 0x1f, 0xb3, 0xe4, 0x52, 0xb1, 0x75, + 0x4c, 0xc3, 0x52, 0xd0, 0x93, 0xad, 0xff, 0x36, 0x4a, 0xae, 0xbe, + 0x60, 0x32, 0xdd, 0x71, 0xef, 0xce, 0x2e, 0x8b, 0x09, 0xcb, 0x9a, + 0x44, 0x32, 0xb3, 0xda, 0x42, 0x58, 0x29, 0x78, 0xc3 + }; + NSData *encData = [NSData dataWithBytesNoCopy:encrypted length:sizeof(encrypted) freeWhenDone:NO]; + + // invalid key length + { + @try { + [encData dataDecryptedWithKey:[NSData data]]; + XCTFail(@"Invalid key length!"); + } + @catch (NSException *exception) { + //expected + } + } + // data too short for encryption + { + @try { + [[@"Hello World!" dataUsingEncoding:NSASCIIStringEncoding] dataDecryptedWithKey:key]; + XCTFail(@"Invalid data length!"); + } + @catch (NSException *exception) { + //expected + } + } + // wrong data with valid length + { + NSData *inp = [@"12345678901234567890123456789012" dataUsingEncoding:NSASCIIStringEncoding]; + XCTAssertNil([inp dataDecryptedWithKey:key], @"Trying to decrypt invalid data."); + } + // wrong data with invalid length + { + NSData *inp = [@"12345678901234567890123456789012345678901234567" dataUsingEncoding:NSASCIIStringEncoding]; + XCTAssertNil([inp dataDecryptedWithKey:key], @"Trying to decrypt data with invalid length."); + } + // simple decryption test + { + NSData *decrypted = [encData dataDecryptedWithKey:key]; + XCTAssertEqualObjects(decrypted, raw, @"Simple Decryption test"); + } + // malicious message test + { + //this is an empty message with a length field set to UINT32_MAX + unsigned char _encrypted[] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, + 0x42, 0x43, 0x44, 0x45, 0x46, 0x50, 0xc9, 0xca, 0x75, 0x14, 0xd3, + 0x6e, 0xec, 0x9e, 0xc6, 0x4c, 0x25, 0x02, 0x33, 0xdd, 0x86, 0x54, + 0xea, 0x1a, 0x0d, 0xe9, 0x88, 0xe3, 0xeb, 0xcb, 0xb7, 0x01, 0x52, + 0x42, 0x1c, 0xd8, 0xd5 + }; + NSData *_encData = [NSData dataWithBytesNoCopy:_encrypted length:sizeof(_encrypted) freeWhenDone:NO]; + + @try { + [_encData dataDecryptedWithKey:key]; + XCTFail(@"Malicious message with invalid data length"); + } + @catch (NSException *exception) { + //expected + } + } +} + +- (void)testEnumerateLinesBreakingAt_withBlock +{ + //simple empty data + { + __block NSUInteger invocations = 0; + NSData *data = [NSData data]; + [data enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { + invocations++; + }]; + XCTAssertTrue(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: + XCTAssertTrue(NSEqualRanges(line, NSMakeRange(0, 3)), @"range of first line"); + break; + case 1: + XCTAssertTrue(NSEqualRanges(line, NSMakeRange(4, 5)), @"range of second line"); + break; + } + invocations++; + }]; + XCTAssertTrue(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: + XCTAssertTrue(NSEqualRanges(line, NSMakeRange(0, 1)), @"range of first line"); + break; + case 1: + XCTAssertTrue(NSEqualRanges(line, NSMakeRange(3, 7)), @"range of second line"); + break; + case 2: + XCTAssertTrue(NSEqualRanges(line, NSMakeRange(12, 4)), @"range of third line"); + break; + } + invocations++; + }]; + XCTAssertTrue(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: + XCTAssertTrue(NSEqualRanges(line, NSMakeRange(0, 0)), @"range of first line"); + break; + case 1: + XCTAssertTrue(NSEqualRanges(line, NSMakeRange(1, 0)), @"range of second line"); + break; + case 2: + XCTAssertTrue(NSEqualRanges(line, NSMakeRange(3, 0)), @"range of third line"); + break; + } + invocations++; + }]; + XCTAssertTrue(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: + XCTAssertTrue(NSEqualRanges(line, NSMakeRange(0, 7)), @"range of first line"); + break; + case 1: + XCTAssertTrue(NSEqualRanges(line, NSMakeRange(9, 4)), @"range of second line"); + break; + } + invocations++; + }]; + XCTAssertTrue(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; + }]; + XCTAssertTrue(invocations==1, @"File with two lines, stopped after first"); + } +} + +@end diff --git a/UnitTests/SPDatabaseActionTest.m b/UnitTests/SPDatabaseActionTest.m index f7704df5..a74dc0f0 100644 --- a/UnitTests/SPDatabaseActionTest.m +++ b/UnitTests/SPDatabaseActionTest.m @@ -29,14 +29,14 @@ // More info at <https://github.com/sequelpro/sequelpro> #import <OCMock/OCMock.h> -#import <SenTestingKit/SenTestingKit.h> +#import <XCTest/XCTest.h> #import "SPDatabaseAction.h" #import <SPMySQL/SPMySQL.h> -@interface SPDatabaseActionTest : SenTestCase +@interface SPDatabaseActionTest : XCTestCase - (void)testCreateDatabase_01_emptyName; - (void)testCreateDatabase_02_allParams; @@ -53,7 +53,7 @@ SPDatabaseAction *createDb = [[[SPDatabaseAction alloc] init] autorelease]; [createDb setConnection:mockConnection]; - STAssertFalse([createDb createDatabase:@"" withEncoding:nil collation:nil],@"create database = NO with empty db name"); + XCTAssertFalse([createDb createDatabase:@"" withEncoding:nil collation:nil],@"create database = NO with empty db name"); OCMVerifyAll(mockConnection); } @@ -69,7 +69,7 @@ SPDatabaseAction *createDb = [[[SPDatabaseAction alloc] init] autorelease]; [createDb setConnection:mockConnection]; - STAssertTrue([createDb createDatabase:@"target_name" withEncoding:@"utf8" collation:@"utf8_bin_ci"], @"create database return"); + XCTAssertTrue([createDb createDatabase:@"target_name" withEncoding:@"utf8" collation:@"utf8_bin_ci"], @"create database return"); OCMVerifyAll(mockConnection); } @@ -84,7 +84,7 @@ SPDatabaseAction *createDb = [[[SPDatabaseAction alloc] init] autorelease]; [createDb setConnection:mockConnection]; - STAssertTrue([createDb createDatabase:@"target_name" withEncoding:@"" collation:nil], @"create database return"); + XCTAssertTrue([createDb createDatabase:@"target_name" withEncoding:@"" collation:nil], @"create database return"); OCMVerifyAll(mockConnection); } diff --git a/UnitTests/SPMenuAdditionsTests.m b/UnitTests/SPMenuAdditionsTests.m index 83647e6f..7e8b5ecc 100644 --- a/UnitTests/SPMenuAdditionsTests.m +++ b/UnitTests/SPMenuAdditionsTests.m @@ -30,7 +30,7 @@ #import "SPMenuAdditions.h" -#import <SenTestingKit/SenTestingKit.h> +#import <XCTest/XCTest.h> /** * @class SPMenuAdditionsTests SPMenuAdditionsTests.h @@ -39,7 +39,7 @@ * * SPMenuAdditionsTests tests class. */ -@interface SPMenuAdditionsTests : SenTestCase +@interface SPMenuAdditionsTests : XCTestCase { NSMenu *menu; } @@ -71,7 +71,7 @@ static NSString *SPTestMenuItemTitle = @"Menu Item"; { [menu compatibleRemoveAllItems]; - STAssertFalse([menu numberOfItems], @"The menu should have no menu items."); + XCTAssertFalse([menu numberOfItems], @"The menu should have no menu items."); } @end diff --git a/UnitTests/SPMutableArrayAdditionsTests.m b/UnitTests/SPMutableArrayAdditionsTests.m index e09f00a4..82fec400 100644 --- a/UnitTests/SPMutableArrayAdditionsTests.m +++ b/UnitTests/SPMutableArrayAdditionsTests.m @@ -30,7 +30,7 @@ #import "SPMutableArrayAdditions.h" -#import <SenTestingKit/SenTestingKit.h> +#import <XCTest/XCTest.h> /** * @class SPMutableArrayAdditionsTest SPMutableArrayAdditionsTest.h @@ -39,7 +39,7 @@ * * SPMutableArrayAdditions tests class. */ -@interface SPMutableArrayAdditionsTests : SenTestCase +@interface SPMutableArrayAdditionsTests : XCTestCase @end @@ -55,7 +55,7 @@ [testArray reverse]; - STAssertEqualObjects(testArray, expectedArray, @"The reversed array should look like: %@, but actually looks like: %@", expectedArray, testArray); + XCTAssertEqualObjects(testArray, expectedArray, @"The reversed array should look like: %@, but actually looks like: %@", expectedArray, testArray); } @end diff --git a/UnitTests/SPParserUtilsTest.m b/UnitTests/SPParserUtilsTest.m index 994b166a..6796a250 100644 --- a/UnitTests/SPParserUtilsTest.m +++ b/UnitTests/SPParserUtilsTest.m @@ -31,11 +31,11 @@ #define USE_APPLICATION_UNIT_TEST 1 #import <Cocoa/Cocoa.h> -#import <SenTestingKit/SenTestingKit.h> +#import <XCTest/XCTest.h> #include "SPParserUtils.h" -@interface SPParserUtilsTest : SenTestCase +@interface SPParserUtilsTest : XCTestCase - (void)testUtf8strlen; @@ -49,39 +49,39 @@ const char *empty = ""; NSString *emptyString = [NSString stringWithCString:empty encoding:NSUTF8StringEncoding]; - STAssertEquals(utf8strlen(empty),[emptyString length], @"empty string"); + XCTAssertEqual(utf8strlen(empty),[emptyString length], @"empty string"); // This is just a little safeguard. // If any of those conditions fail, all of the following assumptions are moot. const char *charSeq = "\xF0\x9F\x8D\x8F"; //🍏 NSString *charString = [NSString stringWithCString:charSeq encoding:NSUTF8StringEncoding]; - STAssertEquals(strlen(charSeq), (size_t)4, @"assumption about storage for binary C string"); - STAssertEquals([charString length], (NSUInteger)2, @"assumption about NSString internal storage of string"); + XCTAssertEqual(strlen(charSeq), (size_t)4, @"assumption about storage for binary C string"); + XCTAssertEqual([charString length], (NSUInteger)2, @"assumption about NSString internal storage of string"); const char *singleByteSeq = "Hello World!"; NSString *singleByteString = [NSString stringWithCString:singleByteSeq encoding:NSUTF8StringEncoding]; - STAssertEquals(utf8strlen(singleByteSeq), [singleByteString length], @"ASCII UTF-8 subset"); + XCTAssertEqual(utf8strlen(singleByteSeq), [singleByteString length], @"ASCII UTF-8 subset"); const char *twoByteSeq = "H\xC3\xA4ll\xC3\xB6 W\xC3\x9Crld\xC3\x9F!"; // Hällö WÜrldß! NSString *twoByteString = [NSString stringWithCString:twoByteSeq encoding:NSUTF8StringEncoding]; - STAssertEquals(utf8strlen(twoByteSeq), [twoByteString length], @"String containing two-byte utf8 characters"); + XCTAssertEqual(utf8strlen(twoByteSeq), [twoByteString length], @"String containing two-byte utf8 characters"); const char *threeByteSeq = "\xE3\x81\x93.\xE3\x82\x93.\xE3\x81\xAB.\xE3\x81\xA1.\xE3\x81\xAF"; // こ.ん.に.ち.は NSString *threeByteString = [NSString stringWithCString:threeByteSeq encoding:NSUTF8StringEncoding]; - STAssertEquals(utf8strlen(threeByteSeq), [threeByteString length], @"String containing three-byte utf8 characters"); + XCTAssertEqual(utf8strlen(threeByteSeq), [threeByteString length], @"String containing three-byte utf8 characters"); const char *fourByteSeq = "\xF0\x9F\x8D\x8F\xF0\x9F\x8D\x8B\xF0\x9F\x8D\x92"; //🍏🍋🍒 NSString *fourByteString = [NSString stringWithCString:fourByteSeq encoding:NSUTF8StringEncoding]; - STAssertEquals(utf8strlen(fourByteSeq), [fourByteString length], @"String containing only 4-byte utf8 characters (outside BMP)"); + XCTAssertEqual(utf8strlen(fourByteSeq), [fourByteString length], @"String containing only 4-byte utf8 characters (outside BMP)"); const char *mixedSeq = "\xE3\x81\x82\xE3\x82\x81\xE3\x80\x90\xE9\xA3\xB4\xE3\x80\x91\xF0\x9F\x8D\xAD \xE2\x89\x88 S\xC3\xBC\xC3\x9Figkeit"; // あめ【飴】🍭 ≈ Süßigkeit NSString *mixedString = [NSString stringWithCString:mixedSeq encoding:NSUTF8StringEncoding]; - STAssertEquals(utf8strlen(mixedSeq), [mixedString length], @"utf8 characters with all 4 lengths mixed together."); + XCTAssertEqual(utf8strlen(mixedSeq), [mixedString length], @"utf8 characters with all 4 lengths mixed together."); //composed vs. decomposed chars const char *decompSeq = "\xC3\xA4 - a\xCC\x88"; // ä - ä NSString *decompString = [NSString stringWithCString:decompSeq encoding:NSUTF8StringEncoding]; - STAssertEquals(utf8strlen(decompSeq), [decompString length], @"\"LATIN SMALL LETTER A WITH DIAERESIS\" vs. \"LATIN SMALL LETTER A\" + \"COMBINING DIAERESIS\""); + XCTAssertEqual(utf8strlen(decompSeq), [decompString length], @"\"LATIN SMALL LETTER A WITH DIAERESIS\" vs. \"LATIN SMALL LETTER A\" + \"COMBINING DIAERESIS\""); } @end diff --git a/UnitTests/SPStringAdditionsTests.m b/UnitTests/SPStringAdditionsTests.m index b0528ec7..eca9f7ab 100644 --- a/UnitTests/SPStringAdditionsTests.m +++ b/UnitTests/SPStringAdditionsTests.m @@ -31,17 +31,20 @@ #import "SPStringAdditions.h" #import "RegexKitLite.h" -#import <SenTestingKit/SenTestingKit.h> +#import <XCTest/XCTest.h> -@interface SPStringAdditionsTests : SenTestCase +@interface SPStringAdditionsTests : XCTestCase - (void)testStringByRemovingCharactersInSet; - (void)testStringWithNewUUID; - (void)testCreateViewSyntaxPrettifier; - (void)testNonConsecutivelySearchStringMatchingRanges; +- (void)testStringByReplacingCharactersInSetWithString; @end +static NSRange RangeFromArray(NSArray *a,NSUInteger idx); + @implementation SPStringAdditionsTests /** @@ -62,12 +65,12 @@ NSString *expectedUTFString = @"In der Krze liegt die Wrz"; NSString *expectedASCIIString = @"this is ig rzy test string with some rndom spes nd quotes"; - STAssertEqualObjects([actualASCIIString stringByRemovingCharactersInSet:junk], + XCTAssertEqualObjects([actualASCIIString stringByRemovingCharactersInSet:junk], expectedASCIIString, @"The following characters should have been removed %@", charsToRemove); - STAssertEqualObjects([actualUTFString stringByRemovingCharactersInSet:junk], + XCTAssertEqualObjects([actualUTFString stringByRemovingCharactersInSet:junk], expectedUTFString, @"The following characters should have been removed %@", charsToRemove); @@ -80,7 +83,7 @@ { NSString *uuid = [NSString stringWithNewUUID]; - STAssertTrue([uuid isMatchedByRegex:@"[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12}"], @"UUID %@ doesn't match regex", uuid); + XCTAssertTrue([uuid isMatchedByRegex:@"[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12}"], @"UUID %@ doesn't match regex", uuid); } /** @@ -93,7 +96,7 @@ NSString *actualSyntax = [originalSyntax createViewSyntaxPrettifier]; - STAssertEqualObjects([actualSyntax description], [expectedSyntax description], @"Actual view syntax '%@' does not equal expected syntax '%@'", actualSyntax, expectedSyntax); + XCTAssertEqualObjects([actualSyntax description], [expectedSyntax description], @"Actual view syntax '%@' does not equal expected syntax '%@'", actualSyntax, expectedSyntax); } - (void)testNonConsecutivelySearchStringMatchingRanges @@ -101,51 +104,79 @@ //basic tests { NSArray *matches = nil; - STAssertTrue([@"" nonConsecutivelySearchString:@"" matchingRanges:&matches], @"Equality of empty strings"); - STAssertTrue(([matches count] == 1) && NSEqualRanges(NSMakeRange(0, 0), [(NSValue *)[matches objectAtIndex:0] rangeValue]), @"Returned matches in empty string"); + XCTAssertTrue([@"" nonConsecutivelySearchString:@"" matchingRanges:&matches], @"Equality of empty strings"); + XCTAssertTrue(([matches count] == 1) && NSEqualRanges(NSMakeRange(0, 0), RangeFromArray(matches, 0)), @"Returned matches in empty string"); } { NSArray *matches = (void *)0xdeadbeef; - STAssertFalse([@"" nonConsecutivelySearchString:@"R" matchingRanges:&matches], @"Inequality with empty left side"); - STAssertTrue((matches == (void *)0xdeadbeef), @"out variable not touched by mismatch"); + XCTAssertFalse([@"" nonConsecutivelySearchString:@"R" matchingRanges:&matches], @"Inequality with empty left side"); + XCTAssertTrue((matches == (void *)0xdeadbeef), @"out variable not touched by mismatch"); } - STAssertFalse([@"L" nonConsecutivelySearchString:@"" matchingRanges:NULL], @"Inequality with empty right side"); + XCTAssertFalse([@"L" nonConsecutivelySearchString:@"" matchingRanges:NULL], @"Inequality with empty right side"); { NSArray *matches = nil; - STAssertTrue([@"left" nonConsecutivelySearchString:@"le" matchingRanges:&matches], @"Anchored match left"); - STAssertTrue(([matches count] == 1) && NSEqualRanges(NSMakeRange(0, 2), [(NSValue *)[matches objectAtIndex:0] rangeValue]), @"Returned matches in anchored left match"); + XCTAssertTrue([@"left" nonConsecutivelySearchString:@"le" matchingRanges:&matches], @"Anchored match left"); + XCTAssertTrue(([matches count] == 1) && NSEqualRanges(NSMakeRange(0, 2), RangeFromArray(matches, 0)), @"Returned matches in anchored left match"); } { NSArray *matches = nil; - STAssertTrue([@"right" nonConsecutivelySearchString:@"ht" matchingRanges:&matches], @"Anchored match right"); - STAssertTrue(([matches count] == 1) && NSEqualRanges(NSMakeRange(3, 2), [(NSValue *)[matches objectAtIndex:0] rangeValue]), @"Returned matches in anchroed right match"); + XCTAssertTrue([@"right" nonConsecutivelySearchString:@"ht" matchingRanges:&matches], @"Anchored match right"); + XCTAssertTrue(([matches count] == 1) && NSEqualRanges(NSMakeRange(3, 2), RangeFromArray(matches, 0)), @"Returned matches in anchroed right match"); } - STAssertFalse([@"ht" nonConsecutivelySearchString:@"right" matchingRanges:NULL], @"Left and Right are not commutative"); + XCTAssertFalse([@"ht" nonConsecutivelySearchString:@"right" matchingRanges:NULL], @"Left and Right are not commutative"); //real tests { NSArray *matches = nil; - STAssertTrue([@"... is not secure anymore!" nonConsecutivelySearchString:@"NSA" matchingRanges:&matches], @"Non-consecutive match, ignoring case"); - STAssertTrue(([matches count] == 3) && - (NSEqualRanges(NSMakeRange(7, 1), [(NSValue *)[matches objectAtIndex:0] rangeValue])) && - (NSEqualRanges(NSMakeRange(11, 1), [(NSValue *)[matches objectAtIndex:1] rangeValue])) && - (NSEqualRanges(NSMakeRange(18, 1), [(NSValue *)[matches objectAtIndex:2] rangeValue])), @"Returned matches in non-consecutive string"); + XCTAssertTrue([@"... is not secure anymore!" nonConsecutivelySearchString:@"NSA" matchingRanges:&matches], @"Non-consecutive match, ignoring case"); + XCTAssertTrue(([matches count] == 3) && + NSEqualRanges(NSMakeRange( 7, 1), RangeFromArray(matches, 0)) && + NSEqualRanges(NSMakeRange(11, 1), RangeFromArray(matches, 1)) && + NSEqualRanges(NSMakeRange(18, 1), RangeFromArray(matches, 2)), @"Returned matches in non-consecutive string"); } - STAssertFalse([@"Deoxyribonucleic Acid" nonConsecutivelySearchString:@"DNS" matchingRanges:NULL], @"Non-consecutive mismatch"); + XCTAssertFalse([@"Deoxyribonucleic Acid" nonConsecutivelySearchString:@"DNS" matchingRanges:NULL], @"Non-consecutive mismatch"); { NSArray *matches = nil; - STAssertTrue([@"Turn left, then right at the corner" nonConsecutivelySearchString:@"left right" matchingRanges:&matches], @"Partly consecutive match"); - STAssertTrue(([matches count] == 3) && - (NSEqualRanges(NSMakeRange(5, 4), [(NSValue *)[matches objectAtIndex:0] rangeValue])) && - (NSEqualRanges(NSMakeRange(10, 1), [(NSValue *)[matches objectAtIndex:1] rangeValue])) && - (NSEqualRanges(NSMakeRange(16, 5), [(NSValue *)[matches objectAtIndex:2] rangeValue])), @"Returned matches in partly-consecutive string"); + XCTAssertTrue([@"Turn left, then right at the corner" nonConsecutivelySearchString:@"left right" matchingRanges:&matches], @"Partly consecutive match"); + XCTAssertTrue(([matches count] == 2) && + (NSEqualRanges(NSMakeRange( 5, 4), RangeFromArray(matches, 0))) && + (NSEqualRanges(NSMakeRange(15, 6), RangeFromArray(matches, 1))), @"Returned matches in partly-consecutive string"); + } + + //optimization tests + { + NSArray *matches = nil; + // Haystack: "central_private_rabbit_park" + // Needle: "centralpark" + // Unoptimized: "central_private_rabbit_park" + // ^^^^^^^ ^ ^ ^ ^ = 5 (after optimizing consecutive atomic matches) + // Desired: "central_private_rabbit_park" + // ^^^^^^^ ^^^^ = 2 + XCTAssertTrue([@"central_private_rabbit_park" nonConsecutivelySearchString:@"centralpark" matchingRanges:&matches], @"Optimization partly consecutive match"); + XCTAssertTrue((([matches count] == 2) && + (NSEqualRanges(NSMakeRange( 0, 7), RangeFromArray(matches, 0))) && + (NSEqualRanges(NSMakeRange(23, 4), RangeFromArray(matches, 1)))), @"Returned matches set is minimal"); + } + { + // In the previous test it was always the end of the matches array that got optimized. + // This time we'll have two different optimizations + // Needle: ".abc123" + // Haystack: "a.?a?ab?abc?1?12?123?" + // Unoptimized: ^ ^ ^ ^ ^ ^ ^ = 7 + // Desired: ^ ^^^ ^^^ = 3 + NSArray *matches = nil; + XCTAssertTrue([@"a.?a?ab?abc?1?12?123?" nonConsecutivelySearchString:@".abc123" matchingRanges:&matches], @"Optimization non-consecutive match"); + XCTAssertTrue((([matches count] == 3) && + (NSEqualRanges(NSMakeRange( 1, 1), RangeFromArray(matches, 0))) && + (NSEqualRanges(NSMakeRange( 8, 3), RangeFromArray(matches, 1))) && + (NSEqualRanges(NSMakeRange(17, 3), RangeFromArray(matches, 2)))), @"Returned matches set is minimal (2)"); } //advanced tests @@ -153,16 +184,41 @@ // LATIN CAPITAL LETTER A == LATIN SMALL LETTER A // LATIN SMALL LETTER O WITH DIAERESIS == LATIN SMALL LETTER O // FULLWIDTH LATIN SMALL LETTER b == LATIN SMALL LETTER B - STAssertTrue([@"A:\xC3\xB6:\xEF\xBD\x82" nonConsecutivelySearchString:@"aob" matchingRanges:NULL], @"Fuzzy matching of defined characters"); + XCTAssertTrue([@"A:\xC3\xB6:\xEF\xBD\x82" nonConsecutivelySearchString:@"aob" matchingRanges:NULL], @"Fuzzy matching of defined characters"); //all bytes on the right are contained on the left, but on a character level "ä" is not contained in "Hütte Ф" - STAssertFalse([@"H\xC3\xBCtte \xD0\xA4" nonConsecutivelySearchString:@"\xC3\xA4" matchingRanges:NULL], @"Mismatch of composed characters with same prefix"); + XCTAssertFalse([@"H\xC3\xBCtte \xD0\xA4" nonConsecutivelySearchString:@"\xC3\xA4" matchingRanges:NULL], @"Mismatch of composed characters with same prefix"); // ":😥:𠘄:" vs "😄" (according to wikipedia "𠘄" is the arachic variant of "印") // TECHNICALLY THIS SHOULD NOT MATCH! // However Apple doesn't correctly handle characters in the 4-Byte UTF range, so let's use this test to check for changes in Apples behaviour :) - STAssertTrue([@":\xF0\x9F\x98\x84:\xF0\xA0\x98\x84:" nonConsecutivelySearchString:@"\xF0\x9F\x98\x84" matchingRanges:NULL], @"Mismatch of composed characters (4-byte) with same prefix"); + XCTAssertTrue([@":\xF0\x9F\x98\x84:\xF0\xA0\x98\x84:" nonConsecutivelySearchString:@"\xF0\x9F\x98\x84" matchingRanges:NULL], @"Mismatch of composed characters (4-byte) with same prefix"); } +- (void)testStringByReplacingCharactersInSetWithString +{ + { + //test against empty string + XCTAssertEqualObjects([@"" stringByReplacingCharactersInSet:[NSCharacterSet whitespaceCharacterSet] withString:@"x"], @"", @"replacement on empty string must result in empty string"); + } + { + //test match at begin, middle, end / consecutive matches + XCTAssertEqualObjects([@" ab c " stringByReplacingCharactersInSet:[NSCharacterSet whitespaceCharacterSet] withString:@"_"], @"_ab__c_", @"Testing matches at both end, replacement of consecutive matches"); + } + { + //test replacement of different characters + XCTAssertEqualObjects([@"ab\r\ncd" stringByReplacingCharactersInSet:[NSCharacterSet newlineCharacterSet] withString:@"*"], @"ab**cd", @"Testing replacement of different characters in set"); + } + { + // nil for replacement char + XCTAssertEqualObjects([@"ab\r\ncd" stringByReplacingCharactersInSet:[NSCharacterSet newlineCharacterSet] withString:nil], @"abcd", @"testing replacement with nil"); + } +} + @end + +NSRange RangeFromArray(NSArray *a,NSUInteger idx) +{ + return [(NSValue *)[a objectAtIndex:idx] rangeValue]; +} diff --git a/UnitTests/SPTableCopyTest.m b/UnitTests/SPTableCopyTest.m index 76a2fe45..563322df 100644 --- a/UnitTests/SPTableCopyTest.m +++ b/UnitTests/SPTableCopyTest.m @@ -31,60 +31,71 @@ #import "SPTableCopy.h" #import <SPMySQL/SPMySQL.h> -#import <SenTestingKit/SenTestingKit.h> +#import <XCTest/XCTest.h> #import <OCMock/OCMock.h> #define USE_APPLICATION_UNIT_TEST 1 -@interface SPTableCopyTest : SenTestCase +@interface SPTableCopyTest : XCTestCase - (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]; + [tableCopy setConnection:mockConnection]; + + XCTAssertFalse([tableCopy copyTable:@"table_name" from:@"source_db" to:@"target_db"], @"copy operation must fail."); + + [tableCopy release]; + } + + [mockConnection verify]; +} + @end diff --git a/UnitTests/SPTableFilterParserTest.m b/UnitTests/SPTableFilterParserTest.m index d5f46969..1ff69e7f 100644 --- a/UnitTests/SPTableFilterParserTest.m +++ b/UnitTests/SPTableFilterParserTest.m @@ -12,9 +12,9 @@ #define USE_APPLICATION_UNIT_TEST 1 #import <Cocoa/Cocoa.h> -#import <SenTestingKit/SenTestingKit.h> +#import <XCTest/XCTest.h> -@interface SPTableFilterParserTest : SenTestCase +@interface SPTableFilterParserTest : XCTestCase - (void)testFilterString; @@ -29,7 +29,7 @@ [p setCurrentField:@"FLD"]; // binary matches as "$BINARY ", eating the one additional whitespace - STAssertEqualObjects([p filterString],@"`FLD` constant string", @"Constant replacement"); + XCTAssertEqualObjects([p filterString],@"`FLD` constant string", @"Constant replacement"); } //simple one argument case with binary { @@ -38,7 +38,7 @@ [p setCaseSensitive:YES]; [p setArgument:@"arg1"]; - STAssertEqualObjects([p filterString], @"`FLD2` = FOO(BINARY arg1)", @"One Argument, $BINARY variable"); + XCTAssertEqualObjects([p filterString], @"`FLD2` = FOO(BINARY arg1)", @"One Argument, $BINARY variable"); } //simple two argument case with explicit current field { @@ -48,7 +48,7 @@ [p setFirstBetweenArgument:@"LA"]; [p setSecondBetweenArgument:@"RA"]; - STAssertEqualObjects([p filterString], @"MIN(`FLD3`,LA) = RA", @"Two Arguments, $CURRENT_FIELD variable"); + XCTAssertEqualObjects([p filterString], @"MIN(`FLD3`,LA) = RA", @"Two Arguments, $CURRENT_FIELD variable"); } } @@ -11,7 +11,7 @@ Build Instructions ================== * Install the [latest version of Xcode](https://itunes.apple.com/au/app/xcode/id497799835) - * Install [Github for Mac](http://mac.github.com) (or [Tower](http://www.git-tower.com), or [SourceTree](http://www.sourcetreeapp.com), or […](http://git-scm.com/downloads/guis)) + * Install [Github for Mac](https://desktop.github.com/) (or [Tower](https://www.git-tower.com/), or [SourceTree](https://www.sourcetreeapp.com/), or […](http://git-scm.com/downloads/guis)) * Click "Clone in Desktop" on the right sidebar of our [github page](https://github.com/sequelpro/sequelpro) * Open `sequel-pro.xcodeproj` * Click the `Run` button in the toolbar @@ -20,6 +20,6 @@ Build Instructions License ======= -Copyright (c) 2002-2015 Sequel Pro & CocoaMySQL Teams. All rights reserved. +Copyright (c) 2002-2016 Sequel Pro & CocoaMySQL Teams. All rights reserved. -Sequel Pro is free and open source software, licensed under [MIT](http://opensource.org/licenses/MIT). See [LICENSE](https://github.com/sequelpro/sequelpro/blob/master/LICENSE) for full details. +Sequel Pro is free and open source software, licensed under [MIT](https://opensource.org/licenses/MIT). See [LICENSE](https://github.com/sequelpro/sequelpro/blob/master/LICENSE) for full details. diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index fd4f3207..aaca3759 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 */; }; @@ -178,13 +179,48 @@ 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 */; }; + 500C1F921BFB5F9F0095DC7F /* SPPrivilegesMO.m in Sources */ = {isa = PBXBuildFile; fileRef = 500C1F911BFB5F9F0095DC7F /* SPPrivilegesMO.m */; }; + 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 */; }; + 502D22151BA62FA5000D4CE7 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5EAC0FC0EC87FF900CC579C /* Security.framework */; }; + 502D22161BA62FF5000D4CE7 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5EAC0FC0EC87FF900CC579C /* Security.framework */; }; 503B02CA1AE82C5E0060CAB1 /* SPTableFilterParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 503B02C91AE82C5E0060CAB1 /* SPTableFilterParser.m */; }; 503B02CF1AE95C2C0060CAB1 /* SPTableFilterParserTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 503B02CE1AE95C2C0060CAB1 /* SPTableFilterParserTest.m */; }; 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 */; }; + 507FF2421BC33BBC00104523 /* SPOSInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 50EAB5B71A8FBB08008F627A /* SPOSInfo.m */; }; + 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 */; }; 50D3C3491A75B8A800B5429C /* GotoDatabaseDialog.xib in Resources */ = {isa = PBXBuildFile; fileRef = 50D3C34B1A75B8A800B5429C /* GotoDatabaseDialog.xib */; }; 50D3C3521A77135F00B5429C /* SPParserUtils.c in Sources */ = {isa = PBXBuildFile; fileRef = 50D3C3501A77135F00B5429C /* SPParserUtils.c */; }; @@ -406,7 +442,6 @@ BC09D7E312A786FB0030DB64 /* cancel.png in Resources */ = {isa = PBXBuildFile; fileRef = BC09D7DD12A786FB0030DB64 /* cancel.png */; }; BC0E1486120AAB5600E52E25 /* SPDataAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = BC2C16D30FEBEDF10003993B /* SPDataAdditions.m */; }; BC0E1487120AAB5C00E52E25 /* SPStringAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 1789343B0F30C1DD0097539A /* SPStringAdditions.m */; }; - BC0E1493120AABE900E52E25 /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 17B7B58F1016028F00F057DE /* libcrypto.dylib */; }; BC0E14A1120AAC2E00E52E25 /* libbz2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 179ECEC611F265EE009C6A40 /* libbz2.dylib */; }; BC0ED3DA12A9196C00088461 /* SPChooseMenuItemDialog.m in Sources */ = {isa = PBXBuildFile; fileRef = BC0ED3D912A9196C00088461 /* SPChooseMenuItemDialog.m */; }; BC1847EA0FE6EC8400094BFB /* SPEditSheetTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = BC1847E90FE6EC8400094BFB /* SPEditSheetTextView.m */; }; @@ -503,6 +538,13 @@ remoteGlobalIDString = 17E5969E14F307CE0054EE08; remoteInfo = Tests; }; + 29B70BD51C805B2A00D1BE0C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 584D876015140D3500F24774 /* SPMySQLFramework.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 507FF1D51BC0D7D300104523; + remoteInfo = "SPMySQL Unit Tests"; + }; 5847571D120A1C6D0057631F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 2A37F4A9FDCFA73011CA2CEA /* Project object */; @@ -755,7 +797,6 @@ 17A7773711C52E61001E27B4 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = Interfaces/English.lproj/IndexesView.xib; sourceTree = "<group>"; }; 17AF787911FC41C00073D043 /* SPExportFilenameUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPExportFilenameUtilities.h; sourceTree = "<group>"; }; 17AF787A11FC41C00073D043 /* SPExportFilenameUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExportFilenameUtilities.m; sourceTree = "<group>"; }; - 17B7B58F1016028F00F057DE /* libcrypto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcrypto.dylib; path = /usr/lib/libcrypto.dylib; sourceTree = "<absolute>"; }; 17B7B591101602AE00F057DE /* libssl.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libssl.dylib; path = /usr/lib/libssl.dylib; sourceTree = "<absolute>"; }; 17BA2A3015275D8600389803 /* SPExportInterfaceController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPExportInterfaceController.h; sourceTree = "<group>"; }; 17BA2A3115275D8600389803 /* SPExportInterfaceController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExportInterfaceController.m; sourceTree = "<group>"; }; @@ -849,6 +890,8 @@ 17F90E4A1210B43A00274C98 /* SPExportFileUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExportFileUtilities.m; sourceTree = "<group>"; }; 17FDB04A1280778B00DBBBC2 /* SPFontPreviewTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPFontPreviewTextField.h; sourceTree = "<group>"; }; 17FDB04B1280778B00DBBBC2 /* SPFontPreviewTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPFontPreviewTextField.m; sourceTree = "<group>"; }; + 1A56463D14569A0B56EE8BAC /* SPPillAttachmentCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPPillAttachmentCell.m; sourceTree = "<group>"; }; + 1A564C0C0FFB444D2E5CA447 /* SPPillAttachmentCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPPillAttachmentCell.h; sourceTree = "<group>"; }; 296DC89E0F8FD336002A3258 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = /System/Library/Frameworks/WebKit.framework; sourceTree = "<absolute>"; }; 296DC8A50F909194002A3258 /* MGTemplateMarker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGTemplateMarker.h; sourceTree = "<group>"; }; 296DC8A60F909194002A3258 /* MGTemplateFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGTemplateFilter.h; sourceTree = "<group>"; }; @@ -874,7 +917,7 @@ 29FA88221114619E00D1AF3D /* SPTableTriggers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableTriggers.m; sourceTree = "<group>"; }; 2A37F4C4FDCFA73011CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; }; 2A37F4C5FDCFA73011CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; }; - 380F4ED90FC0B50500B0BFD7 /* Unit Tests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Unit Tests.octest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 380F4ED90FC0B50500B0BFD7 /* Unit Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Unit Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 380F4EF40FC0B68F00B0BFD7 /* SPStringAdditionsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPStringAdditionsTests.m; sourceTree = "<group>"; }; 384582C30FB95FF800DDACB6 /* func-small.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "func-small.png"; sourceTree = "<group>"; }; 384582C60FB9603600DDACB6 /* proc-small.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "proc-small.png"; sourceTree = "<group>"; }; @@ -889,8 +932,15 @@ 4D90B7A1101E0D1500D116A1 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/UserManagerView.xib; sourceTree = "<group>"; }; 4DECC3320EC2A170008D359E /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = Frameworks/Sparkle.framework; sourceTree = "<group>"; }; 4DECC3340EC2A170008D359E /* Growl.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Growl.framework; path = Frameworks/Growl.framework; sourceTree = "<group>"; }; + 500C1F901BFB5F9F0095DC7F /* SPPrivilegesMO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPPrivilegesMO.h; sourceTree = "<group>"; }; + 500C1F911BFB5F9F0095DC7F /* SPPrivilegesMO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPPrivilegesMO.m; sourceTree = "<group>"; }; + 500DA4B51BEFF877000773FE /* SPComboBoxCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPComboBoxCell.h; sourceTree = "<group>"; }; + 500DA4B61BEFF877000773FE /* SPComboBoxCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPComboBoxCell.m; sourceTree = "<group>"; }; + 500DA4BA1BF0CD57000773FE /* SPScreenAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPScreenAdditions.h; sourceTree = "<group>"; }; + 500DA4BB1BF0CD57000773FE /* SPScreenAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPScreenAdditions.m; sourceTree = "<group>"; }; 501B1D161728A3DA0017C92E /* SPCharsetCollationHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPCharsetCollationHelper.h; sourceTree = "<group>"; }; 501B1D171728A3DA0017C92E /* SPCharsetCollationHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPCharsetCollationHelper.m; sourceTree = "<group>"; }; + 502D21F51BA50710000D4CE7 /* SPDataAdditionsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDataAdditionsTests.m; sourceTree = "<group>"; }; 5037F79A1B00148000733564 /* SPNamedNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SPNamedNode.h; sourceTree = "<group>"; }; 503B02C81AE82C5E0060CAB1 /* SPTableFilterParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableFilterParser.h; sourceTree = "<group>"; }; 503B02C91AE82C5E0060CAB1 /* SPTableFilterParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableFilterParser.m; sourceTree = "<group>"; }; @@ -898,6 +948,15 @@ 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 = "<group>"; }; 506CE9301A311C6C0039F736 /* SPTableContentFilterController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableContentFilterController.m; sourceTree = "<group>"; }; + 507FF1101BBCC4C400104523 /* SPFunctions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPFunctions.h; sourceTree = "<group>"; }; + 507FF1111BBCC57600104523 /* SPFunctions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPFunctions.m; sourceTree = "<group>"; }; + 507FF2681BC8450100104523 /* SPExportSettingsPersistence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPExportSettingsPersistence.h; sourceTree = "<group>"; }; + 507FF2691BC8450100104523 /* SPExportSettingsPersistence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExportSettingsPersistence.m; sourceTree = "<group>"; }; + 508022941BF7BA470052A9B2 /* English */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = English; path = English.lproj/SPQLPluginExportSettingsTemplate.html; sourceTree = "<group>"; }; + 50805B0B1BF2A068005F7A99 /* SPPopUpButtonCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPPopUpButtonCell.h; sourceTree = "<group>"; }; + 50805B0C1BF2A068005F7A99 /* SPPopUpButtonCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPPopUpButtonCell.m; sourceTree = "<group>"; }; + 5089B0251BE714E300E226CD /* SPIdMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPIdMenu.h; sourceTree = "<group>"; }; + 5089B0261BE714E300E226CD /* SPIdMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPIdMenu.m; sourceTree = "<group>"; }; 50A9F8AF19EAD4B90053E571 /* SPGotoDatabaseController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPGotoDatabaseController.h; sourceTree = "<group>"; }; 50A9F8B019EAD4B90053E571 /* SPGotoDatabaseController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPGotoDatabaseController.m; sourceTree = "<group>"; }; 50D3C34A1A75B8A800B5429C /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/GotoDatabaseDialog.xib; sourceTree = "<group>"; }; @@ -1272,6 +1331,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 502D22151BA62FA5000D4CE7 /* Security.framework in Frameworks */, 1717F9DB1558114D0065C036 /* OCMock.framework in Frameworks */, 1717FA43155831600065C036 /* libicucore.dylib in Frameworks */, 50EA92671AB23EE1008D3C4F /* SPMySQL.framework in Frameworks */, @@ -1282,8 +1342,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 50082B471BF7D1F600746ECC /* libicucore.dylib in Frameworks */, + 502D22161BA62FF5000D4CE7 /* Security.framework in Frameworks */, BC0E14A1120AAC2E00E52E25 /* libbz2.dylib in Frameworks */, - BC0E1493120AABE900E52E25 /* libcrypto.dylib in Frameworks */, 584D87C415141A5D00F24774 /* libz.dylib in Frameworks */, 58475686120A065B0057631F /* CoreFoundation.framework in Frameworks */, 584756B5120A06740057631F /* CoreServices.framework in Frameworks */, @@ -1358,7 +1419,6 @@ 296DC8BE0F9091DF002A3258 /* libicucore.dylib */, 179ECEC611F265EE009C6A40 /* libbz2.dylib */, 17B7B591101602AE00F057DE /* libssl.dylib */, - 17B7B58F1016028F00F057DE /* libcrypto.dylib */, 584D87BE15141A4A00F24774 /* libz.dylib */, ); name = "Linked Frameworks"; @@ -1426,6 +1486,10 @@ B57747DB0F7A89D0003B34F9 /* SPFavoriteTextFieldCell.m */, BC5750D412A6233900911BA2 /* SPActivityTextFieldCell.h */, BC5750D312A6233900911BA2 /* SPActivityTextFieldCell.m */, + 1A56463D14569A0B56EE8BAC /* SPPillAttachmentCell.m */, + 1A564C0C0FFB444D2E5CA447 /* SPPillAttachmentCell.h */, + 500DA4B51BEFF877000773FE /* SPComboBoxCell.h */, + 500DA4B61BEFF877000773FE /* SPComboBoxCell.m */, ); name = Cells; sourceTree = "<group>"; @@ -1554,6 +1618,8 @@ 4D90B79C101E0CF200D116A1 /* SPUserMO.h */, 4D90B79D101E0CF200D116A1 /* SPUserMO.m */, 4D90B79B101E0CF200D116A1 /* SPUserManager.xcdatamodel */, + 500C1F901BFB5F9F0095DC7F /* SPPrivilegesMO.h */, + 500C1F911BFB5F9F0095DC7F /* SPPrivilegesMO.m */, ); name = "Core Data"; sourceTree = "<group>"; @@ -1936,6 +2002,7 @@ 380F4EF40FC0B68F00B0BFD7 /* SPStringAdditionsTests.m */, 1760599E1336199D0098E162 /* SPMenuAdditionsTests.m */, 1798F1C2155018D4004B0AB8 /* SPMutableArrayAdditionsTests.m */, + 502D21F51BA50710000D4CE7 /* SPDataAdditionsTests.m */, ); name = "Category Additions"; sourceTree = "<group>"; @@ -2065,6 +2132,7 @@ 17E6416E0EF01F3B001BC333 /* Other */ = { isa = PBXGroup; children = ( + 507FF10E1BBCC4A900104523 /* Utility */, 1198F5B01174EDA700670590 /* Database Actions */, 583CE39511722B70008F148E /* File Compression */, 173284E51088FEC20062E892 /* Data */, @@ -2258,6 +2326,8 @@ 173C837C11AAD2C500B8B084 /* Delegate Protocols */, 173C837D11AAD2D300B8B084 /* Delegate Categories */, 50D3C3831A8177D900B5429C /* SPExportController+SharedPrivateAPI.h */, + 507FF2681BC8450100104523 /* SPExportSettingsPersistence.h */, + 507FF2691BC8450100104523 /* SPExportSettingsPersistence.m */, ); name = "Data Export"; sourceTree = "<group>"; @@ -2291,6 +2361,8 @@ 17FDB04B1280778B00DBBBC2 /* SPFontPreviewTextField.m */, 58D2A6A516FBDEFF002EB401 /* SPComboPopupButton.h */, 58D2A6A616FBDEFF002EB401 /* SPComboPopupButton.m */, + 50805B0B1BF2A068005F7A99 /* SPPopUpButtonCell.h */, + 50805B0C1BF2A068005F7A99 /* SPPopUpButtonCell.m */, ); name = Controls; sourceTree = "<group>"; @@ -2299,7 +2371,7 @@ isa = PBXGroup; children = ( 8D15AC370486D014006FF6A4 /* Sequel Pro.app */, - 380F4ED90FC0B50500B0BFD7 /* Unit Tests.octest */, + 380F4ED90FC0B50500B0BFD7 /* Unit Tests.xctest */, 58CDB3360FCE13C900F8ACA3 /* SequelProTunnelAssistant */, 58B906E611BD989A000826E5 /* PSMTabBar.framework */, 58B9096111C3A42B000826E5 /* xibLocalizationPostprocessor */, @@ -2365,6 +2437,17 @@ path = UnitTests; sourceTree = "<group>"; }; + 507FF10E1BBCC4A900104523 /* Utility */ = { + isa = PBXGroup; + children = ( + 507FF1101BBCC4C400104523 /* SPFunctions.h */, + 507FF1111BBCC57600104523 /* SPFunctions.m */, + 5089B0251BE714E300E226CD /* SPIdMenu.h */, + 5089B0261BE714E300E226CD /* SPIdMenu.m */, + ); + name = Utility; + sourceTree = "<group>"; + }; 50D3C3591A771C2300B5429C /* Other */ = { isa = PBXGroup; children = ( @@ -2402,6 +2485,7 @@ 584756FE120A1B290057631F /* SPQLPluginQueryFavoritesTemplate.html */, 58475700120A1B290057631F /* SPQLPluginSQLTemplate.html */, BCEC861F12115A30002561DA /* SPQLPluginConnectionBundleWindowTemplate.html */, + 508022931BF7BA470052A9B2 /* SPQLPluginExportSettingsTemplate.html */, ); name = "QuickLook Plugin"; sourceTree = "<group>"; @@ -2410,6 +2494,7 @@ isa = PBXGroup; children = ( 584D876815140D3500F24774 /* SPMySQL.framework */, + 29B70BD61C805B2A00D1BE0C /* SPMySQL Unit Tests.xctest */, ); name = Products; sourceTree = "<group>"; @@ -2603,6 +2688,8 @@ 1798F19715501838004B0AB8 /* SPMutableArrayAdditions.m */, 584D899B15162CBE00F24774 /* SPDataBase64EncodingAdditions.h */, 584D899C15162CBE00F24774 /* SPDataBase64EncodingAdditions.m */, + 500DA4BA1BF0CD57000773FE /* SPScreenAdditions.h */, + 500DA4BB1BF0CD57000773FE /* SPScreenAdditions.m */, ); name = "Category Additions"; sourceTree = "<group>"; @@ -2634,6 +2721,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 */, @@ -2658,8 +2754,8 @@ ); name = "Unit Tests"; productName = "Unit Tests"; - productReference = 380F4ED90FC0B50500B0BFD7 /* Unit Tests.octest */; - productType = "com.apple.product-type.bundle.ocunit-test"; + productReference = 380F4ED90FC0B50500B0BFD7 /* Unit Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; }; 584754C1120A04560057631F /* Sequel Pro QLGenerator */ = { isa = PBXNativeTarget; @@ -2815,6 +2911,13 @@ remoteRef = 17E596A114F307CE0054EE08 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 29B70BD61C805B2A00D1BE0C /* SPMySQL Unit Tests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = "SPMySQL Unit Tests.xctest"; + remoteRef = 29B70BD51C805B2A00D1BE0C /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 584D876815140D3500F24774 /* SPMySQL.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; @@ -2830,6 +2933,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 */, @@ -3052,10 +3156,15 @@ 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 */, 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 */, @@ -3075,6 +3184,15 @@ 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 */, BC34F3281292AD6F000DA1AA /* SPConstants.m in Sources */, BC6D709E120C4C9F008027B5 /* SPEditorTokens.l in Sources */, @@ -3119,6 +3237,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 */, @@ -3135,6 +3254,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 */, @@ -3193,18 +3313,21 @@ 50EAB5B81A8FBB08008F627A /* SPOSInfo.m in Sources */, 173C4366104455E0001F3A30 /* SPQueryFavoriteManager.m in Sources */, 173C44D81044A6B0001F3A30 /* SPOutlineView.m in Sources */, + 500C1F921BFB5F9F0095DC7F /* SPPrivilegesMO.m in Sources */, 17F5B1511048C4E400FC794F /* SPCSVExporter.m in Sources */, 17F5B1541048C50D00FC794F /* SPExporter.m in Sources */, 17F5B39C1049B96A00FC794F /* SPSQLExporter.m in Sources */, 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 */, 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 */, @@ -3227,6 +3350,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 */, @@ -3255,6 +3379,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 */, @@ -3274,6 +3399,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 */, @@ -3310,6 +3436,7 @@ 501B1D181728A3DA0017C92E /* SPCharsetCollationHelper.m in Sources */, 50E217B318174246009D3580 /* SPColorSelectorView.m in Sources */, 50E217B618174280009D3580 /* SPFavoriteColorSupport.m in Sources */, + 1A564F74237E2E4958CA593A /* SPPillAttachmentCell.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3451,6 +3578,14 @@ name = UserManagerView.xib; sourceTree = "<group>"; }; + 508022931BF7BA470052A9B2 /* SPQLPluginExportSettingsTemplate.html */ = { + isa = PBXVariantGroup; + children = ( + 508022941BF7BA470052A9B2 /* English */, + ); + name = SPQLPluginExportSettingsTemplate.html; + sourceTree = "<group>"; + }; 50D3C34B1A75B8A800B5429C /* GotoDatabaseDialog.xib */ = { isa = PBXVariantGroup; children = ( @@ -3726,7 +3861,7 @@ COPY_PHASE_STRIP = NO; FRAMEWORK_SEARCH_PATHS = ( "$(SRCROOT)/Frameworks", - "$(DEVELOPER_LIBRARY_DIR)/Frameworks", + "$(inherited)", ); GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; @@ -3741,13 +3876,9 @@ "-framework", Cocoa, "-framework", - SenTestingKit, - "-framework", OCMock, ); PRODUCT_NAME = "Unit Tests"; - TEST_AFTER_BUILD = YES; - WRAPPER_EXTENSION = octest; }; name = Debug; }; @@ -3759,7 +3890,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; FRAMEWORK_SEARCH_PATHS = ( "$(SRCROOT)/Frameworks", - "$(DEVELOPER_LIBRARY_DIR)/Frameworks", + "$(inherited)", ); GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_MODEL_TUNING = G5; @@ -3772,13 +3903,9 @@ "-framework", Cocoa, "-framework", - SenTestingKit, - "-framework", OCMock, ); PRODUCT_NAME = "Unit Tests"; - TEST_AFTER_BUILD = YES; - WRAPPER_EXTENSION = octest; }; name = Release; }; @@ -3789,7 +3916,7 @@ COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(SRCROOT)/Frameworks", - "$(DEVELOPER_LIBRARY_DIR)/Frameworks", + "$(inherited)", ); GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_MODEL_TUNING = G5; @@ -3802,13 +3929,9 @@ "-framework", Foundation, "-framework", - SenTestingKit, - "-framework", OCMock, ); PRODUCT_NAME = "Unit Tests"; - TEST_AFTER_BUILD = YES; - WRAPPER_EXTENSION = octest; }; name = Distribution; }; @@ -3927,10 +4050,7 @@ INSTALL_PATH = "$(HOME)/Applications"; LIBRARY_SEARCH_PATHS = "$(SRCROOT)/Frameworks/zlib"; MACOSX_DEPLOYMENT_TARGET = 10.6; - OTHER_LDFLAGS = ( - "$(SRCROOT)/Frameworks/zlib/libz.a", - "-lcrypto", - ); + OTHER_LDFLAGS = "$(SRCROOT)/Frameworks/zlib/libz.a"; PRODUCT_NAME = "Sequel Pro"; SEPARATE_STRIP = NO; }; @@ -4130,10 +4250,7 @@ INSTALL_PATH = "$(HOME)/Applications"; LIBRARY_SEARCH_PATHS = "$(SRCROOT)/Frameworks/zlib"; MACOSX_DEPLOYMENT_TARGET = 10.6; - OTHER_LDFLAGS = ( - "$(SRCROOT)/Frameworks/zlib/libz.a", - "-lcrypto", - ); + OTHER_LDFLAGS = "$(SRCROOT)/Frameworks/zlib/libz.a"; PRODUCT_NAME = "Sequel Pro"; SEPARATE_STRIP = NO; }; @@ -4157,10 +4274,7 @@ INSTALL_PATH = "$(HOME)/Applications"; LIBRARY_SEARCH_PATHS = "$(SRCROOT)/Frameworks/zlib"; MACOSX_DEPLOYMENT_TARGET = 10.6; - OTHER_LDFLAGS = ( - "$(SRCROOT)/Frameworks/zlib/libz.a", - "-lcrypto", - ); + OTHER_LDFLAGS = "$(SRCROOT)/Frameworks/zlib/libz.a"; PRODUCT_NAME = "Sequel Pro"; SEPARATE_STRIP = NO; }; diff --git a/sequel-pro.xcodeproj/xcshareddata/xcschemes/Sequel Pro (10.6 SDK).xcscheme b/sequel-pro.xcodeproj/xcshareddata/xcschemes/Sequel Pro (10.6 SDK).xcscheme index e6b89ad7..00d9dc14 100644 --- a/sequel-pro.xcodeproj/xcshareddata/xcschemes/Sequel Pro (10.6 SDK).xcscheme +++ b/sequel-pro.xcodeproj/xcshareddata/xcschemes/Sequel Pro (10.6 SDK).xcscheme @@ -33,7 +33,7 @@ <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "380F4ED80FC0B50500B0BFD7" - BuildableName = "Unit Tests.octest" + BuildableName = "Unit Tests.xctest" BlueprintName = "Unit Tests" ReferencedContainer = "container:sequel-pro.xcodeproj"> </BuildableReference> @@ -48,6 +48,8 @@ ReferencedContainer = "container:sequel-pro.xcodeproj"> </BuildableReference> </MacroExpansion> + <AdditionalOptions> + </AdditionalOptions> </TestAction> <LaunchAction selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" @@ -57,8 +59,10 @@ buildConfiguration = "Debug" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" + debugServiceExtension = "internal" allowLocationSimulation = "YES"> - <BuildableProductRunnable> + <BuildableProductRunnable + runnableDebuggingMode = "0"> <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "8D15AC270486D014006FF6A4" @@ -76,7 +80,8 @@ useCustomWorkingDirectory = "NO" buildConfiguration = "Release" debugDocumentVersioning = "YES"> - <BuildableProductRunnable> + <BuildableProductRunnable + runnableDebuggingMode = "0"> <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "8D15AC270486D014006FF6A4" diff --git a/sequel-pro.xcodeproj/xcshareddata/xcschemes/Sequel Pro Release Build (10.6+).xcscheme b/sequel-pro.xcodeproj/xcshareddata/xcschemes/Sequel Pro Release Build (10.6+).xcscheme index a3785caa..2492f320 100644 --- a/sequel-pro.xcodeproj/xcshareddata/xcschemes/Sequel Pro Release Build (10.6+).xcscheme +++ b/sequel-pro.xcodeproj/xcshareddata/xcschemes/Sequel Pro Release Build (10.6+).xcscheme @@ -42,7 +42,7 @@ <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "380F4ED80FC0B50500B0BFD7" - BuildableName = "Unit Tests.octest" + BuildableName = "Unit Tests.xctest" BlueprintName = "Unit Tests" ReferencedContainer = "container:sequel-pro.xcodeproj"> </BuildableReference> @@ -57,6 +57,8 @@ ReferencedContainer = "container:sequel-pro.xcodeproj"> </BuildableReference> </MacroExpansion> + <AdditionalOptions> + </AdditionalOptions> </TestAction> <LaunchAction selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" @@ -66,6 +68,7 @@ buildConfiguration = "Distribution" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" + debugServiceExtension = "internal" allowLocationSimulation = "YES"> <BuildableProductRunnable runnableDebuggingMode = "0"> diff --git a/sequel-pro.xcodeproj/xcshareddata/xcschemes/Sequel Pro.xcscheme b/sequel-pro.xcodeproj/xcshareddata/xcschemes/Sequel Pro.xcscheme index 579336f6..34c76c45 100644 --- a/sequel-pro.xcodeproj/xcshareddata/xcschemes/Sequel Pro.xcscheme +++ b/sequel-pro.xcodeproj/xcshareddata/xcschemes/Sequel Pro.xcscheme @@ -33,7 +33,7 @@ <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "380F4ED80FC0B50500B0BFD7" - BuildableName = "Unit Tests.octest" + BuildableName = "Unit Tests.xctest" BlueprintName = "Unit Tests" ReferencedContainer = "container:sequel-pro.xcodeproj"> </BuildableReference> @@ -55,6 +55,8 @@ isEnabled = "YES"> </EnvironmentVariable> </EnvironmentVariables> + <AdditionalOptions> + </AdditionalOptions> </TestAction> <LaunchAction selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" @@ -64,8 +66,10 @@ buildConfiguration = "Debug" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" + debugServiceExtension = "internal" allowLocationSimulation = "YES"> - <BuildableProductRunnable> + <BuildableProductRunnable + runnableDebuggingMode = "0"> <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "8D15AC270486D014006FF6A4" @@ -83,7 +87,8 @@ useCustomWorkingDirectory = "NO" buildConfiguration = "Release" debugDocumentVersioning = "YES"> - <BuildableProductRunnable> + <BuildableProductRunnable + runnableDebuggingMode = "0"> <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "8D15AC270486D014006FF6A4" |