diff options
180 files changed, 8861 insertions, 3825 deletions
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/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..38544a84 --- /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>com.sequelpro.spmysql-unittests</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..32dca24c 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" */; @@ -445,6 +501,11 @@ isa = PBXProject; attributes = { LastUpgradeCheck = 0510; + TargetAttributes = { + 507FF1D41BC0D7D300104523 = { + CreatedOnToolsVersion = 6.2; + }; + }; }; buildConfigurationList = 1DEB91B108733DA50010E9CD /* Build configuration list for PBXProject "SPMySQLFramework" */; compatibilityVersion = "Xcode 3.2"; @@ -462,11 +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; @@ -559,7 +644,6 @@ DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; - GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = Source/SPMySQLFramework_Prefix.pch; INFOPLIST_FILE = Resources/Info.plist; @@ -644,6 +728,198 @@ }; name = Release; }; + 507FF1DF1BC0D7D300104523 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = 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_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_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_NAME = "$(TARGET_NAME)"; + }; + name = Distribution; + }; + 507FF2361BC0E0A800104523 /* Unit Testing */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + CLANG_LINK_OBJC_RUNTIME = NO; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ""; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; + GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; + GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; + GCC_WARN_MISSING_PARENTHESES = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_STRICT_SELECTOR_MATCH = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACH_O_TYPE = mh_dylib; + MACOSX_DEPLOYMENT_TARGET = 10.6; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + VALID_ARCHS = "i386 x86_64"; + }; + name = "Unit Testing"; + }; + 507FF2371BC0E0A800104523 /* Unit Testing */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_DYNAMIC_NO_PIC = NO; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = Source/SPMySQLFramework_Prefix.pch; + GCC_PREPROCESSOR_DEFINITIONS = SPMYSQL_FOR_UNIT_TESTING; + GENERATE_PKGINFO_FILE = YES; + INFOPLIST_FILE = Resources/Info.plist; + INSTALL_PATH = "@executable_path/../Frameworks"; + LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/MySQL Client Libraries/lib\"", + ); + PRODUCT_NAME = SPMySQL; + SKIP_INSTALL = YES; + WRAPPER_EXTENSION = framework; + }; + name = "Unit Testing"; + }; + 507FF2381BC0E0A800104523 /* Unit Testing */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = 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_NAME = "$(TARGET_NAME)"; + }; + name = "Unit Testing"; + }; 586AA55214F5D599007F82BF /* Distribution */ = { isa = XCBuildConfiguration; buildSettings = { @@ -688,7 +964,6 @@ DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; - GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = Source/SPMySQLFramework_Prefix.pch; INFOPLIST_FILE = Resources/Info.plist; @@ -710,6 +985,7 @@ isa = XCConfigurationList; buildConfigurations = ( 1DEB91AE08733DA50010E9CD /* Debug */, + 507FF2371BC0E0A800104523 /* Unit Testing */, 1DEB91AF08733DA50010E9CD /* Release */, 586AA55314F5D599007F82BF /* Distribution */, ); @@ -720,12 +996,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/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 e8338bb4..7940b483 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m @@ -80,6 +80,10 @@ */ - (void)_threadedKeepAlive { + if(keepAliveThread) { + NSLog(@"warning: overwriting existing keepAliveThread: %@, results may be unpredictable!",keepAliveThread); + } + keepAliveThread = [NSThread currentThread]; [keepAliveThread setName:@"SPMySQL connection keepalive monitor thread"]; @@ -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; @@ -238,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(); @@ -250,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]; + // Mark the thread as cancelled + [keepAliveThread cancel]; - // Wait inside a time limit of ten seconds for it to exit - uint64_t threadCancelStartTime_t = mach_absolute_time(); - do { - usleep(100000); - if (_elapsedSecondsSinceAbsoluteTime(threadCancelStartTime_t) > 10) break; - } while (keepAliveThread); + // Wait inside a time limit of ten seconds for it to exit + uint64_t threadCancelStartTime_t = mach_absolute_time(); + do { + usleep(100000); + if (_elapsedSecondsSinceAbsoluteTime(threadCancelStartTime_t) > 10) return NO; + } while (keepAliveThread); + + } + + return YES; } @end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m index 48f4fc1e..e5a48d48 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,6 +295,7 @@ // Lock the connection while it's actively in use [self _lockConnection]; + unsigned long long theAffectedRowCount; while (queryAttemptsAllowed > 0) { // While recording the overall execution time (including network lag!), run @@ -296,6 +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,11 @@ return nil; } [self _lockConnection]; + NSAssert(mySQLConnection != NULL, @"mySQLConnection has disappeared while checking it!"); queryAttemptsAllowed--; } - unsigned long long theAffectedRowCount = mysql_affected_rows(mySQLConnection); id theResult = nil; // On success, if there is a query result, retrieve the result data type @@ -660,6 +673,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..b09a27ee 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 { @@ -897,6 +933,7 @@ asm(".desc ___crashreporter_info__, 0x10"); [self _unlockConnection]; [self _cancelKeepAlives]; + [self _lockConnection]; // Close the underlying MySQL connection if it still appears to be active, and not reading // or writing. While this may result in a leak of the MySQL object, it prevents crashes // due to attempts to close a blocked/stuck connection. @@ -904,17 +941,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,10 +975,6 @@ asm(".desc ___crashreporter_info__, 0x10"); [variables setObject:[variableRow objectAtIndex:1] forKey:[variableRow objectAtIndex:0]]; } - // Copy the server version string to the instance variable - if (serverVariableVersion) [serverVariableVersion release], serverVariableVersion = nil; - serverVariableVersion = [[variables objectForKey:@"version"] retain]; - // Get the connection encoding. Although a specific encoding may have been requested on // connection, it may be overridden by init_connect commands or connection state changes. // Default to latin1 for older server versions. @@ -995,6 +1027,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 +1053,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.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 a6bfe95c..85f9728a 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..dd04904d 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"> @@ -5878,8 +5840,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 +6694,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 +6719,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 +6919,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 +7165,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 +7358,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 +7430,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 +7483,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 +7503,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 +7528,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 +7580,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 +7632,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 +7665,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 +7687,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 +7812,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 +8084,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"> @@ -8412,7 +8368,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 +9018,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 +9074,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 +9131,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 +9143,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 +10142,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 +11270,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 +11433,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 +11921,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 +11984,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 +12553,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 +12628,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 +13428,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 +14980,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 +16141,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 +17488,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> @@ -24261,6 +24423,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 +24490,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 +24562,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 +24572,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 +24645,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 +25149,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 +25485,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 +25916,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"/> @@ -26111,6 +26322,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 +26563,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 +27049,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 +27289,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 +27297,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 +27309,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 +27319,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 +27355,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 +27555,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 +28717,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 +28957,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 +29452,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 +29552,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 +29818,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 +30103,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 +30323,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 +31323,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 +31337,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 +31650,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 +31741,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 6d8c7119..f9a9fc84 100644 --- a/Interfaces/English.lproj/FieldEditorSheet.xib +++ b/Interfaces/English.lproj/FieldEditorSheet.xib @@ -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"/> 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 7a2988d2..31f3ff83 100644 --- a/Interfaces/English.lproj/MainMenu.xib +++ b/Interfaces/English.lproj/MainMenu.xib @@ -1845,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"> @@ -3420,6 +3428,14 @@ <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"/> @@ -4385,6 +4401,7 @@ <reference ref="607642194"/> <reference ref="859280893"/> <reference ref="346403677"/> + <reference ref="682123491"/> </array> <reference key="parent" ref="19149132"/> </object> @@ -5130,6 +5147,11 @@ <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"> @@ -5766,6 +5788,7 @@ <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> @@ -6069,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> @@ -6741,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> @@ -6792,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> @@ -7111,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> @@ -7160,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> @@ -7855,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> @@ -8027,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> @@ -8093,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> @@ -8332,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> @@ -8435,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> @@ -8607,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> @@ -8862,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> @@ -8962,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"> @@ -9228,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> @@ -9247,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:"> @@ -9383,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> @@ -9397,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:"> @@ -10868,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> @@ -10882,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> @@ -10978,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> @@ -11067,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> @@ -12324,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/elements b/Models/SPUserManager.xcdatamodel/elements Binary files differindex a4d11321..9723af21 100644 --- a/Models/SPUserManager.xcdatamodel/elements +++ b/Models/SPUserManager.xcdatamodel/elements diff --git a/Models/SPUserManager.xcdatamodel/layout b/Models/SPUserManager.xcdatamodel/layout Binary files differindex 62662ba9..7fe6d9bf 100644 --- a/Models/SPUserManager.xcdatamodel/layout +++ b/Models/SPUserManager.xcdatamodel/layout 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/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..c4a82db9 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> 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/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 99e64e59..c92b635d 100644 --- a/Source/SPAlertSheets.h +++ b/Source/SPAlertSheets.h @@ -59,6 +59,12 @@ void SPBeginAlertSheet( void SPOnewayAlertSheet( NSString *title, + NSWindow *docWindow, + NSString *msg +); + +void SPOnewayAlertSheetWithStyle( + NSString *title, NSString *defaultButton, NSWindow *docWindow, NSString *msg, diff --git a/Source/SPAlertSheets.m b/Source/SPAlertSheets.m index d19da6df..258aac6d 100644 --- a/Source/SPAlertSheets.m +++ b/Source/SPAlertSheets.m @@ -144,6 +144,17 @@ @end /** + * Shorthand for SPOnewayAlertSheetWithStyle() with defaultButton=nil and alertStyle=NSWarningAlertStyle + */ +void SPOnewayAlertSheet( + NSString *title, + NSWindow *docWindow, + NSString *msg) +{ + SPOnewayAlertSheetWithStyle(title, nil, docWindow, msg, NSWarningAlertStyle); +} + +/** * A Send-and-forget variant for displaying alerts. * It will queue the alert on the main thread and *always* immediately return. * Because of that there is no way to set a delegate and callback method @@ -151,7 +162,7 @@ * If nil is passed as the button title it will be changed to @"OK". * If nil is passed as the window NSAlert will be modal */ -void SPOnewayAlertSheet( +void SPOnewayAlertSheetWithStyle( NSString *title, NSString *defaultButton, NSWindow *docWindow, diff --git a/Source/SPAppController.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/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/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 0340fd8e..91629bf3 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -53,6 +53,7 @@ #import "SPFavoriteColorSupport.h" #import "SPNamedNode.h" #import "SPWindowController.h" +#import "SPFavoritesOutlineView.h" #import <SPMySQL/SPMySQL.h> @@ -62,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 () @@ -169,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; } @@ -183,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; } } @@ -200,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; } @@ -211,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; } @@ -222,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; } @@ -343,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) { @@ -430,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]; @@ -485,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. */ @@ -1013,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 @@ -1143,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; } @@ -1407,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; } @@ -1704,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")]; @@ -1942,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 f2816745..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, @@ -623,6 +644,16 @@ extern NSString *SPURLSchemeQueryResultStatusPathHeader; extern NSString *SPURLSchemeQueryResultMetaPathHeader; extern NSString *SPCommonCryptoExceptionName; +extern NSString *SPErrorDomain; // generic SP error domain for NSError + +typedef NS_ENUM(NSInteger,SPErrorCode) { // error codes in SPErrorDomain + /** When plist deserialization fails with nil return and no NSError or the returned object has the wrong type */ + SPErrorWrongTypeOrNil = 110001, + /** Parsed data is syntactically correct, but semantically wrong (e.g. a SPF file with the wrong content format */ + SPErrorWrongContentType = 110002, + /** Some data has a version that we don't know how to handle (can be used with e.g. SPF files, which have explicit version numbers) */ + SPErrorWrongContentVersion = 110003, +}; #define SPAppDelegate ((SPAppController *)[NSApp delegate]) @@ -654,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 42631dab..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,6 +433,11 @@ NSString *SPBundleShellVariableSelectedText = @"SP_SELECTED_TEXT NSString *SPBundleShellVariableSelectedTextRange = @"SP_SELECTED_TEXT_RANGE"; NSString *SPBundleShellVariableUsedQueryForTable = @"SP_USED_QUERY_FOR_TABLE"; +#define OWS @"\\s*" /* optional whitespace */ +// CURRENT_TIMESTAMP [ ( [n] ) ] +NSString *SPCurrentTimestampPattern = (@"^" OWS @"CURRENT_TIMESTAMP" @"(?:" OWS @"\\(" OWS @"(\\d*)" OWS @"\\)" @")?" OWS @"$"); +#undef OWS + // URL scheme NSString *SPURLSchemeQueryInputPathHeader = @"/tmp/SP_QUERY_"; NSString *SPURLSchemeQueryResultPathHeader = @"/tmp/SP_QUERY_RESULT_"; @@ -422,6 +445,7 @@ NSString *SPURLSchemeQueryResultStatusPathHeader = @"/tmp/SP_QUERY_RESULT_STAT NSString *SPURLSchemeQueryResultMetaPathHeader = @"/tmp/SP_QUERY_META_"; NSString *SPCommonCryptoExceptionName = @"SPCommonCryptoException"; +NSString *SPErrorDomain = @"SPErrorDomain"; void inline _SPClear(id *addr) { [*addr release], *addr = nil; diff --git a/Source/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 c344f078..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 @""; @@ -2568,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]; } } } @@ -3996,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 @@ -4006,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 5158c270..cd8374f6 100644 --- a/Source/SPDataAdditions.h +++ b/Source/SPDataAdditions.h @@ -28,6 +28,13 @@ // // 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; @@ -46,4 +53,6 @@ - (NSString *)stringRepresentationUsingEncoding:(NSStringEncoding)encoding; - (NSString *)shortStringRepresentationUsingEncoding:(NSStringEncoding)encoding; +- (void)enumerateLinesBreakingAt:(SPLineTerminator)lbChars withBlock:(void (^)(NSRange line,BOOL *stop))block; + @end diff --git a/Source/SPDataAdditions.m b/Source/SPDataAdditions.m index 8002595c..65605577 100644 --- a/Source/SPDataAdditions.m +++ b/Source/SPDataAdditions.m @@ -37,7 +37,13 @@ #include <zlib.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 - @@ -74,11 +80,12 @@ uint32_t LimitUInt32(NSUInteger i); { // Create a random 128-bit initialization vector // IV is block "-1" of plaintext data, therefore it is blockSize long - srand((unsigned int)time(NULL)); - NSInteger ivIndex; unsigned char iv[kCCBlockSizeAES128]; - for (ivIndex = 0; ivIndex < kCCBlockSizeAES128; ivIndex++) - iv[ivIndex] = rand() & 0xff; + if(SPBetterRandomBytes(iv,sizeof(iv)) != 0) + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:@"Getting random data bytes failed!" + userInfo:@{@"errno":@(errno)}]; + NSData *ivData = [NSData dataWithBytes:iv length:sizeof(iv)]; // Create the key from first 128-bits of the 160-bit password hash @@ -431,6 +438,54 @@ uint32_t LimitUInt32(NSUInteger i); return string; } +- (void)enumerateLinesBreakingAt:(SPLineTerminator)lbChars withBlock:(void (^)(NSRange line,BOOL *stop))block +{ + if(lbChars == SPLineTerminatorAny) lbChars = SPLineTerminatorCR|SPLineTerminatorLF|SPLineTerminatorCRLF; + + const uint8_t *bytes = [self bytes]; + NSUInteger length = [self length]; + + NSUInteger curStart = 0; + SPLineTerminator terminatorFound = 0; + NSUInteger i; + for (i = 0; i < length; i++) { + uint8_t chr = bytes[i]; + // if looking for cr and/or crlf we look for cr otherwise for lf + if(((lbChars & SPLineTerminatorCRLF) || (lbChars & SPLineTerminatorCR)) && chr == '\r') { + //if we are looking for CRLF check for the following LF + if((lbChars & SPLineTerminatorCRLF) && ((i+1) < length) && bytes[i+1] == '\n') { + terminatorFound = SPLineTerminatorCRLF; + } + //if we were looking for CR we've found one + else if((lbChars & SPLineTerminatorCR)) { + terminatorFound = SPLineTerminatorCR; + } + } + else if((lbChars & SPLineTerminatorLF) && chr == '\n') { + terminatorFound = SPLineTerminatorLF; + } + // no linebreak yet ? + if(!terminatorFound) continue; + + // found one. call the block. + BOOL stop = NO; + NSRange lineRange = NSMakeRange(curStart, (i-curStart)); + block(lineRange,&stop); + if(stop) return; + + // reset vars for next line + if(terminatorFound == SPLineTerminatorCRLF) i++; //skip the \n in CRLF + curStart = (i+1); + terminatorFound = 0; + } + // there could we one unterminated line left in buffer + if(curStart < i) { + NSRange lineRange = NSMakeRange(curStart, (i-curStart)); + BOOL iDontCare = NO; + block(lineRange,&iDontCare); + } +} + @end #pragma mark - diff --git a/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..bae17111 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) @@ -60,7 +65,13 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore NSUInteger i; editedRowCount = 0; SPClear(editedRows); - if (unloadedColumns) free(unloadedColumns), unloadedColumns = NULL; + @synchronized(self) { + if (unloadedColumns) { + [self _recordClearingUnloadedColumnsAt:mach_absolute_time() from:[NSThread callStackSymbols]]; + free(unloadedColumns), unloadedColumns = NULL; + } + } + if (dataStorage) { @@ -81,9 +92,11 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore [self resultStoreDidFinishLoadingData:dataStorage]; } - unloadedColumns = calloc(numberOfColumns, sizeof(BOOL)); - for (i = 0; i < numberOfColumns; i++) { - unloadedColumns[i] = NO; + @synchronized(self) { + unloadedColumns = calloc(numberOfColumns, sizeof(BOOL)); + for (i = 0; i < numberOfColumns; i++) { + unloadedColumns[i] = NO; + } } } @@ -110,10 +123,12 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore NSMutableArray *dataArray = SPMySQLResultStoreGetRow(dataStorage, anIndex); // Modify unloaded cells as appropriate - for (NSUInteger i = 0; i < numberOfColumns; i++) { - NSAssert(unloadedColumns != NULL, @"unloadedColumns not loaded!"); - if (unloadedColumns[i]) { - CFArraySetValueAtIndex((CFMutableArrayRef)dataArray, i, [SPNotLoaded notLoaded]); + @synchronized(self) { + [self _assesUnloadedColumnsIsSet]; + for (NSUInteger i = 0; i < numberOfColumns; i++) { + if (unloadedColumns[i]) { + CFArraySetValueAtIndex((CFMutableArrayRef)dataArray, i, [SPNotLoaded notLoaded]); + } } } @@ -140,9 +155,11 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore } // If the specified column is not loaded, return a SPNotLoaded reference - NSAssert(unloadedColumns != NULL, @"unloadedColumns not loaded!"); - if (unloadedColumns[columnIndex]) { - return [SPNotLoaded notLoaded]; + @synchronized(self) { + [self _assesUnloadedColumnsIsSet]; + if (unloadedColumns[columnIndex]) { + return [SPNotLoaded notLoaded]; + } } // Return the content @@ -175,9 +192,11 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore } // If the specified column is not loaded, return a SPNotLoaded reference - NSAssert(unloadedColumns != NULL, @"unloadedColumns not loaded!"); - if (unloadedColumns[columnIndex]) { - return [SPNotLoaded notLoaded]; + @synchronized(self) { + [self _assesUnloadedColumnsIsSet]; + if (unloadedColumns[columnIndex]) { + return [SPNotLoaded notLoaded]; + } } // Return the content @@ -203,9 +222,11 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore [NSException raise:NSRangeException format:@"Requested storage column (col %llu) beyond bounds (%llu)", (unsigned long long)columnIndex, (unsigned long long)numberOfColumns]; } - NSAssert(unloadedColumns != NULL, @"unloadedColumns not loaded!"); - if (unloadedColumns[columnIndex]) { - return YES; + @synchronized(self) { + [self _assesUnloadedColumnsIsSet]; + if (unloadedColumns[columnIndex]) { + return YES; + } } return [dataStorage cellIsNullAtRow:rowIndex column:columnIndex]; @@ -236,10 +257,12 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore targetRow = SPMySQLResultStoreGetRow(dataStorage, state->state); // Modify unloaded cells as appropriate - for (NSUInteger i = 0; i < numberOfColumns; i++) { - NSAssert(unloadedColumns != NULL, @"unloadedColumns not loaded!"); - if (unloadedColumns[i]) { - CFArraySetValueAtIndex((CFMutableArrayRef)targetRow, i, [SPNotLoaded notLoaded]); + @synchronized(self) { + [self _assesUnloadedColumnsIsSet]; + for (NSUInteger i = 0; i < numberOfColumns; i++) { + if (unloadedColumns[i]) { + CFArraySetValueAtIndex((CFMutableArrayRef)targetRow, i, [SPNotLoaded notLoaded]); + } } } } @@ -397,8 +420,10 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore if (columnIndex >= numberOfColumns) { [NSException raise:NSRangeException format:@"Invalid column set as unloaded; requested column index (%llu) beyond bounds (%llu)", (unsigned long long)columnIndex, (unsigned long long)numberOfColumns]; } - NSAssert(unloadedColumns != NULL, @"unloadedColumns not loaded!"); - unloadedColumns[columnIndex] = YES; + @synchronized(self) { + [self _assesUnloadedColumnsIsSet]; + unloadedColumns[columnIndex] = YES; + } } #pragma mark - Basic information @@ -451,6 +476,9 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore numberOfColumns = 0; editedRowCount = 0; + + _debugInfo = nil; + _debugTime = mach_absolute_time(); } return self; } @@ -458,8 +486,19 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore - (void) dealloc { SPClear(dataStorage); SPClear(editedRows); - if (unloadedColumns) free(unloadedColumns), unloadedColumns = NULL; - + @synchronized(self) { + if (unloadedColumns) { + [self _recordClearingUnloadedColumnsAt:mach_absolute_time() from:[NSThread callStackSymbols]]; + free(unloadedColumns), unloadedColumns = NULL; + } + } + // this is very very unlikely, but if another thread had been waiting on the lock + // right before we free'd unloadedColumns, it should get it before we can release + // _debugInfo, too. + @synchronized(self) { + SPClear(_debugInfo); + } + [super dealloc]; } @@ -474,5 +513,37 @@ static inline NSMutableArray* SPDataStorageGetEditedRow(NSPointerArray* rowStore } } +// DO NOT CALL THIS METHOD UNLESS YOU CURRENTLY HAVE A LOCK ON SELF!!! +- (void)_recordClearingUnloadedColumnsAt:(uint64_t)now from:(NSArray *)callStack +{ + _debugTime = now; + SPClear(_debugInfo); + _debugInfo = [[NSString alloc] initWithFormat:@"Thread: %@, Stack: %@",[NSThread currentThread],callStack]; +} + +// DO NOT CALL THIS METHOD UNLESS YOU CURRENTLY HAVE A LOCK ON SELF!!! +- (void)_assesUnloadedColumnsIsSet +{ + if(unloadedColumns != NULL) + return; + + uint64_t timeDiff = _elapsedMilliSecondsSinceAbsoluteTime(_debugTime); + + NSString *msg; + if(!_debugInfo) + msg = [NSString stringWithFormat:@"unloadedColumns is not set and never has been since the object was created %llums ago.",timeDiff]; + else + msg = [NSString stringWithFormat:@"unloadedColumns was last cleared %llums ago at %@",timeDiff,_debugInfo]; + + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:msg userInfo:nil]; +} @end + +static uint64_t _elapsedMilliSecondsSinceAbsoluteTime(uint64_t comparisonTime) +{ + uint64_t elapsedTime_t = mach_absolute_time() - comparisonTime; + Nanoseconds elapsedTime = AbsoluteToNanoseconds(*(AbsoluteTime *)&(elapsedTime_t)); + + return (UnsignedWideToUInt64(elapsedTime) / 1000000ULL); +} 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 5c0553f8..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,10 +6240,8 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; SPOnewayAlertSheet( NSLocalizedString(@"Error", @"error"), - nil, parentWindow, - [NSString stringWithFormat:NSLocalizedString(@"Unable to select database %@.\nPlease check you have the necessary privileges to view the database, and that the database still exists.", @"message of panel when connection to db failed after selecting from popupbutton"), targetDatabaseName], - NSWarningAlertStyle + [NSString stringWithFormat:NSLocalizedString(@"Unable to select database %@.\nPlease check you have the necessary privileges to view the database, and that the database still exists.", @"message of panel when connection to db failed after selecting from popupbutton"), targetDatabaseName] ); } @@ -6268,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]; } } } @@ -6290,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..a4663268 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]; @@ -334,8 +276,6 @@ [queriedStructure setObject:[NSMutableDictionary dictionary] forKey:db_id]; NSMutableDictionary *databaseStructure = [queriedStructure objectForKey:db_id]; - NSString *currentDatabaseEscaped = [currentDatabase stringByReplacingOccurrencesOfString:@"`" withString:@"``"]; - NSUInteger uniqueCounter = 0; // used to make field data unique SPMySQLResult *theResult; @@ -345,48 +285,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 +306,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 +345,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 +383,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 +452,7 @@ { [[NSNotificationCenter defaultCenter] removeObserver:self]; - [self destroy:nil]; + [self _destroy:nil]; SPClear(structureRetrievalThreads); pthread_mutex_destroy(&threadManagementLock); @@ -588,6 +474,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 +514,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 +526,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 +569,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..26574621 100644 --- a/Source/SPExtendedTableInfo.m +++ b/Source/SPExtendedTableInfo.m @@ -119,7 +119,7 @@ static NSString *SPMySQLCommentField = @"Comment"; if ([currentType isEqualToString:newType]) return; // If the table is empty, perform the change directly - if ([[[tableDataInstance statusValues] objectForKey:SPMySQLRowsField] isEqualToString:@"0"]) { + if ([[tableDataInstance statusValueForKey:SPMySQLRowsField] isEqualToString:@"0"]) { [self _changeCurrentTableTypeFrom:currentType to:newType]; return; } @@ -171,9 +171,11 @@ static NSString *SPMySQLCommentField = @"Comment"; else { [sender selectItemWithTitle:currentEncoding]; - SPBeginAlertSheet(NSLocalizedString(@"Error changing table encoding", @"error changing table encoding message"), - NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table encoding to '%@'.\n\nMySQL said: %@", @"error changing table encoding informative message"), newEncoding, [connection lastErrorMessage]]); + SPOnewayAlertSheet( + NSLocalizedString(@"Error changing table encoding", @"error changing table encoding message"), + [NSApp mainWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table encoding to '%@'.\n\nMySQL said: %@", @"error changing table encoding informative message"), newEncoding, [connection lastErrorMessage]] + ); } } @@ -198,9 +200,11 @@ static NSString *SPMySQLCommentField = @"Comment"; else { [sender selectItemWithTitle:currentCollation]; - SPBeginAlertSheet(NSLocalizedString(@"Error changing table collation", @"error changing table collation message"), - NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table collation to '%@'.\n\nMySQL said: %@", @"error changing table collation informative message"), newCollation, [connection lastErrorMessage]]); + SPOnewayAlertSheet( + NSLocalizedString(@"Error changing table collation", @"error changing table collation message"), + [NSApp mainWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table collation to '%@'.\n\nMySQL said: %@", @"error changing table collation informative message"), newCollation, [connection lastErrorMessage]] + ); } } @@ -546,9 +550,11 @@ static NSString *SPMySQLCommentField = @"Comment"; [self reloadTable:self]; } else { - SPBeginAlertSheet(NSLocalizedString(@"Error changing table comment", @"error changing table comment message"), - NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table's comment to '%@'.\n\nMySQL said: %@", @"error changing table comment informative message"), newComment, [connection lastErrorMessage]]); + SPOnewayAlertSheet( + NSLocalizedString(@"Error changing table comment", @"error changing table comment message"), + [NSApp mainWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table's comment to '%@'.\n\nMySQL said: %@", @"error changing table comment informative message"), newComment, [connection lastErrorMessage]] + ); } } } @@ -650,9 +656,11 @@ static NSString *SPMySQLCommentField = @"Comment"; [tableTypePopUpButton selectItemWithTitle:currentType]; - SPBeginAlertSheet(NSLocalizedString(@"Error changing table type", @"error changing table type message"), - NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, - [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table type to '%@'.\n\nMySQL said: %@", @"error changing table type informative message"), newType, [connection lastErrorMessage]]); + SPOnewayAlertSheet( + NSLocalizedString(@"Error changing table type", @"error changing table type message"), + [NSApp mainWindow], + [NSString stringWithFormat:NSLocalizedString(@"An error occurred when trying to change the table type to '%@'.\n\nMySQL said: %@", @"error changing table type informative message"), newType, [connection lastErrorMessage]] + ); return; } diff --git a/Source/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.m b/Source/SPFieldMapperController.m index 9bef9d03..8e662b99 100644 --- a/Source/SPFieldMapperController.m +++ b/Source/SPFieldMapperController.m @@ -788,7 +788,10 @@ static NSUInteger SPSourceColumnTypeInteger = 1; if([importFieldNamesHeaderSwitch state] == NSOnState) { 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++) { @@ -876,11 +879,13 @@ static NSUInteger SPSourceColumnTypeInteger = 1; BOOL serverGreaterThanVersion4 = ([mySQLConnection serverMajorVersion] >= 5) ? YES : NO; BOOL importFirstRowAsFieldNames = ([importFieldNamesHeaderSwitch state] == NSOnState); - NSString *headerName; + 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)]]; @@ -1043,9 +1048,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) { @@ -1478,9 +1481,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; @@ -1574,7 +1577,9 @@ static NSUInteger SPSourceColumnTypeInteger = 1; return; } 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; } @@ -1606,15 +1611,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; 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..e462b8cb --- /dev/null +++ b/Source/SPFunctions.h @@ -0,0 +1,44 @@ +// +// 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); diff --git a/Source/SPFunctions.m b/Source/SPFunctions.m new file mode 100644 index 00000000..d1c72555 --- /dev/null +++ b/Source/SPFunctions.m @@ -0,0 +1,71 @@ +// +// 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; +} diff --git a/Source/SPGotoDatabaseController.m b/Source/SPGotoDatabaseController.m index b4c11a8c..61ec8962 100644 --- a/Source/SPGotoDatabaseController.m +++ b/Source/SPGotoDatabaseController.m @@ -303,7 +303,7 @@ static BOOL StringQualifiesForWordSearch(NSString *s); NSUInteger mgc2 = [[(SPGotoFilteredItem *)obj2 matches] count]; if(mgc1 < mgc2) return NSOrderedAscending; - if(mgc2 > mgc1) + if(mgc2 < mgc1) return NSOrderedDescending; } // For strings with the same number of match groups we just sort alphabetically @@ -396,15 +396,30 @@ static BOOL StringQualifiesForWordSearch(NSString *s); [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; } 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 9f51d652..bf8210ea 100644 --- a/Source/SPObjectAdditions.m +++ b/Source/SPObjectAdditions.m @@ -29,8 +29,6 @@ // More info at <https://github.com/sequelpro/sequelpro> #import <objc/runtime.h> -static NSMutableDictionary *gScrollViewListeners; -static NSMutableDictionary *gScrollViewDealloc; @implementation NSObject (SPObjectAdditions) @@ -46,143 +44,45 @@ static NSMutableDictionary *gScrollViewDealloc; 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; - } - - 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"]; - - [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]]; - - NSString *key = [NSString stringWithFormat:@"snd=%p,obs=%p",obj,self]; - - [msg appendFormat:@"registration info for pair (%@):\n %@\n\n",key,[gScrollViewListeners objectForKey:key]]; - - NSString *deallocKey = [NSString stringWithFormat:@"=%p",self]; - [msg appendFormat:@"self %@ was originally dealloc'ed at:\n %@",deallocKey,[gScrollViewDealloc objectForKey:deallocKey]]; - - @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:msg userInfo:nil]; + return [list containsObject:self]; } @end +#pragma mark - -@implementation NSNotificationCenter (SPScrollViewDebug) +@interface NSAlert (ApplePrivate) -+ (void)load -{ - static dispatch_once_t onceToken; - - dispatch_once(&onceToken, ^{ - gScrollViewListeners = [[NSMutableDictionary alloc] init]; - - Class notificationCenter = [self class]; - - SEL orig = @selector(addObserver:selector:name:object:); - SEL exch = @selector(sp_addObserver:selector:name:object:); - - Method origM = class_getInstanceMethod(notificationCenter, orig); - Method exchM = class_getInstanceMethod(notificationCenter, exch); - - method_exchangeImplementations(origM, exchM); - }); - -} - -- (void)sp_addObserver:(id)notificationObserver selector:(SEL)notificationSelector name:(NSString *)notificationName object:(id)notificationSender -{ - if(notificationSelector == @selector(_scrollViewDidChangeBounds:) && [notificationName isEqualToString:NSViewBoundsDidChangeNotification]) { - NSString *key = [NSString stringWithFormat:@"snd=%p,obs=%p",notificationSender,notificationObserver]; - NSMutableString *val = [NSMutableString string]; - [val appendFormat:@"observer: %1$p (class %2$@) description: %1$@\n",notificationObserver,[notificationObserver className]]; - if([notificationObserver isKindOfClass:[NSView class]]) { - [val appendFormat:@" view info: id=%@, tag=%ld\n",[(NSView *)notificationObserver identifier], [(NSView *)notificationObserver tag]]; - } - [val appendFormat:@"\nbacktrace:\n%@\n\n",[NSThread callStackSymbols]]; - - [gScrollViewListeners setObject:val forKey:key]; - } - // not recursive! method is swizzled. - [self sp_addObserver:notificationObserver selector:notificationSelector name:notificationName object:notificationSender]; -} +- (IBAction)buttonPressed:(id)sender; @end -#import "SPTableView.h" - -@implementation SPTableView (SPScrollViewDebug) +@implementation NSAlert (SPAlertDebug) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - gScrollViewDealloc = [[NSMutableDictionary alloc] init]; + Class alertClass = [self class]; - Class tableView = [self class]; + SEL orig = @selector(buttonPressed:); + SEL exch = @selector(sp_buttonPressed:); - SEL orig = @selector(dealloc); - SEL exch = @selector(sp_dealloc); - - Method origM = class_getInstanceMethod(tableView, orig); - Method exchM = class_getInstanceMethod(tableView, exch); + Method origM = class_getInstanceMethod(alertClass, orig); + Method exchM = class_getInstanceMethod(alertClass, exch); method_exchangeImplementations(origM, exchM); }); } -- (void)sp_dealloc +- (IBAction)sp_buttonPressed:(id)obj { - NSString *key = [NSString stringWithFormat:@"=%p",self]; - NSString *val = [NSString stringWithFormat:@"\ndealloc backtrace:\n%@\n\n",[NSThread callStackSymbols]]; - - [gScrollViewDealloc setObject:val forKey:key]; + NSLog(@"%s of %@ title=\n%@\ntext=\n%@",__func__,self,[self messageText],[self informativeText]); - // not recursive! method is swizzled. - [self sp_dealloc]; + [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..5857b1b7 100644 --- a/Source/SPQueryFavoriteManager.m +++ b/Source/SPQueryFavoriteManager.m @@ -917,8 +917,8 @@ NSMutableArray *favoriteData = [NSMutableArray array]; - [spfdata setObject:@1 forKey:@"version"]; - [spfdata setObject:@"query favorites" forKey:@"format"]; + [spfdata setObject:@1 forKey:SPFVersionKey]; + [spfdata setObject: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 de6d8377..a5b1b468 100644 --- a/Source/SPStringAdditions.h +++ b/Source/SPStringAdditions.h @@ -78,6 +78,13 @@ static inline id NSMutableAttributedStringAttributeAtIndex(NSMutableAttributedSt - (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet *)charSet; - (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet *)charSet options:(NSUInteger)mask; +/** + * Replace all occurances of any character in set with the replacement string + * @param set Characters to look for (MUST NOT be nil) + * @param string A replacement string (can be nil == empty string) + * @return A string with replacements applied + */ +- (NSString *)stringByReplacingCharactersInSet:(NSCharacterSet *)set withString:(NSString *)string; - (CGFloat)levenshteinDistanceWithWord:(NSString *)stringB; diff --git a/Source/SPStringAdditions.m b/Source/SPStringAdditions.m index f8aeb296..25a21541 100644 --- a/Source/SPStringAdditions.m +++ b/Source/SPStringAdditions.m @@ -344,6 +344,44 @@ static NSInteger _smallestOf(NSInteger a, NSInteger b, NSInteger c); return lineRangesArray; } +- (NSString *)stringByReplacingCharactersInSet:(NSCharacterSet *)set withString:(NSString *)string +{ + NSUInteger len = [self length]; + NSMutableString *newString = [NSMutableString string]; + + NSRange range = NSMakeRange (0, len); + + while (true) { + NSRange substringRange; + NSUInteger pos = range.location; + BOOL endAfterInsert = NO; + + range = [self rangeOfCharacterFromSet:set options:0 range:range]; + + if(range.location == NSNotFound) { + // insert the current substring up to the end + substringRange = NSMakeRange(pos, len - pos); + endAfterInsert = YES; + } + else { + // insert the current substring up to range.location + substringRange = NSMakeRange(pos, range.location - pos); + } + + [newString appendString:[self substringWithRange:substringRange]]; + + if(endAfterInsert) break; + + // insert the replacement character + [newString appendStringOrNil:string]; + + // continue after the replaced characters + range.location += range.length; + range.length = len - range.location; + } + return newString; +} + /** * Returns the string by removing the characters in the supplied set and options. */ diff --git a/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 2b185bca..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 @@ -435,7 +434,8 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper [tableContentView scrollColumnToVisible:0]; // Set the maximum table rows to an estimated count pre-load - maxNumRows = [[tableDataInstance statusValueForKey:@"Rows"] integerValue]; + NSString *rows = [tableDataInstance statusValueForKey:@"Rows"]; + maxNumRows = (rows && ![rows isNSNull])? [rows integerValue] : 0; maxNumRowsIsEstimate = YES; } @@ -566,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"]]; @@ -987,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 @@ -1125,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; } @@ -1576,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]; @@ -1810,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; @@ -1820,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:@""] @@ -1870,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; } @@ -2794,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]; } } } @@ -2852,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(); } @@ -2934,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; } @@ -3074,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 @""; @@ -3146,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; } } @@ -3316,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 @@ -3417,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]; } } @@ -3441,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]; @@ -3454,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(); } @@ -3466,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]; @@ -3975,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 @@ -4086,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 25ea9822..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]; } } } @@ -255,8 +255,11 @@ SPMySQLResult *tempResult = [mySQLConnection queryString:query]; if (![tempResult numberOfRows]) { - SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, - NSLocalizedString(@"Couldn't load the row. Reload the table to be sure that the row exists and use a primary key for your table.", @"message of panel when loading of row failed")); + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [tableDocumentInstance parentWindow], + NSLocalizedString(@"Couldn't load the row. Reload the table to be sure that the row exists and use a primary key for your table.", @"message of panel when loading of row failed") + ); return NO; } diff --git a/Source/SPTableData.m b/Source/SPTableData.m index d3af3580..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,10 +482,8 @@ SPOnewayAlertSheet( NSLocalizedString(@"Error retrieving table information", @"error retrieving table information message"), - nil, [NSApp mainWindow], - errorMessage, - NSWarningAlertStyle + errorMessage ); if (changeEncoding) [mySQLConnection restoreStoredEncoding]; @@ -516,10 +506,8 @@ if ([[syntaxResult objectAtIndex:1] isNSNull]) { SPOnewayAlertSheet( NSLocalizedString(@"Permission Denied", @"Permission Denied"), - nil, [NSApp mainWindow], - NSLocalizedString(@"The creation syntax could not be retrieved due to a permissions error.\n\nPlease check your user permissions with an administrator.", @"Create syntax permission denied detail"), - NSWarningAlertStyle + NSLocalizedString(@"The creation syntax could not be retrieved due to a permissions error.\n\nPlease check your user permissions with an administrator.", @"Create syntax permission denied detail") ); if (changeEncoding) [mySQLConnection restoreStoredEncoding]; @@ -527,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]]; @@ -559,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 /* @@ -571,7 +578,7 @@ ignoringQuotedStrings: NO]; if(fieldName == nil || [fieldName length] == 0) { NSBeep(); - SPOnewayAlertSheet( + SPOnewayAlertSheetWithStyle( NSLocalizedString(@"Error while parsing CREATE TABLE syntax",@"error while parsing CREATE TABLE syntax"), nil, nil, @@ -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,10 +853,8 @@ if ([mySQLConnection isConnected]) { SPOnewayAlertSheet( NSLocalizedString(@"Error", @"error"), - nil, [NSApp mainWindow], - [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving information.\nMySQL said: %@", @"message of panel when retrieving information failed"),[mySQLConnection lastErrorMessage]], - NSWarningAlertStyle + [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving information.\nMySQL said: %@", @"message of panel when retrieving information failed"),[mySQLConnection lastErrorMessage]] ); if (changeEncoding) [mySQLConnection restoreStoredEncoding]; } @@ -860,15 +864,19 @@ // 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"), - NSWarningAlertStyle + NSLocalizedString(@"The creation syntax could not be retrieved due to a permissions error.\n\nPlease check your user permissions with an administrator.", @"Create syntax permission denied detail") ); if (changeEncoding) [mySQLConnection restoreStoredEncoding]; return nil; @@ -885,10 +893,8 @@ if ([mySQLConnection isConnected]) { SPOnewayAlertSheet( NSLocalizedString(@"Error", @"error"), - nil, [NSApp mainWindow], - [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving information.\nMySQL said: %@", @"message of panel when retrieving information failed"), [mySQLConnection lastErrorMessage]], - NSWarningAlertStyle + [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving information.\nMySQL said: %@", @"message of panel when retrieving information failed"), [mySQLConnection lastErrorMessage]] ); if (changeEncoding) [mySQLConnection restoreStoredEncoding]; } @@ -997,10 +1003,8 @@ if ([mySQLConnection isConnected]) { SPOnewayAlertSheet( NSLocalizedString(@"Error", @"error"), - nil, [NSApp mainWindow], - [NSString stringWithFormat:NSLocalizedString(@"An error occured while retrieving status data.\nMySQL said: %@", @"message of panel when retrieving view information failed"), [mySQLConnection lastErrorMessage]], - NSWarningAlertStyle + [NSString stringWithFormat:NSLocalizedString(@"An error occured while retrieving status data.\n\nMySQL said: %@", @"message of panel when retrieving view information failed"), [mySQLConnection lastErrorMessage]] ); if (changeEncoding) [mySQLConnection restoreStoredEncoding]; } @@ -1037,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]] + ); + } } } @@ -1085,10 +1099,8 @@ if ([mySQLConnection isConnected]) { SPOnewayAlertSheet( NSLocalizedString(@"Error retrieving trigger information", @"error retrieving trigger information message"), - nil, [NSApp mainWindow], - [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving the trigger information for table '%@'. Please try again.\n\nMySQL said: %@", @"error retrieving table information informative message"), [tableListInstance tableName], [mySQLConnection lastErrorMessage]], - NSWarningAlertStyle + [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving the trigger information for table '%@'. Please try again.\n\nMySQL said: %@", @"error retrieving table information informative message"), [tableListInstance tableName], [mySQLConnection lastErrorMessage]] ); if (triggers) SPClear(triggers); if (changeEncoding) [mySQLConnection restoreStoredEncoding]; @@ -1255,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"] @@ -1347,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..2347f12b 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. Invalid values are converted to 0000-00-00.",@"description of date") + ), + MakeFieldTypeHelp( + SPMySQLDatetimeType, + @"DATETIME[(F)]", + NSLocalizedString(@"Range: 1000-01-01 00:00:00.0 to 9999-12-31 23:59:59.999999\nF (precision): 0 (1s) to 6 (1µs)", @"range for datetime type"), + NSLocalizedString(@"Stores a date and time of day. The representation is YYYY-MM-DD HH:MM:SS[.I*], I being fractional seconds. Invalid values are converted to 0000-00-00 00:00:00.0. Fractional seconds were added in MySQL 5.6.4 with a precision down to microseconds (6), specified by F.",@"description of datetime") + ), + MakeFieldTypeHelp( + SPMySQLTimestampType, + @"TIMETSTAMP[(F)]", + NSLocalizedString(@"Range: 1970-01-01 00:00:01.0 to 2038-01-19 03:14:07.999999\nF (precision): 0 (1s) to 6 (1µs)", @"range for timestamp type"), + NSLocalizedString(@"Stores a date and time of day as seconds since the beginning of the UNIX epoch (1970-01-01 00:00:00). The representation is the same as for DATETIME. Invalid values, as well as \"second zero\", are converted to 0000-00-00 00:00:00.0. Fractional seconds were added in MySQL 5.6.4 with a precision down to microseconds (6), specified by F. Some additional rules may apply.",@"description of timestamp") + ), + MakeFieldTypeHelp( + SPMySQLTimeType, + @"TIME[(F)]", + NSLocalizedString(@"Range: -838:59:59.0 to 838:59:59.0\nF (precision): 0 (1s) to 6 (1µs)", @"range for time type"), + NSLocalizedString(@"Stores a time of day, duration or time interval. The representation is HH:MM:SS[.I*], I being fractional seconds.Invalid values are converted to 00:00:00. Fractional seconds were added in MySQL 5.6.4 with a precision down to microseconds (6), specified by F.",@"description of time") + ), + MakeFieldTypeHelp( + SPMySQLYearType, + @"YEAR(4)", + NSLocalizedString(@"Range: 0000, 1901 to 2155", @"range for year type"), + NSLocalizedString(@"Represents a 4 digit year value, stored as 1 byte. Invalid values are converted to 0000 and two digit values 0 to 69 will be converted to years 2000 to 2069, resp. 70 to 99 to years 1970 to 1999.\nThe YEAR(2) type was removed in MySQL 5.7.5.",@"description of year") + ), + // -------------------------------------------------------------------------- + MakeFieldTypeHelp( + SPMySQLGeometryType, + @"GEOMETRY", + @"", + NSLocalizedString(@"Can store a single spatial value of types POINT, LINESTRING or POLYGON. Spatial support in MySQL is based on the OpenGIS Geometry Model.",@"description of geometry") + ), + MakeFieldTypeHelp( + SPMySQLPointType, + @"POINT", + @"", + NSLocalizedString(@"Represents a single location in coordinate space using X and Y coordinates. The point is zero-dimensional.",@"description of point") + ), + MakeFieldTypeHelp( + SPMySQLLineStringType, + @"LINESTRING", + @"", + NSLocalizedString(@"Represents an ordered set of coordinates where each consecutive pair of two points is connected by a straight line.",@"description of linestring") + ), + MakeFieldTypeHelp( + SPMySQLPolygonType, + @"POLYGON", + @"", + NSLocalizedString(@"Creates a surface by combining one LinearRing (ie. a LineString that is closed and simple) as the outside boundary with zero or more inner LinearRings acting as \"holes\".",@"description of polygon") + ), + MakeFieldTypeHelp( + SPMySQLMultiPointType, + @"MULTIPOINT", + @"", + NSLocalizedString(@"Represents a set of Points without specifying any kind of relation and/or order between them.",@"description of multipoint") + ), + MakeFieldTypeHelp( + SPMySQLMultiLineStringType, + @"MULTILINESTRING", + @"", + NSLocalizedString(@"Represents a collection of LineStrings.",@"description of multilinestring") + ), + MakeFieldTypeHelp( + SPMySQLMultiPolygonType, + @"MULTIPOLYGON", + @"", + NSLocalizedString(@"Represents a collection of Polygons. The Polygons making up the MultiPolygon must not intersect.",@"description of multipolygon") + ), + MakeFieldTypeHelp( + SPMySQLGeometryCollectionType, + @"GEOMETRYCOLLECTION", + @"", + NSLocalizedString(@"Represents a collection of objects of any other single- or multi-valued spatial type. The only restriction being, that all objects must share a common coordinate system.",@"description of geometrycollection") + ), + ] retain]; +#undef FN +#undef INTR + }); + + for (SPFieldTypeHelp *item in list) { + if ([[item typeName] isEqualToString:typeName]) { + return item; + } + } + + return nil; +} + @end diff --git a/Source/SPTableStructureDelegate.m b/Source/SPTableStructureDelegate.m index 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 7a1c01c1..84e43a26 100644 --- a/Source/SPTableView.m +++ b/Source/SPTableView.m @@ -72,7 +72,7 @@ if ([NSTableView instancesRespondToSelector:@selector(awakeFromNib)]) { [super awakeFromNib]; -} + } } #pragma mark - @@ -93,6 +93,8 @@ [notifier addObserver:self selector:@selector(_disableDoubleClickAction:) name:NSWindowWillBeginSheetNotification object:aWindow]; [notifier addObserver:self selector:@selector(_enableDoubleClickAction:) name:NSWindowDidEndSheetNotification object:aWindow]; } + + [super viewWillMoveToWindow:aWindow]; } /** 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.m b/Source/SPTextView.m index e37b5939..970869c1 100644 --- a/Source/SPTextView.m +++ b/Source/SPTextView.m @@ -1844,8 +1844,11 @@ static inline NSPoint SPPointOnLine(NSPoint a, NSPoint b, CGFloat t) { return NS [theHintString replaceCharactersInRange:tagRange withString:cmdResult]; } else if([err code] != 9) { // Suppress an error message if command was killed NSString *errorMessage = [err localizedDescription]; - SPBeginAlertSheet(NSLocalizedString(@"BASH Error", @"bash error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil, - [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), [theHintString substringWithRange:cmdRange], errorMessage]); + SPOnewayAlertSheet( + NSLocalizedString(@"BASH Error", @"bash error"), + [self window], + [NSString stringWithFormat:NSLocalizedString(@"Error for “%1$@”:\n%2$@", @"error for bash command ($1), $2=message"), [theHintString substringWithRange:cmdRange], errorMessage] + ); } } else { [theHintString replaceCharactersInRange:tagRange withString:@""]; diff --git a/Source/SPTextViewAdditions.m b/Source/SPTextViewAdditions.m index 56103a53..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..3bee8636 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 { @@ -92,6 +94,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 +119,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..6f42ac75 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,7 +157,7 @@ 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]; [usersResultArray addObjectsFromArray:[result getAllRows]]; @@ -168,7 +171,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 +191,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 +222,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"]]) { @@ -244,8 +247,8 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; NSString *username = [[items objectAtIndex:i] objectForKey:@"User"]; NSArray *parentResults = [[self _fetchUserWithUserName:username] retain]; NSDictionary *item = [items objectAtIndex:i]; - NSManagedObject *parent; - NSManagedObject *child; + SPUserMO *parent; + SPUserMO *child; // Check to make sure if we already have added the parent if (parentResults != nil && [parentResults count] > 0) { @@ -373,7 +376,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 +391,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 +424,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 +440,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 +466,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 +545,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 +620,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 +686,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 +695,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,7 +803,7 @@ 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"]; @@ -919,34 +923,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,174 +942,147 @@ 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 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) - { - [self _renameUserFrom:[user valueForKey:@"originaluser"] - host:[child valueForKey:@"originalhost"] ? [child valueForKey:@"originalhost"] : [child host] - to:[user valueForKey:@"user"] - host:[child host]]; - } - } - - // If the password has been changed, use the same password on all hosts - if (![[user valueForKey:@"password"] isEqualToString:[user valueForKey:@"originalpassword"]]) { - - for (NSManagedObject *child in hosts) - { - NSString *changePasswordStatement = [NSString stringWithFormat: - @"SET PASSWORD FOR %@@%@ = PASSWORD(%@)", - [[user valueForKey:@"user"] tickQuotedString], - [[child host] tickQuotedString], - ([user valueForKey:@"password"]) ? [[user valueForKey:@"password"] tickQuotedString] : @"''"]; - - [[self connection] queryString:changePasswordStatement]; - [self _checkAndDisplayMySqlError]; - } + 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]]; } - } - else { - // If the hostname has changed, remane the detail before editing details - if (![[user valueForKey:@"host"] isEqualToString:[user valueForKey:@"originalhost"]]) { + } + + // If the password has been changed, use the same password on all hosts + if (![[user valueForKey:@"password"] isEqualToString:[user valueForKey:@"originalpassword"]]) { + + for (SPUserMO *child in hosts) + { + NSString *changePasswordStatement = [NSString stringWithFormat: + @"SET PASSWORD FOR %@@%@ = PASSWORD(%@)", + [[user valueForKey:@"user"] tickQuotedString], + [[child host] tickQuotedString], + ([user valueForKey:@"password"]) ? [[user valueForKey:@"password"] tickQuotedString] : @"''"]; - [self _renameUserFrom:[[user parent] valueForKey:@"originaluser"] - host:[user valueForKey:@"originalhost"] - to:[[user parent] valueForKey:@"user"] - host:[user valueForKey:@"host"]]; + [connection queryString:changePasswordStatement]; + if(![self _checkAndDisplayMySqlError]) return NO; } - - 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]; - - // 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]]; - } +- (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"]) { - // DROP USER was added in MySQL 4.1.1 - if ([serverSupport supportsDropUser]) { - [[self connection] queryString:[NSString stringWithFormat:@"DROP USER %@", droppedUsers]]; - } - // 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]]]; - } - } + NSString *username = [[[user parent] valueForKey:@"user"] tickQuotedString]; + NSString *password = [[[user parent] valueForKey:@"password"] tickQuotedString]; - [droppedUsers release]; + 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]; } - - 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 +1095,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 +1118,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 +1135,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 +1146,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 +1156,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 +1183,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 +1266,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 +1274,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 +1295,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 +1316,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 +1332,8 @@ static NSString * const SPTableViewNameColumnID = @"NameColumn"; [aHost tickQuotedString]]; } - [[self connection] queryString:revokeStatement]; - [self _checkAndDisplayMySqlError]; + [connection queryString:revokeStatement]; + return [self _checkAndDisplayMySqlError]; } /** @@ -1381,14 +1341,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 +1370,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 +1412,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 index 812eb45d..988267d2 100644 --- a/UnitTests/SPDataAdditionsTests.m +++ b/UnitTests/SPDataAdditionsTests.m @@ -40,6 +40,7 @@ - (void)testDataEncryptedWithKeyIV; - (void)testDataDecryptedWithPassword; - (void)testDataDecryptedWithKey; +- (void)testEnumerateLinesBreakingAt_withBlock; @end @@ -281,4 +282,106 @@ } } +- (void)testEnumerateLinesBreakingAt_withBlock +{ + //simple empty data + { + __block NSUInteger invocations = 0; + NSData *data = [NSData data]; + [data enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { + invocations++; + }]; + STAssertTrue(invocations==0, @"Empty data never invokes block"); + } + //simple unix file + { + const char inp[] = "Two\nLines\n"; + __block NSUInteger invocations = 0; + NSData *data = [NSData dataWithBytes:inp length:strlen(inp)]; + [data enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { + switch (invocations) { + case 0: + STAssertEquals(line, NSMakeRange(0, 3), @"range of first line"); + break; + case 1: + STAssertEquals(line, NSMakeRange(4, 5), @"range of second line"); + break; + } + invocations++; + }]; + STAssertTrue(invocations==2, @"File with two lines, terminated with empty line"); + } + //simple windows file without ending empty line + { + const char inp[] = "A\r\nWindows\r\nfile"; + __block NSUInteger invocations = 0; + NSData *data = [NSData dataWithBytes:inp length:strlen(inp)]; + [data enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { + switch (invocations) { + case 0: + STAssertEquals(line, NSMakeRange(0, 1), @"range of first line"); + break; + case 1: + STAssertEquals(line, NSMakeRange(3, 7), @"range of second line"); + break; + case 2: + STAssertEquals(line, NSMakeRange(12, 4), @"range of third line"); + break; + } + invocations++; + }]; + STAssertTrue(invocations==3, @"File with three lines, CRLF, terminated with empty line"); + } + //empty lines with all 3 endings + { + const char inp[] = "\n\r\n\r"; + __block NSUInteger invocations = 0; + NSData *data = [NSData dataWithBytes:inp length:strlen(inp)]; + [data enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { + switch (invocations) { + case 0: + STAssertEquals(line, NSMakeRange(0, 0), @"range of first line"); + break; + case 1: + STAssertEquals(line, NSMakeRange(1, 0), @"range of second line"); + break; + case 2: + STAssertEquals(line, NSMakeRange(3, 0), @"range of third line"); + break; + } + invocations++; + }]; + STAssertTrue(invocations==3, @"LF, CRLF and CR mixed"); + } + //looking for specific line breaks only + { + const char inp[] = "foo\nbar\r\nbaz\r"; + __block NSUInteger invocations = 0; + NSData *data = [NSData dataWithBytes:inp length:strlen(inp)]; + [data enumerateLinesBreakingAt:SPLineTerminatorCRLF withBlock:^(NSRange line, BOOL *stop) { + switch (invocations) { + case 0: + STAssertEquals(line, NSMakeRange(0, 7), @"range of first line"); + break; + case 1: + STAssertEquals(line, NSMakeRange(9, 4), @"range of second line"); + break; + } + invocations++; + }]; + STAssertTrue(invocations==2, @"other line breaks when only CRLF is expected"); + } + //stopping early + { + const char inp[] = "Two\nLines\n"; + __block NSUInteger invocations = 0; + NSData *data = [NSData dataWithBytes:inp length:strlen(inp)]; + [data enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { + invocations++; + *stop = YES; + }]; + STAssertTrue(invocations==1, @"File with two lines, stopped after first"); + } +} + @end diff --git a/UnitTests/SPStringAdditionsTests.m b/UnitTests/SPStringAdditionsTests.m index 23ec82e9..00cbdc14 100644 --- a/UnitTests/SPStringAdditionsTests.m +++ b/UnitTests/SPStringAdditionsTests.m @@ -39,6 +39,7 @@ - (void)testStringWithNewUUID; - (void)testCreateViewSyntaxPrettifier; - (void)testNonConsecutivelySearchStringMatchingRanges; +- (void)testStringByReplacingCharactersInSetWithString; @end @@ -195,6 +196,26 @@ static NSRange RangeFromArray(NSArray *a,NSUInteger idx); } +- (void)testStringByReplacingCharactersInSetWithString +{ + { + //test against empty string + STAssertEqualObjects([@"" stringByReplacingCharactersInSet:[NSCharacterSet whitespaceCharacterSet] withString:@"x"], @"", @"replacement on empty string must result in empty string"); + } + { + //test match at begin, middle, end / consecutive matches + STAssertEqualObjects([@" ab c " stringByReplacingCharactersInSet:[NSCharacterSet whitespaceCharacterSet] withString:@"_"], @"_ab__c_", @"Testing matches at both end, replacement of consecutive matches"); + } + { + //test replacement of different characters + STAssertEqualObjects([@"ab\r\ncd" stringByReplacingCharactersInSet:[NSCharacterSet newlineCharacterSet] withString:@"*"], @"ab**cd", @"Testing replacement of different characters in set"); + } + { + // nil for replacement char + STAssertEqualObjects([@"ab\r\ncd" stringByReplacingCharactersInSet:[NSCharacterSet newlineCharacterSet] withString:nil], @"abcd", @"testing replacement with nil"); + } +} + @end NSRange RangeFromArray(NSArray *a,NSUInteger idx) diff --git a/UnitTests/SPTableCopyTest.m b/UnitTests/SPTableCopyTest.m index 76a2fe45..05ab4a76 100644 --- a/UnitTests/SPTableCopyTest.m +++ b/UnitTests/SPTableCopyTest.m @@ -39,52 +39,62 @@ @interface SPTableCopyTest : SenTestCase - (void)testCopyTableFromToWithData; +- (void)testCopyTableFromTo_NoPermissions; @end @implementation SPTableCopyTest -- (id)mockConnection -{ - return [[OCMockObject niceMockForClass:[SPMySQLConnection class]] autorelease]; -} - -- (id)mockResult -{ - return [[OCMockObject niceMockForClass:[SPMySQLResult class]] autorelease]; -} - - (void)testCopyTableFromToWithData { - id mockResult = [self mockResult]; + id mockResult = OCMClassMock([SPMySQLResult class]); - unsigned long long varOne = 1; - NSValue *valueOne = [NSValue value:&varOne withObjCType:@encode(__typeof__(varOne))]; - BOOL varNo = NO; - - NSValue *valueNo = [NSValue value:&varNo withObjCType:@encode(BOOL)]; NSArray *resultArray = [[NSArray alloc] initWithObjects:@"", @"CREATE TABLE `table_name` ()", nil]; - id mockConnection = [self mockConnection]; + id mockConnection = OCMClassMock([SPMySQLConnection class]); - [(SPMySQLResult *)[[mockResult expect] andReturn:valueOne] numberOfRows]; - [[[mockResult expect] andReturn:resultArray] getRowAsArray]; + OCMExpect([mockResult numberOfRows]).andReturn(1); + OCMExpect([mockResult getRowAsArray]).andReturn(resultArray); - [[[mockConnection expect] andReturn:mockResult] queryString:@"SHOW CREATE TABLE `source_db`.`table_name`"]; - [[mockConnection expect] queryString:@"CREATE TABLE `target_db`.`table_name` ()"]; - [[mockConnection expect] queryString:@"INSERT INTO `target_db`.`table_name` SELECT * FROM `source_db`.`table_name`"]; - [[[mockConnection stub] andReturnValue:valueNo] queryErrored]; + OCMExpect([mockConnection queryString:@"SHOW CREATE TABLE `source_db`.`table_name`"]).andReturn(mockResult); + OCMExpect([mockConnection queryString:@"CREATE TABLE `target_db`.`table_name` ()"]); + OCMExpect([mockConnection queryString:@"INSERT INTO `target_db`.`table_name` SELECT * FROM `source_db`.`table_name`"]); + OCMStub([mockConnection queryErrored]).andReturn(NO); - SPTableCopy *tableCopy = [[SPTableCopy alloc] init]; - - [tableCopy setConnection:mockConnection]; - [tableCopy copyTable:@"table_name" from:@"source_db" to:@"target_db" withContent:YES]; + { + SPTableCopy *tableCopy = [[SPTableCopy alloc] init]; + + [tableCopy setConnection:mockConnection]; + [tableCopy copyTable:@"table_name" from:@"source_db" to:@"target_db" withContent:YES]; + + [tableCopy release]; + } - [mockResult verify]; - [mockConnection verify]; + OCMVerifyAll(mockResult); + OCMVerifyAll(mockConnection); - [tableCopy release]; [resultArray release]; } +- (void)testCopyTableFromTo_NoPermissions +{ + id mockConnection = OCMStrictClassMock([SPMySQLConnection class]); + + OCMExpect([mockConnection queryString:@"SHOW CREATE TABLE `source_db`.`table_name`"]).andReturn(nil); + OCMStub([mockConnection queryErrored]).andReturn(YES); + OCMStub([mockConnection lastErrorMessage]).andReturn(@"SHOW command denied to user 'alice'@'localhost' for table 'table_name'"); + OCMStub([mockConnection lastErrorID]).andReturn(1142); + OCMStub([mockConnection lastSqlstate]).andReturn(@"42000"); + + { + SPTableCopy *tableCopy = [[SPTableCopy alloc] init]; + + STAssertFalse([tableCopy copyTable:@"table_name" from:@"source_db" to:@"target_db"],@"copy operation must fail."); + + [tableCopy release]; + } + + [mockConnection verify]; +} + @end @@ -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. diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index 0958501d..b1521906 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,6 +179,18 @@ 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 */; }; @@ -188,7 +201,26 @@ 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 */; }; @@ -851,6 +883,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>"; }; @@ -891,6 +925,12 @@ 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>"; }; @@ -901,6 +941,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>"; }; @@ -1286,6 +1335,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 50082B471BF7D1F600746ECC /* libicucore.dylib in Frameworks */, 502D22161BA62FF5000D4CE7 /* Security.framework in Frameworks */, BC0E14A1120AAC2E00E52E25 /* libbz2.dylib in Frameworks */, 584D87C415141A5D00F24774 /* libz.dylib in Frameworks */, @@ -1429,6 +1479,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>"; @@ -1557,6 +1611,8 @@ 4D90B79C101E0CF200D116A1 /* SPUserMO.h */, 4D90B79D101E0CF200D116A1 /* SPUserMO.m */, 4D90B79B101E0CF200D116A1 /* SPUserManager.xcdatamodel */, + 500C1F901BFB5F9F0095DC7F /* SPPrivilegesMO.h */, + 500C1F911BFB5F9F0095DC7F /* SPPrivilegesMO.m */, ); name = "Core Data"; sourceTree = "<group>"; @@ -2069,6 +2125,7 @@ 17E6416E0EF01F3B001BC333 /* Other */ = { isa = PBXGroup; children = ( + 507FF10E1BBCC4A900104523 /* Utility */, 1198F5B01174EDA700670590 /* Database Actions */, 583CE39511722B70008F148E /* File Compression */, 173284E51088FEC20062E892 /* Data */, @@ -2262,6 +2319,8 @@ 173C837C11AAD2C500B8B084 /* Delegate Protocols */, 173C837D11AAD2D300B8B084 /* Delegate Categories */, 50D3C3831A8177D900B5429C /* SPExportController+SharedPrivateAPI.h */, + 507FF2681BC8450100104523 /* SPExportSettingsPersistence.h */, + 507FF2691BC8450100104523 /* SPExportSettingsPersistence.m */, ); name = "Data Export"; sourceTree = "<group>"; @@ -2295,6 +2354,8 @@ 17FDB04B1280778B00DBBBC2 /* SPFontPreviewTextField.m */, 58D2A6A516FBDEFF002EB401 /* SPComboPopupButton.h */, 58D2A6A616FBDEFF002EB401 /* SPComboPopupButton.m */, + 50805B0B1BF2A068005F7A99 /* SPPopUpButtonCell.h */, + 50805B0C1BF2A068005F7A99 /* SPPopUpButtonCell.m */, ); name = Controls; sourceTree = "<group>"; @@ -2369,6 +2430,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 = ( @@ -2406,6 +2478,7 @@ 584756FE120A1B290057631F /* SPQLPluginQueryFavoritesTemplate.html */, 58475700120A1B290057631F /* SPQLPluginSQLTemplate.html */, BCEC861F12115A30002561DA /* SPQLPluginConnectionBundleWindowTemplate.html */, + 508022931BF7BA470052A9B2 /* SPQLPluginExportSettingsTemplate.html */, ); name = "QuickLook Plugin"; sourceTree = "<group>"; @@ -2607,6 +2680,8 @@ 1798F19715501838004B0AB8 /* SPMutableArrayAdditions.m */, 584D899B15162CBE00F24774 /* SPDataBase64EncodingAdditions.h */, 584D899C15162CBE00F24774 /* SPDataBase64EncodingAdditions.m */, + 500DA4BA1BF0CD57000773FE /* SPScreenAdditions.h */, + 500DA4BB1BF0CD57000773FE /* SPScreenAdditions.m */, ); name = "Category Additions"; sourceTree = "<group>"; @@ -2638,6 +2713,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 */, @@ -2834,6 +2918,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 */, @@ -3056,12 +3141,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 */, @@ -3081,6 +3169,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 */, @@ -3125,6 +3222,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 */, @@ -3141,6 +3239,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 */, @@ -3199,18 +3298,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 */, @@ -3233,6 +3335,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 */, @@ -3261,6 +3364,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 */, @@ -3280,6 +3384,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 */, @@ -3316,6 +3421,7 @@ 501B1D181728A3DA0017C92E /* SPCharsetCollationHelper.m in Sources */, 50E217B318174246009D3580 /* SPColorSelectorView.m in Sources */, 50E217B618174280009D3580 /* SPFavoriteColorSupport.m in Sources */, + 1A564F74237E2E4958CA593A /* SPPillAttachmentCell.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3457,6 +3563,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 = ( |