Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added online backup functionality. #410

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
94 changes: 94 additions & 0 deletions Tests/FMDatabaseOnlineBackupTests.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//
// FMDatabaseOnlineBackupTests.m
// fmdb
//
// Created by Mark Pustjens <pustjens@dds.nl> on 23/09/15.
// (c) Angelbird Technologies GmbH
//
//

#import <Cocoa/Cocoa.h>
#import <XCTest/XCTest.h>

@interface FMDatabaseOnlineBackupTests : FMDBTempDBTests

@end

@implementation FMDatabaseOnlineBackupTests

+ (void)populateDatabase:(FMDatabase *)db
{
[db executeUpdate:@"create table test (a text, b text, c integer, d double, e double)"];

[db beginTransaction];
int i = 0;
while (i++ < 2000) {
[db executeUpdate:@"insert into test (a, b, c, d, e) values (?, ?, ?, ?, ?)" ,
@"hi'", // look! I put in a ', and I'm not escaping it!
[NSString stringWithFormat:@"number %d", i],
[NSNumber numberWithInt:i],
[NSDate date],
[NSNumber numberWithFloat:2.2f]];
}
[db commit];
}

- (void)setUp {
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
}

- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
[super tearDown];
}

- (void)testBackupDatabase
{
NSString *backupPath = @"/tmp/tmp.db.bck";

// perform the backup
XCTAssertTrue ([self.db backupTo:backupPath withKey:nil andProgressBlock:^(int pagesRemaining, int pageCount) {
// No need to show progress.
}], @"Should have succeeded");

// check if the backup file exists
NSFileManager *fileManager = [NSFileManager defaultManager];
XCTAssertTrue([fileManager fileExistsAtPath:backupPath],
@"Backup db file should exist");

// test if the bakcup is ok
FMDatabase *bck = [FMDatabase databaseWithPath:backupPath];
XCTAssertTrue([bck open], @"Should pass");
XCTAssert([bck executeQuery:@"select * from test"] != nil, @"Should pass");
XCTAssertTrue([bck close], @"Should pass");

// delete backup
[fileManager removeItemAtPath:backupPath error:NULL];
}

- (void)testBackupDatabaseEncrypted
{
NSString *backupPath = @"/tmp/tmp.edb.bck";

// perform the backup
XCTAssertTrue ([self.db backupTo:backupPath withKey:@"passw0rd" andProgressBlock:^(int pagesRemaining, int pageCount) {
// No need to show progress.
}], @"Should have succeeded");

// check if the backup file exists
NSFileManager *fileManager = [NSFileManager defaultManager];
XCTAssertTrue([fileManager fileExistsAtPath:backupPath],
@"Backup db file should exist");

// test if the backup is ok
FMDatabase *bck = [FMDatabase databaseWithPath:backupPath];
XCTAssertTrue([bck open], @"Should pass");
XCTAssert([bck executeQuery:@"select * from test"] != nil, @"Should pass");
XCTAssertTrue([bck close], @"Should pass");

// delete backup
[fileManager removeItemAtPath:backupPath error:NULL];
}

