Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/ccgus/fmdb.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAugust "Gus" Mueller <gus@flyingmeat.com>2017-10-24 19:30:13 +0300
committerGitHub <noreply@github.com>2017-10-24 19:30:13 +0300
commite0fcde9a0e5868e7400032448133f929c64500d1 (patch)
tree85ae728bb8a53a486c650beb336896b7e9a43137
parent7ebbfd055c48d75dfb0119abbd9b5fc2f9001b9f (diff)
parent1da3a20b00ed519f80fe42453433d4975a2b3c4d (diff)
Merge pull request #620 from robertmryan/master2.7.4
Update transactions
-rw-r--r--CHANGES_AND_TODO_LIST.txt5
-rw-r--r--FMDB.podspec2
-rw-r--r--Tests/FMDatabaseQueueTests.m91
-rw-r--r--Tests/FMDatabaseTests.m2
-rw-r--r--Tests/FMResultSetTests.m3
-rw-r--r--fmdb.xcodeproj/project.pbxproj14
-rw-r--r--fmdb.xcodeproj/xcshareddata/xcschemes/FMDB MacOS.xcscheme4
-rw-r--r--fmdb.xcodeproj/xcshareddata/xcschemes/FMDB iOS.xcscheme4
-rw-r--r--src/fmdb/FMDatabase.h25
-rw-r--r--src/fmdb/FMDatabase.m18
-rwxr-xr-xsrc/fmdb/FMDatabasePool.h17
-rwxr-xr-xsrc/fmdb/FMDatabasePool.m6
-rwxr-xr-xsrc/fmdb/FMDatabaseQueue.h18
-rwxr-xr-xsrc/fmdb/FMDatabaseQueue.m8
-rw-r--r--src/fmdb/Info.plist2
15 files changed, 200 insertions, 19 deletions
diff --git a/CHANGES_AND_TODO_LIST.txt b/CHANGES_AND_TODO_LIST.txt
index d426421..95cfbec 100644
--- a/CHANGES_AND_TODO_LIST.txt
+++ b/CHANGES_AND_TODO_LIST.txt
@@ -3,6 +3,11 @@ Zip, nada, zilch. Got any ideas?
If you would like to contribute some code ... awesome! I just ask that you make it conform to the coding conventions already set in here, and to add the necessary of tests for your new code to tests target. And of course, the code should be of general use to more than just a couple of folks. Send your patches to gus@flyingmeat.com.
+2017.10.23 Version 2.7.4
+ Added support for explicit transactions.
+
+ Add warning that `beginTransaction` and `inTransaction` behavior is likely to change.
+
2017.10.20 Version 2.7.3
Added support for immediate transactions and checkpoint. (thanks to @benasher44)
diff --git a/FMDB.podspec b/FMDB.podspec
index e16d6b7..9f8bf28 100644
--- a/FMDB.podspec
+++ b/FMDB.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'FMDB'
- s.version = '2.7.3'
+ s.version = '2.7.4'
s.summary = 'A Cocoa / Objective-C wrapper around SQLite.'
s.homepage = 'https://github.com/ccgus/fmdb'
s.license = 'MIT'
diff --git a/Tests/FMDatabaseQueueTests.m b/Tests/FMDatabaseQueueTests.m
index bf46a74..54cd995 100644
--- a/Tests/FMDatabaseQueueTests.m
+++ b/Tests/FMDatabaseQueueTests.m
@@ -59,6 +59,54 @@
queue = nil;
}
+- (void)testInvalidURL {
+ NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
+ NSURL *folderURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
+ NSURL *fileURL = [folderURL URLByAppendingPathComponent:@"test.sqlite"];
+
+ FMDatabaseQueue *queue = [[FMDatabaseQueue alloc] initWithURL:fileURL];
+ XCTAssertNil(queue, @"Database queue should not be returned for invalid path");
+ queue = nil;
+}
+
+- (void)testInvalidPath {
+ NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
+ NSURL *folderURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
+ NSURL *fileURL = [folderURL URLByAppendingPathComponent:@"test.sqlite"];
+
+ FMDatabaseQueue *queue = [[FMDatabaseQueue alloc] initWithPath:fileURL.path];
+ XCTAssertNil(queue, @"Database queue should not be returned for invalid path");
+ queue = nil;
+}
+
+- (void)testReopenFailure {
+ NSFileManager *manager = [NSFileManager defaultManager];
+
+ NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
+ NSURL *folderURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
+ BOOL success = [manager createDirectoryAtURL:folderURL withIntermediateDirectories:true attributes:nil error:nil];
+ NSAssert(success, @"Unable to create folder");
+
+ NSURL *fileURL = [folderURL URLByAppendingPathComponent:@"test.sqlite"];
+
+ FMDatabaseQueue *queue = [[FMDatabaseQueue alloc] initWithURL:fileURL];
+ XCTAssert(queue, @"Database queue was unable to be created");
+
+ [queue close];
+
+ success = [manager removeItemAtURL:fileURL error:nil];
+ XCTAssert(success, @"Unable to remove database");
+
+ success = [manager removeItemAtURL:folderURL error:nil];
+ XCTAssert(success, @"Unable to remove folder");
+
+ [queue inDatabase:^(FMDatabase *db) {
+ XCTAssertNil(db, @"Should be `nil` or never have reached here because database couldn't be reopened");
+ }];
+
+ queue = nil;
+}
+
- (void)testURLOpen {
NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
NSURL *fileURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
@@ -260,7 +308,50 @@
XCTAssertEqual(rowCount, 2);
}];
+
+}
+- (void)testSavePoint
+{
+ [self.queue inDatabase:^(FMDatabase *adb) {
+ [adb executeUpdate:@"create table transtest (a integer)"];
+ XCTAssertTrue([adb executeUpdate:@"insert into transtest values (1)"]);
+ XCTAssertTrue([adb executeUpdate:@"insert into transtest values (2)"]);
+
+ int rowCount = 0;
+ FMResultSet *ars = [adb executeQuery:@"select * from transtest"];
+ while ([ars next]) {
+ rowCount++;
+ }
+
+ XCTAssertEqual(rowCount, 2);
+ }];
+
+ [self.queue inSavePoint:^(FMDatabase *adb, BOOL *rollback) {
+ XCTAssertTrue([adb executeUpdate:@"insert into transtest values (3)"]);
+
+ if (YES) {
+ // uh oh!, something went wrong (not really, this is just a test
+ *rollback = YES;
+ return;
+ }
+
+ XCTFail(@"This shouldn't be reached");
+ }];
+
+ [self.queue inDatabase:^(FMDatabase *adb) {
+
+ int rowCount = 0;
+ FMResultSet *ars = [adb executeQuery:@"select * from transtest"];
+ while ([ars next]) {
+ rowCount++;
+ }
+
+ XCTAssertFalse([adb hasOpenResultSets]);
+
+ XCTAssertEqual(rowCount, 2);
+ }];
+
}
@end
diff --git a/Tests/FMDatabaseTests.m b/Tests/FMDatabaseTests.m
index 3f2dd8b..7792609 100644
--- a/Tests/FMDatabaseTests.m
+++ b/Tests/FMDatabaseTests.m
@@ -1125,7 +1125,7 @@
}
- (void)testVersionNumber {
- XCTAssertTrue([FMDatabase FMDBVersion] == 0x0273); // this is going to break everytime we bump it.
+ XCTAssertTrue([FMDatabase FMDBVersion] == 0x0274); // this is going to break everytime we bump it.
}
- (void)testExecuteStatements {
diff --git a/Tests/FMResultSetTests.m b/Tests/FMResultSetTests.m
index 3796828..fdc7450 100644
--- a/Tests/FMResultSetTests.m
+++ b/Tests/FMResultSetTests.m
@@ -69,12 +69,11 @@
FMDatabase *newDB = [FMDatabase databaseWithPath:self.databasePath];
[newDB open];
- [newDB beginTransaction];
+ [newDB beginExclusiveTransaction];
NSError *error;
XCTAssertFalse([resultSet nextWithError:&error]);
[newDB commit];
-
XCTAssertEqual(error.code, SQLITE_BUSY, @"SQLITE_BUSY should be the last error");
[resultSet close];
}
diff --git a/fmdb.xcodeproj/project.pbxproj b/fmdb.xcodeproj/project.pbxproj
index 8b271b3..dc6c4c1 100644
--- a/fmdb.xcodeproj/project.pbxproj
+++ b/fmdb.xcodeproj/project.pbxproj
@@ -552,7 +552,7 @@
08FB7793FE84155DC02AAC07 /* Project object */ = {
isa = PBXProject;
attributes = {
- LastUpgradeCheck = 0820;
+ LastUpgradeCheck = 0900;
TargetAttributes = {
83C73EFD1C326AB000FFC730 = {
CreatedOnToolsVersion = 7.2;
@@ -767,12 +767,18 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
@@ -799,12 +805,18 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
diff --git a/fmdb.xcodeproj/xcshareddata/xcschemes/FMDB MacOS.xcscheme b/fmdb.xcodeproj/xcshareddata/xcschemes/FMDB MacOS.xcscheme
index cd6a678..f2372c7 100644
--- a/fmdb.xcodeproj/xcshareddata/xcschemes/FMDB MacOS.xcscheme
+++ b/fmdb.xcodeproj/xcshareddata/xcschemes/FMDB MacOS.xcscheme
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
- LastUpgradeVersion = "0820"
+ LastUpgradeVersion = "0900"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -26,6 +26,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
@@ -36,6 +37,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
diff --git a/fmdb.xcodeproj/xcshareddata/xcschemes/FMDB iOS.xcscheme b/fmdb.xcodeproj/xcshareddata/xcschemes/FMDB iOS.xcscheme
index b70a01e..e7815a5 100644
--- a/fmdb.xcodeproj/xcshareddata/xcschemes/FMDB iOS.xcscheme
+++ b/fmdb.xcodeproj/xcshareddata/xcschemes/FMDB iOS.xcscheme
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
- LastUpgradeVersion = "0820"
+ LastUpgradeVersion = "0900"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -26,6 +26,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
@@ -36,6 +37,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
diff --git a/src/fmdb/FMDatabase.h b/src/fmdb/FMDatabase.h
index f5ad036..94d145d 100644
--- a/src/fmdb/FMDatabase.h
+++ b/src/fmdb/FMDatabase.h
@@ -685,6 +685,15 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
@see rollback
@see beginDeferredTransaction
@see isInTransaction
+
+ @warning Unlike SQLite's `BEGIN TRANSACTION`, this method currently performs
+ an exclusive transaction, not a deferred transaction. This behavior
+ is likely to change in future versions of FMDB, whereby this method
+ will likely eventually adopt standard SQLite behavior and perform
+ deferred transactions. If you really need exclusive tranaction, it is
+ recommended that you use `beginExclusiveTransaction`, instead, not
+ only to make your intent explicit, but also to future-proof your code.
+
*/
- (BOOL)beginTransaction;
@@ -702,9 +711,9 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
- (BOOL)beginDeferredTransaction;
/** Begin an immediate transaction
-
+
@return `YES` on success; `NO` on failure. If failed, you can call `<lastError>`, `<lastErrorCode>`, or `<lastErrorMessage>` for diagnostic information regarding the failure.
-
+
@see commit
@see rollback
@see beginTransaction
@@ -713,6 +722,18 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
- (BOOL)beginImmediateTransaction;
+/** Begin an exclusive transaction
+
+ @return `YES` on success; `NO` on failure. If failed, you can call `<lastError>`, `<lastErrorCode>`, or `<lastErrorMessage>` for diagnostic information regarding the failure.
+
+ @see commit
+ @see rollback
+ @see beginTransaction
+ @see isInTransaction
+ */
+
+- (BOOL)beginExclusiveTransaction;
+
/** Commit a transaction
Commit a transaction that was initiated with either `<beginTransaction>` or with `<beginDeferredTransaction>`.
diff --git a/src/fmdb/FMDatabase.m b/src/fmdb/FMDatabase.m
index 1398765..2f8dd2a 100644
--- a/src/fmdb/FMDatabase.m
+++ b/src/fmdb/FMDatabase.m
@@ -98,7 +98,7 @@ NS_ASSUME_NONNULL_END
}
+ (NSString*)FMDBUserVersion {
- return @"2.7.3";
+ return @"2.7.4";
}
// returns 0x0240 for version 2.4. This makes it super easy to do things like:
@@ -1312,6 +1312,16 @@ int FMDBExecuteBulkSQLCallback(void *theBlockAsVoid, int columns, char **values,
return b;
}
+- (BOOL)beginTransaction {
+
+ BOOL b = [self executeUpdate:@"begin exclusive transaction"];
+ if (b) {
+ _isInTransaction = YES;
+ }
+
+ return b;
+}
+
- (BOOL)beginDeferredTransaction {
BOOL b = [self executeUpdate:@"begin deferred transaction"];
@@ -1323,16 +1333,16 @@ int FMDBExecuteBulkSQLCallback(void *theBlockAsVoid, int columns, char **values,
}
- (BOOL)beginImmediateTransaction {
-
+
BOOL b = [self executeUpdate:@"begin immediate transaction"];
if (b) {
_isInTransaction = YES;
}
-
+
return b;
}
-- (BOOL)beginTransaction {
+- (BOOL)beginExclusiveTransaction {
BOOL b = [self executeUpdate:@"begin exclusive transaction"];
if (b) {
diff --git a/src/fmdb/FMDatabasePool.h b/src/fmdb/FMDatabasePool.h
index 831d3f8..4c472be 100755
--- a/src/fmdb/FMDatabasePool.h
+++ b/src/fmdb/FMDatabasePool.h
@@ -199,11 +199,26 @@ NS_ASSUME_NONNULL_BEGIN
- (void)inDatabase:(__attribute__((noescape)) void (^)(FMDatabase *db))block;
/** Synchronously perform database operations in pool using transaction.
+
+ @param block The code to be run on the `FMDatabasePool` pool.
+
+ @warning Unlike SQLite's `BEGIN TRANSACTION`, this method currently performs
+ an exclusive transaction, not a deferred transaction. This behavior
+ is likely to change in future versions of FMDB, whereby this method
+ will likely eventually adopt standard SQLite behavior and perform
+ deferred transactions. If you really need exclusive tranaction, it is
+ recommended that you use `inExclusiveTransaction`, instead, not only
+ to make your intent explicit, but also to future-proof your code.
+ */
+- (void)inTransaction:(__attribute__((noescape)) void (^)(FMDatabase *db, BOOL *rollback))block;
+
+/** Synchronously perform database operations in pool using exclusive transaction.
+
@param block The code to be run on the `FMDatabasePool` pool.
*/
-- (void)inTransaction:(__attribute__((noescape)) void (^)(FMDatabase *db, BOOL *rollback))block;
+- (void)inExclusiveTransaction:(__attribute__((noescape)) void (^)(FMDatabase *db, BOOL *rollback))block;
/** Synchronously perform database operations in pool using deferred transaction.
diff --git a/src/fmdb/FMDatabasePool.m b/src/fmdb/FMDatabasePool.m
index 33f0cf2..1a319af 100755
--- a/src/fmdb/FMDatabasePool.m
+++ b/src/fmdb/FMDatabasePool.m
@@ -281,11 +281,15 @@ typedef NS_ENUM(NSInteger, FMDBTransaction) {
[self pushDatabaseBackInPool:db];
}
+- (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block {
+ [self beginTransaction:FMDBTransactionExclusive withBlock:block];
+}
+
- (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block {
[self beginTransaction:FMDBTransactionDeferred withBlock:block];
}
-- (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block {
+- (void)inExclusiveTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block {
[self beginTransaction:FMDBTransactionExclusive withBlock:block];
}
diff --git a/src/fmdb/FMDatabaseQueue.h b/src/fmdb/FMDatabaseQueue.h
index b4915bf..27f4012 100755
--- a/src/fmdb/FMDatabaseQueue.h
+++ b/src/fmdb/FMDatabaseQueue.h
@@ -205,17 +205,33 @@ NS_ASSUME_NONNULL_BEGIN
/** Synchronously perform database operations on queue, using transactions.
@param block The code to be run on the queue of `FMDatabaseQueue`
+
+ @warning Unlike SQLite's `BEGIN TRANSACTION`, this method currently performs
+ an exclusive transaction, not a deferred transaction. This behavior
+ is likely to change in future versions of FMDB, whereby this method
+ will likely eventually adopt standard SQLite behavior and perform
+ deferred transactions. If you really need exclusive tranaction, it is
+ recommended that you use `inExclusiveTransaction`, instead, not only
+ to make your intent explicit, but also to future-proof your code.
+
*/
- (void)inTransaction:(__attribute__((noescape)) void (^)(FMDatabase *db, BOOL *rollback))block;
/** Synchronously perform database operations on queue, using deferred transactions.
-
+
@param block The code to be run on the queue of `FMDatabaseQueue`
*/
- (void)inDeferredTransaction:(__attribute__((noescape)) void (^)(FMDatabase *db, BOOL *rollback))block;
+/** Synchronously perform database operations on queue, using exclusive transactions.
+
+ @param block The code to be run on the queue of `FMDatabaseQueue`
+ */
+
+- (void)inExclusiveTransaction:(__attribute__((noescape)) void (^)(FMDatabase *db, BOOL *rollback))block;
+
/** Synchronously perform database operations on queue, using immediate transactions.
@param block The code to be run on the queue of `FMDatabaseQueue`
diff --git a/src/fmdb/FMDatabaseQueue.m b/src/fmdb/FMDatabaseQueue.m
index df993e0..0511cab 100755
--- a/src/fmdb/FMDatabaseQueue.m
+++ b/src/fmdb/FMDatabaseQueue.m
@@ -127,7 +127,6 @@ static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey
return [self initWithPath:nil];
}
-
- (void)dealloc {
FMDBRelease(_db);
FMDBRelease(_path);
@@ -189,6 +188,7 @@ static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey
dispatch_sync(_queue, ^() {
FMDatabase *db = [self database];
+
block(db);
if ([db hasOpenResultSets]) {
@@ -238,11 +238,15 @@ static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey
FMDBRelease(self);
}
+- (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block {
+ [self beginTransaction:FMDBTransactionExclusive withBlock:block];
+}
+
- (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block {
[self beginTransaction:FMDBTransactionDeferred withBlock:block];
}
-- (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block {
+- (void)inExclusiveTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block {
[self beginTransaction:FMDBTransactionExclusive withBlock:block];
}
diff --git a/src/fmdb/Info.plist b/src/fmdb/Info.plist
index a172c9b..1f28e82 100644
--- a/src/fmdb/Info.plist
+++ b/src/fmdb/Info.plist
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
- <string>2.7.3</string>
+ <string>2.7.4</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>