iOS: Coredata backups - Anything to pay attention to? - ios

I'm using coredata in my app and want to offer the possibility to create and restore safety copies. My process is relatively simple:
- The user can just create a copy of the current active database, this is a new backup
- If he chooses to restore a backup, then there will be a safety copy of the current active database created, then the active database will be deleted and the selected safety copy will be copied and renamed to the previous active database name. Does this work that simple or are there any traps?
Here's my code:
#pragma mark Database methods
- (BOOL)createSafetyCopy: (void (^)(void))completionHandler
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSString *dbPath = [self getFullDBPathAsString];
if([fileManager fileExistsAtPath:dbPath]) {
//Create the timestamp
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"yyyyMMdd"];
NSDateFormatter *timeFormat = [[NSDateFormatter alloc] init];
[timeFormat setDateFormat:#"HHmmss"];
NSDate *now = [[NSDate alloc] init];
NSString *timeStamp = [NSString stringWithFormat:#"%#_%#", [dateFormat stringFromDate:now], [timeFormat stringFromDate:now]];
NSString *copyPath = [[self.applicationDocumentsDirectory URLByAppendingPathComponent:[NSString stringWithFormat:#"%#%#", timeStamp, kSafetyCopySuffix]] path];
bool success = [fileManager copyItemAtPath:dbPath toPath:copyPath error:&error];
if (!success){
NSLog(#"Failed to create safety copy with message '%#'.", [error localizedDescription]);
return FALSE;
} else {
completionHandler();
}
} else {
NSLog(#"Active database doesn't exist at path: %#", dbPath);
return FALSE;
}
return TRUE;
}
- (NSArray *)getListOfSafetyCopies
{
NSFileManager *manager = [NSFileManager defaultManager];
//get the apps documents directory
NSString *documentsDirectory = [self.applicationDocumentsDirectory path];
// grab all the files in the documents dir
NSArray *allFiles = [manager contentsOfDirectoryAtPath:documentsDirectory error:nil];
// filter the array for only safety copies
NSPredicate *filter = [NSPredicate predicateWithFormat:#"self ENDSWITH %#", kSafetyCopySuffix];
NSArray *safetyCopies = [allFiles filteredArrayUsingPredicate:filter];
return safetyCopies;
}
- (BOOL)restoreSafetyCopy:(NSString *)safetyCopyName
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSString *fullSafetyCopyPath = [[self.applicationDocumentsDirectory URLByAppendingPathComponent:safetyCopyName] path];
if([fileManager fileExistsAtPath:fullSafetyCopyPath]){
//create safety copy of currently active database
//[self createSafetyCopy];
//Then delete active database
if ([fileManager removeItemAtPath:[self getFullDBPathAsString] error:&error]){
//And if successfull replace it with the safety copy;
if (![fileManager copyItemAtPath:fullSafetyCopyPath toPath:[self getFullDBPathAsString] error:&error]){
NSLog(#"Failed to restore safety copy with message '%#'.", [error localizedDescription]);
return FALSE;
}
} else {
NSLog(#"Failed to delete active database with message '%#'.", [error localizedDescription]);
return FALSE;
}
} else {
NSLog(#"Safety copy doesn't exist at path: %#", fullSafetyCopyPath);
return FALSE;
}
return TRUE;
}

CoreData data is not stored only in single sql file. In my DB folder I see 3 files: Model.sqlite, Model.sqlite-shm, Model.sqlite-wal.
You can read about WAL-journal, but as far as I know, you can't rely only on copying 1 sqlite file. Some data (transactions) is stored in WAL.
Actually you can read about it here:
Technical Q&A QA1809

Related

"Bad Realm file header (#1)" Exception when opening Realm file in iOS

I am using REALM version 0.98.1 (For Objective C), and sometimes getting "Bad Realm file header" exception while compressing DB size on app launch.
Below is the method calling sequence in AppDelegate application didFinishLaunch....
[self setDefaultConfigrutaionForRealm];
[self vacuumRealm];
Below is the code to Configure Realm:
+(void)setDefaultConfigrutaionForRealm{
RLMRealmConfiguration * defCongfig = [RLMRealmConfiguration defaultConfiguration];
defCongfig.path = REALM_PATH(REALM_FILE_NAME);
defCongfig.schemaVersion = SCHEMA_VERSION;
[RLMRealmConfiguration setDefaultConfiguration:defCongfig];
}
And below is the code to VacuumRealm (Compress DB size):
+ (void)vacuumRealm {
#try{
#autoreleasepool {
RLMRealm *realm = [RLMRealm defaultRealm];
NSString *realmPath = [realm path];
NSLog(#"vacuumRealm realmPath = %#", realmPath);
long long fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:realmPath error:nil][NSFileSize] longLongValue];
NSLog(#"vacuumRealm ENTER filesize = %llu", fileSize);
//
NSError *err;
BOOL success;
NSDate *startDate = [NSDate date];
NSString *copyPath = [realmPath stringByAppendingString:#".copy"];
[[NSFileManager defaultManager] removeItemAtPath:copyPath error:&err];
success = [realm writeCopyToPath:copyPath error:&err];
if (success) {
success = [[NSFileManager defaultManager] removeItemAtPath:realmPath error:&err];
if (success) {
success = [[NSFileManager defaultManager] copyItemAtPath:copyPath toPath:realmPath error:&err];
if (success) {
[[NSFileManager defaultManager] removeItemAtPath:copyPath error:&err];
NSDate *endDate = [NSDate date];
NSTimeInterval executionTime = [endDate timeIntervalSinceDate:startDate];
NSLog(#"vacuumRealm cleanup took %f ms", executionTime);
}
}
}
//
fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:realmPath error:nil][NSFileSize] longLongValue];
NSLog(#"vacuumRealm EXIT filesize = %llu", fileSize);
}
}
#catch (NSException *exception) {
NSLog(#"Inside vacuumRealm exception = %#",exception.description);
}
#finally {
}
}
While debugging I observed realm path has been configured properly in "setDefaultConfigrutaionForRealm" method (Attached Screen shot for reference), but once "vacuumRealm" method is called there I am getting "Bad Realm file header (#1)" on below line :
RLMRealm *realm = [RLMRealm defaultRealm];
any help to resolve this Exception will really be helpful.
Thanks in advance.
You're creating a Realm instance (RLMRealm *realm = [RLMRealm defaultRealm];) and deleting the file from under it without releasing the instance. This will cause problems like the corruption you're seeing because you're modifying the file while Realm is still accessing it.
Here's an updated version of your method (omitting the debug logs and err since it wasn't being used):
__block BOOL copySuccess = NO;
NSString *realmPath = [[RLMRealmConfiguration defaultConfiguration] path];
NSString *copyPath = [realmPath stringByAppendingString:#".copy"];
#autoreleasepool {
[[NSFileManager defaultManager] removeItemAtPath:copyPath error:nil];
copySuccess = [[RLMRealm defaultRealm] writeCopyToPath:copyPath error:nil];
}
if (copySuccess && [[NSFileManager defaultManager] removeItemAtPath:realmPath error:nil]) {
[[NSFileManager defaultManager] moveItemAtPath:copyPath toPath:realmPath error:nil];
}
Also, whatever exceptions are being thrown in this process are not designed to be recoverable. So the only safe thing to do in the #catch block is to abort. Or not have a #try/#catch clause at all.

Sqlite DB no such table exists

Ok so I have a database in my iPhone simulator documents. And I now know for sure it's in the applications sandbox. Something is funky in the code I have. So I first get the DB path:
-(NSString *)getsynDbPath
{
NSString* dataBAse = [[NSBundle mainBundle] pathForResource:#"ddd"ofType:#"sqlite"];
return dataBAse;
}
Then I test the path:
NSString *testData;
testData = [self getsynDbPath];
NSFileManager * fileManager = [NSFileManager defaultManager];
BOOL success = [fileManager fileExistsAtPath:testData];
if (success) {
NSLog(#"Oh no! There was a big problem!");
} else {
//Successfully opened
if(sqlite3_open([testData UTF8String], &db)==SQLITE_OK){
NSLog(#"Raise the roof!");
//Calling method to loop through columns
[self listOfCols];
}
}
I then go to a custom method where I loop through the columns inside the database:
-(NSArray *)listOfCols{
NSMutableArray *retval = [[[NSMutableArray alloc]init]autorelease];
NSString *query = #"SELECT KEY_ID FROM CON_DETAIL";
sqlite3_stmt *statement;
//Does not execute
if (sqlite3_prepare_v2(db, [query UTF8String], -1, &statement, nil)==SQLITE_OK) {
while (sqlite3_step(statement)==SQLITE_ROW) {
int key_id = sqlite3_column_int(statement, 0);
NSLog(#"Key ID: %d", key_id);
char *nameChars = (char *) sqlite3_column_text(statement, 1);
NSLog(#"chars %s", nameChars);
char *cityChars = (char *) sqlite3_column_text(statement, 2);
NSLog(#"chars %s", cityChars);
}
}
NSLog(#"%s Why '%s' (%1d)", __FUNCTION__, sqlite3_errmsg(db), sqlite3_errcode(db));
return retval;
}
So here's my question. After I successfully opened the database, why the heck am I getting a log error that says: no such table: CON_DETAIL ? Any help is appreciated.
I think you have to copy your db in your document directory and then try to fetch. Copy it with following functions.
-(void) dbconnect{
self.databaseName = #”yourdbname.sqlite”;
// Get the path to the documents directory and append the databaseName
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
self.databasePath = [documentsDir stringByAppendingPathComponent:self.databaseName];
// Execute the “checkAndCreateDatabase” function
[self checkAndCreateDatabase];
}
-(void) checkAndCreateDatabase{
// Check if the SQL database has already been saved to the users phone, if not then copy it over
BOOL success;
// Create a FileManager object, we will use this to check the status
// of the database and to copy it over if required
NSFileManager *fileManager = [NSFileManager defaultManager];
// Check if the database has already been created in the users filesystem
success = [fileManager fileExistsAtPath:databasePath];
// If the database already exists then return without doing anything
if(success) {
return;
}
// If not then proceed to copy the database from the application to the users filesystem
// Get the path to the database in the application package
NSString *databasePathFromApp = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:databaseName];
// Copy the database from the package to the users filesystem
[fileManager copyItemAtPath:databasePathFromApp toPath:self.databasePath error:nil];
[fileManager release];
}
NOTE: If you are not getting db in your app’s document directory do the following.
Go to : Target -> “Build Phases” -> “copy bundle Resources” Then add that particular file here.
After that call your "listOfCols" method.

Update row in sqlite isn`t updating

I am trying to update just one cell in row but I can`t get it work. Method for updating:
- (void) UpdateQuestionShownParameter:(int)QuestionId :(BOOL)QuestionShown{
#try {
NSFileManager *fileMgr = [NSFileManager defaultManager];
NSString *dbPath = [[[NSBundle mainBundle] resourcePath ]stringByAppendingPathComponent:#"Milionar.sqlite"];
const char *sql = "UPDATE Questions set Show = ? WHERE id = ?";
BOOL success = [fileMgr fileExistsAtPath:dbPath];
if(!success)
{
NSLog(#"Cannot locate database file '%#'.", dbPath);
}
if(sqlite3_open([dbPath UTF8String], &db) == SQLITE_OK)
{
sqlite3_stmt *sqlStatement;
if(sqlite3_prepare_v2(db, sql, -1, &sqlStatement, NULL) == SQLITE_OK)
{
NSInteger shownInteger = (QuestionShown ? 1 : 0);
sqlite3_bind_int(sqlStatement, 1, shownInteger);
sqlite3_bind_int(sqlStatement, 2, QuestionId);
if (sqlite3_step(sqlStatement) != SQLITE_DONE)
{
NSLog(#"Error while updating. '%s'", sqlite3_errmsg(db));
}
sqlite3_finalize(sqlStatement);
}
else
{
NSLog(#"Problem with prepare statement");
}
}
else
{
NSLog(#"An error has occured while opening database.");
}
sqlite3_close(db);
}
#catch (NSException *exception) {
NSLog(#"An exception occured: %#", [exception reason]);
}
}
Trying in ViewDidLoad:
- (void)viewDidLoad
{
ListOfQuestions *listQuestions =[[ListOfQuestions alloc] init];
self.Questions = [listQuestions getQuestions];
Question *generatedQuestion = (Question *) [self.Questions objectAtIndex:0];
[listQuestions UpdateQuestionShownParameter:generatedQuestion.id :TRUE];
[self.Description setText:(generatedQuestion.Description)];
[super viewDidLoad];
// Do any additional setup after loading the view.
}
Everytime when I tried to run app I get 0 in Shown column. But I don`t have any errors. So am I doing something wrong or everytime when I tried to run app in emulator I get recreate database from project database?
Thanks
You are opening the database in the bundle, which is read-only. You should be copying the database from bundle to Documents folder if the database doesn't already exist in Documents folder:
NSString *filename = #"Milionar.sqlite";
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *bundlePath = [[[NSBundle mainBundle] resourcePath ]stringByAppendingPathComponent:filename];
NSString *documentsFolder = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *documentsPath = [documentsFolder stringByAppendingPathComponent:filename];
if (![fileManager fileExistsAtPath:documentsPath]) {
NSError *error = nil;
BOOL success = [fileManager copyItemAtPath:bundlePath toPath:documentsPath error:&error];
NSAssert(success, #"Unable to copy database: %#", error);
}
if (sqlite3_open([documentsPath UTF8String], &db) != SQLITE_OK) {
NSLog(#"Open failed");
} else {
// ...
}
For more information about where documents belong, see the File System Programming Guide.
By the way, if you're looking for the Documents folder for your simulator, that's located in ~/Library/Application Support/iPhone Simulator (in Xcode 6, this is now ~/Library/Developer/CoreSimulator/Devices). If you don't see the "Library" folder, you can unhide it by typing the following command into your Terminal command line interface:
chflags nohidden ~/Library

Reading an already made sqlite file on ios

I have an already made .sqlite file, I can use the mozilla plugin and see all the fields and edit/add etc. I want to read this file into an iphone application. How do I approach reading the file and saving each entry into a new NSObject?
All I can find on stackoverflow when I search for this question is people saying to use the mozilla plugin but not actually talk about the objective-c side of things.
Thanks in advance,
Oli
To achieve this , you'll need a class that creates the database by copying the database from your project resource to the actual directory that app use.
So, create a Objective C class called DataController like this. In DataController.h do like this
#import <Foundation/Foundation.h>
#import <sqlite3.h>
#interface DataController : NSObject
{
sqlite3 *databaseHandler ;
}
-(void)initDatabase;
-(NSArray*)getBooks;
-(NSString*)getChapter:(NSString*) bible:(NSString*) book:(NSString*) chapter;
#end
In it's implementation do like this. Assume, your database is bible.sqlite . What it basically do is , it checks the document directory that if the database exists , if not it copies your already created database from your project resource to actual directory. Here's the codes.
#import "DataController.h"
#import <sqlite3.h>
#implementation DataController
-(void)initDatabase
{
// Create a string containing the full path to the sqlite.db inside the documents folder
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *databasePath = [documentsDirectory stringByAppendingPathComponent:#"Bibles.sqlite"];
// Check to see if the database file already exists
bool databaseAlreadyExists = [[NSFileManager defaultManager] fileExistsAtPath:databasePath];
// Open the database and store the handle as a data member
if (sqlite3_open([databasePath UTF8String], &databaseHandler) == SQLITE_OK)
{
// Create the database if it doesn't yet exists in the file system
if (!databaseAlreadyExists)
{
NSLog(#"Database doesn't Exists");
BOOL success = NO ;
NSFileManager *filemngr = [NSFileManager defaultManager];
NSError *error;
NSString *defaultDbPath = [[[NSBundle mainBundle] resourcePath]stringByAppendingPathComponent:#"Bibles.sqlite"];
NSLog(#"Size : %lld" , [[[NSFileManager defaultManager] attributesOfItemAtPath:defaultDbPath error:nil] fileSize]);
[filemngr removeItemAtPath:databasePath error:&error];
success = [filemngr copyItemAtPath:defaultDbPath toPath:databasePath error:&error];
if (!success){
NSLog(#"Error : %#" , [error localizedDescription]);
} else{
NSLog(#"Copy Successful");
NSLog(#"Size : %lld" , [[[NSFileManager defaultManager] attributesOfItemAtPath:databasePath error:nil] fileSize]);
}
} else{
NSLog(#"Database already Exists of Size : %lld" , [[[NSFileManager defaultManager] attributesOfItemAtPath:databasePath error:nil] fileSize]);
}
}
}
- (void)dealloc {
sqlite3_close(databaseHandler);
}
-(NSArray*)getBooks
{
NSMutableArray *Books = [[NSMutableArray alloc]init];
NSString *queryStatement = [NSString stringWithFormat:#"SELECT Name FROM Book ORDER BY ID"];
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(databaseHandler, [queryStatement UTF8String], -1, &statement, NULL) == SQLITE_OK)
{
while (sqlite3_step(statement) == SQLITE_ROW) {
NSString *bookName = [NSString stringWithUTF8String:(char*)sqlite3_column_text(statement, 0)];
[Books addObject:bookName];
NSLog(#"Book : %#" , bookName);
}
sqlite3_finalize(statement);
} else{
NSLog(#"Error : %s",sqlite3_errmsg(databaseHandler));
}
return Books ;
}
-(NSString*)getChapter:(NSString*) bible:(NSString*) book:(NSString*) chapter{
NSString *verse = [[NSString alloc]init];
NSString *queryStatement = [NSString stringWithFormat:#"SELECT Text FROM %# WHERE BookID = %# AND Chapter = %#" , bible , book , chapter];
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(databaseHandler, [queryStatement UTF8String], -1, &statement, NULL) == SQLITE_OK)
{
while (sqlite3_step(statement) == SQLITE_ROW) {
NSString *temp = [NSString stringWithUTF8String:(char*)sqlite3_column_text(statement, 0)];
verse = [[NSString alloc]initWithFormat:#"%#\n\n%#",verse ,temp];
// NSLog(#"Book : %#" , verse);
}
sqlite3_finalize(statement);
}
return verse ;
}
#end
Ok...I have created 2 methods for you to show how to use it.
Firstly , in Appdelegate.m , you have to init the database by doing this
DataController *c = [[DataController alloc]init];
[c initDatabase];
Now , let's use it. It's very simple. Just take an instance of DataController and call the method that runs SQL within the database like the last 2 methods I have written. Hope it solves your problem.
I had the same issue a couple of months ago, I found the solution in youtube.
You have to copy your sqlite file to your bundle, import all the link libraries then write some small pieces of code mainly.
Here's a video, you don't need to watch it all, its kinda long.
https://www.youtube.com/watch?v=SVMorX_2Ymk
If you just need the codes let me know ;D

How to open sqlite database on ios 6?

I am really new in ios development. What I try to do now is open an existing sqlite database and select data from there. I debug my source and see that I open the database success (I think I success since the *database pointer is not nil). but when I use sqlite3_prepare_v2() to initialize the select query, I always receive the error: "No such table People". I have checked at the path:
~/Library/Application Support/iphone simulator/6.0/Application//.
The database was copied successful, I can open it a see the data there.
Here is my code to copy the database and open it:
- (NSString*) getDatabasePath{
//Search for standard documents using NSSearchPathForDirectoriesInDomains
//First Param = Searching the documents directory
//Second Param = Searching the Users directory and not the System
//Expand any tildes and identify home directories.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
return [documentsDir stringByAppendingPathComponent:#"data.sqlite"];
}
- (void)copyDatabaseToDocument {
//Using NSFileManager we can perform many file system operations.
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSString *dbPath = [self getDatabasePath];
BOOL success = [fileManager fileExistsAtPath:dbPath];
if(!success) {
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"data.sqlite"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:dbPath error:&error];
if (!success)
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}
- (sqlite3*)openDatabaseConnection {
sqlite3 *database;
NSString * path = [self getDatabasePath];
if (sqlite3_open([path UTF8String], &database) != SQLITE_OK) {
sqlite3_close(database);
NSAssert1(0, #"Failed to open database with message '%s'.", sqlite3_errmsg(database));
}
return database;
}
And here is my code to select data. The error occurs at line: sqlite3_prepare_v2(database, query, -1, &selectStatement, NULL)
- (People*) getPeople{
sqlite3 *database = [[[DBConnector alloc] init] openDatabaseConnection];
if(database == nil)
return nil;
sqlite3_stmt *selectStatement;
NSString *rawquery = #"select * from people";
const char *query = [rawquery UTF8String];
NSMutableArray* result = [[NSMutableArray alloc] init];
if (sqlite3_prepare_v2(database, query, -1, &selectStatement, NULL) == SQLITE_OK) {
while (sqlite3_step(selectStatement) == SQLITE_ROW) {
//Parse the data by calling a private method:
People *people = [self parsePeopleWithStatement:selectStatement];
[result addObject:people];
}
}else{
NSAssert1(0, #"Error: '%s'.", sqlite3_errmsg(database));
}
sqlite3_finalize(selectStatement);
return result;
}
Please tell me if you know what the mistake I have.
Thanks.
I have resolved the problem by myself. When I debug the app, I see that It didn't call applicationDidFinishLaunching method, It calls applicationDidFinishLaunchingWithOptions method. I just place the code to call the copyDatabaseToDocument at the applicationDidFinishLaunchingWithOptions method and It works.
Here is the code of my Delegate class:
//THIS METHOD WAS NOT CALLED
- (void)applicationDidFinishLaunching:(UIApplication *)application{
DBConnector *connector = [[DBConnector alloc] init];
[connector copyDatabaseToDocument];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
//SHOW MY SCREEN
(...)
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
By the way, I think this is not a real answer sync I don't know why my App didn't start at the applicationDidFinishLaunching(). If you know, please give me a description.
Thanks.

Resources