@end
4 changes: 4 additions & 0 deletions fmdb.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
621721B61892BFE30006691F /* FMDatabasePool.m in Sources */ = {isa = PBXBuildFile; fileRef = CC9E4EB813B31188005F9210 /* FMDatabasePool.m */; };
6290CBB7188FE836009790F8 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6290CBB6188FE836009790F8 /* Foundation.framework */; };
67CB1E3019AD27D000A3CA7F /* FMDatabaseFTS3Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 67CB1E2F19AD27D000A3CA7F /* FMDatabaseFTS3Tests.m */; };
8081C06C1BB319E200CA7C73 /* FMDatabaseOnlineBackupTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8081C06B1BB319E200CA7C73 /* FMDatabaseOnlineBackupTests.m */; };
8314AF3318CD73D600EC0E25 /* FMDB.h in Headers */ = {isa = PBXBuildFile; fileRef = 8314AF3218CD73D600EC0E25 /* FMDB.h */; };
8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; };
8DD76F9F0486AA7600D96B5E /* fmdb.1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = C6859EA3029092ED04C91782 /* fmdb.1 */; };
Expand Down Expand Up @@ -95,6 +96,7 @@
6290CBB6188FE836009790F8 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
6290CBC6188FE837009790F8 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; };
67CB1E2F19AD27D000A3CA7F /* FMDatabaseFTS3Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabaseFTS3Tests.m; sourceTree = "<group>"; };
8081C06B1BB319E200CA7C73 /* FMDatabaseOnlineBackupTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabaseOnlineBackupTests.m; sourceTree = "<group>"; };
8314AF3218CD73D600EC0E25 /* FMDB.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FMDB.h; path = src/fmdb/FMDB.h; sourceTree = "<group>"; };
831DE6FD175B7C9C001F7317 /* README.markdown */ = {isa = PBXFileReference; lastKnownFileType = text; path = README.markdown; sourceTree = "<group>"; };
832F502419EC4C6B0087DCBF /* FMDatabaseVariadic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FMDatabaseVariadic.swift; path = "src/extra/Swift extensions/FMDatabaseVariadic.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -308,6 +310,7 @@
BF940F5A18417D490001E077 /* FMDBTempDBTests.h */,
BF940F5B18417D490001E077 /* FMDBTempDBTests.m */,
3354379B19E71096005661F3 /* FMResultSetTests.m */,
8081C06B1BB319E200CA7C73 /* FMDatabaseOnlineBackupTests.m */,
BF5D041B18416BB2008C5AA9 /* Supporting Files */,
);
path = Tests;
Expand Down Expand Up @@ -512,6 +515,7 @@
files = (
BFC152B118417F0D00605DF7 /* FMDatabaseAdditions.m in Sources */,
CCA66A3019C0CB1900EFDAC1 /* FMTokenizers.m in Sources */,
8081C06C1BB319E200CA7C73 /* FMDatabaseOnlineBackupTests.m in Sources */,
BF940F5C18417D490001E077 /* FMDBTempDBTests.m in Sources */,
BF940F5E18417DEA0001E077 /* FMDatabaseAdditionsTests.m in Sources */,
3354379C19E71096005661F3 /* FMResultSetTests.m in Sources */,
Expand Down
20 changes: 20 additions & 0 deletions src/fmdb/FMDatabase.h
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,26 @@ typedef int(^FMDBExecuteStatementsCallbackBlock)(NSDictionary *resultsDictionary

#endif

///----------------------------
/// @name Online Backup
///----------------------------

/** Perform a live backup of the database to a new file

@param path Path of the file to write the backup to
@param key The encryption key to use for the target database.
@param block Block to call with progress updates

@return@return `YES` if successful, `NO` on error.

@see [Using the SQLite Online Backup API()](http://sqlite.org/backup.html)

*/

- (BOOL)backupTo:(NSString*)aPath
withKey:(NSString*)key
andProgressBlock:(void (^)(int pagesRemaining, int pageCount))progressBlock;

///----------------------------
/// @name SQLite library status
///----------------------------
Expand Down
76 changes: 76 additions & 0 deletions src/fmdb/FMDatabase.m
Original file line number Diff line number Diff line change
Expand Up @@ -1330,6 +1330,82 @@ - (NSError*)inSavePoint:(void (^)(BOOL *rollback))block {

#endif

#pragma mark Online backup

- (BOOL)backupTo:(NSString*)aPath
withKey:(NSString*)key
andProgressBlock:(void (^)(int pagesRemaining, int pageCount))progressBlock
{
NSParameterAssert(aPath);
NSParameterAssert(progressBlock);

if (![self databaseExists]) {
return NO;
}

if (_isExecutingStatement) {
[self warnInUse];
return NO;
}

_isExecutingStatement = YES;

int err = 0;
sqlite3 *pFile = NULL;
sqlite3_backup *pBackup = NULL;

if (_traceExecution) {
NSLog(@"%@ backupTo: %@", self, aPath);
}

err = sqlite3_open([aPath fileSystemRepresentation], &pFile);
if (err != SQLITE_OK) {
goto backupTowithProgressBlockDone;
}
#ifdef SQLITE_HAS_CODEC
if (key) {
NSData *keyData = [NSData dataWithBytes:[key UTF8String] length:(NSUInteger)strlen([key UTF8String])];
err = sqlite3_key(pFile, [keyData bytes], (int)[keyData length]);
if (err != SQLITE_OK) {
goto backupTowithProgressBlockDone;
}
}
#endif

/* Open the backup object to accomplish the backup. */
pBackup = sqlite3_backup_init(pFile, "main", _db, "main");
if (!pBackup) {
goto backupTowithProgressBlockDone;
}

do {
err = sqlite3_backup_step(pBackup, 10); //TODO optimize page count
progressBlock(sqlite3_backup_remaining(pBackup), sqlite3_backup_pagecount(pBackup));

//if (err == SQLITE_OK || err == SQLITE_BUSY || err == SQLITE_LOCKED) {
// sqlite3_sleep(5);
//}
} while (err == SQLITE_OK || err == SQLITE_BUSY || err == SQLITE_LOCKED);

/* Release resources allocated by backup_init(). */
sqlite3_backup_finish(pBackup);


backupTowithProgressBlockDone:

err = sqlite3_errcode(pFile);
if (err != SQLITE_OK) {
NSString *msg = [NSString stringWithUTF8String:sqlite3_errmsg(pFile)];
NSLog(@"error performing backup: %d \"%@\"", err, msg);
}

_isExecutingStatement = NO;

sqlite3_close (pFile);

return (err == SQLITE_OK);
}

#pragma mark Cache statements

- (BOOL)shouldCacheStatements {
Expand Down