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.
Related
I want to simulate a shortage of storage, so I try to copy files util the storage is full, but I found it will never get to that, and I found the occuped storage is greater than system, how can this happen?
the code is as follows:
+ (void)copeFiles
{
NSString *srcPath = [self cloudStorageCacheFolderPath];
NSLog(#"copy: src path:%# ,size:%f", srcPath, [self sizeOfcloudStorageCacheFolder]);
int base = 300;
int i = base;
while (1) {
i++;
if (i > base + 100) {
break;
}
NSInteger freeSizeM = [self freeFileStorageSize];
if (freeSizeM > 1024) {
NSString *newFilePath = [NSString stringWithFormat:#"%#-%d", srcPath, i];
[self copyFolderContentFromSrc:srcPath toDestPath:newFilePath];
NSLog(#"copy: i:%d, freeSizeM:%li", i, (long)freeSizeM);
}else {
break;
}
}
}
+ (void)copyFolderContentFromSrc:(NSString *)srcPath toDestPath:(NSString *)dstPath
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSLog(#"copy: src:%# dst:%#", srcPath, dstPath);
BOOL isDir;
if (![fileManager fileExistsAtPath:dstPath isDirectory:&isDir] || !isDir) {
BOOL sucess = [fileManager createDirectoryAtPath:dstPath withIntermediateDirectories:YES attributes:nil error:nil];
NSLog(#"copy: isDir:%d sucess:%d", isDir, sucess);
}
NSArray* array = [fileManager contentsOfDirectoryAtPath:srcPath error:nil];
int i = 0;
for (NSString *fileName in array) {
i++;
NSString *srcFullPath = [srcPath stringByAppendingPathComponent:fileName];
NSString *toFullPath = [dstPath stringByAppendingPathComponent:fileName];
NSError *error;
BOOL isSucess = [fileManager copyItemAtPath:srcFullPath toPath:toFullPath error:&error];
NSLog(#"copy:%d", i);
if (!isSucess) {
NSLog(#"copy:error:%#", error);
}
}
}
image1
image2
The images may be invisible, so I describe the phenomenon simply in advance.My iphone is 32GB in total, but the available storage is 93GB showed in setting.
Yes, It will also increase if you run it again :)
The reason behind it is that It will store the reference only not an actual file. and I have done the same in my mac system to fill the space for simulator and I have ended up with same 256GB system showing 4.3 TB of space
In Device, you can fill space using record a video and still you can not get that error because Apple will manage it like this way.
1) Delete all temporary files that were stored in All App directory.
2) And then Clear cache memory.
Apple will manage the storage space management very well and there is still some mystery about it.
I've an application when users can save their ECG. I want to split Realm usage by user. I'm developing a user.realm (referenced as _realmUsers) to save all the users that login on application, and a userID.realm (referenced as _realm and They will be once per user that login on application) where user saves his data. How to achieve that? Because if I write things like
Initialization of both Realms
+ (void)initializeRealmForUsers
{
// Open the encrypted Realm file
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.fileURL = [[[config.fileURL URLByDeletingLastPathComponent]
URLByAppendingPathComponent:USER_REALM]
URLByAppendingPathExtension:#"realm"];
NSError *err;
RLMRealm *realmUser = [RLMRealm realmWithConfiguration:config error:&err];
if(err == nil && realmUser != nil)
[sharedInstance setRealmUsers:realmUser];
}
+ (void)initializeDataRealmWithUserID:(NSString*)userUUID
{
// Open the encrypted Realm file
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.fileURL = [[[config.fileURL URLByDeletingLastPathComponent]
URLByAppendingPathComponent:userUUID]
URLByAppendingPathExtension:#"realm"];
NSError *err;
RLMRealm *realmUser = [RLMRealm realmWithConfiguration:config error:&err];
if(err == nil && realmUser != nil)
[sharedInstance setRealmUsers:realmUser];
[[RealmManager sharedInstance] deleteIncompleteExam];
}
Creation of User:
- (User *)createUserWithData:(NSDictionary *)data
{
[_realmUsers beginWriteTransaction];
User *newUser = [User new];
[newUser setEmail:[data objectForKey:USER_EMAIL]];
[newUser setPassword:[CryptoUtils MD5String:[data
objectForKey:USER_PASSWORD]]];
[newUser setLogged:#NO];
[_realmUsers addOrUpdateObject:newUser];
[_realmUsers commitWriteTransaction];
return newUser;
}
Creation of Patient, a record type of the second realm (the specific-user one):
- (Patient *)createPatientWithData:(NSDictionary *)data forUser:(BOOL)forUser
{
Patient *newPatient;
if([[RealmManager sharedInstance] getSelfPatient] == nil)
newPatient = [Patient new];
else
newPatient = [[RealmManager sharedInstance] getSelfPatient];
[_realm beginWriteTransaction];
[newPatient setFirstName:[data objectForKey:PATIENT_FIRSTNAME]];
[newPatient setLastName:[data objectForKey:PATIENT_LASTNAME]];
[newPatient setSelfPatient:forUser];
[_realm addOrUpdateObject:newPatient];
[_realm commitWriteTransaction];
return newPatient;
}
and Login:
- (User *)loginWithUsername:(NSString *)email password:(NSString *)password andData:(NSDictionary *)userData
{
User *user = [self userWithUsername:email];
if(user == nil){
NSMutableDictionary *mutableData = [NSMutableDictionary new];
[mutableData addEntriesFromDictionary:userData];
[mutableData setObject:password forKey:USER_PASSWORD];
user = [self createUserWithData:mutableData];
} else{
[_realmUsers beginWriteTransaction];
[user setPassword:[CryptoUtils MD5String:password]];
[_realmUsers commitWriteTransaction];
}
[_realmUsers beginWriteTransaction];
[user setLogged:#YES];
[_realmUsers commitWriteTransaction];
[RealmManager initializeDataRealmWithUserID:[user uuid]];
[self createPatientWithData:#{PATIENT_FIRSTNAME : [userData objectForKey:PATIENT_FIRSTNAME], PATIENT_LASTNAME : [userData objectForKey:PATIENT_LASTNAME]} forUser:YES];
return user;
}
After Login, my Calls Like [User allObjects] (user must be on user.realm) [Patient allObject](patients must be on userID.realm) gives me empty result.
You can access datas from different realm by
[User allObjectsInRealm: user.realm]
[User objectsInRealm:user.realm where:#""];
I am having an existing coredata ios 7 project. I want to encrypt my sqlLite file. While implementing sqlchipher via EncryptedStore I am facing crash when I want to delete an entity say E1 which has one to many and relationship with other entity say E2 with delete rule cascade.
Getting log
'NSInternalInconsistencyException', reason: '(null)'
If I delete E2 individually then its not crashing at save context.
With out sqlcipher is working fine.
Entity Delete Rule
code for persistence store and manage object context
+ (NSPersistentStoreCoordinator *)persistentStoreCoordinatorWithSqliteName: (NSString*)userFolderName fromSqliteFileName:(NSString*) fromSqliteFileNameWithOutExtension shouldCopySQLiteFromBundle:(BOOL)shouldCopyFromBundle withDataModelName:(NSString*)dataModelName
{
static NSPersistentStoreCoordinator *coordinator = nil;
static dispatch_once_t tokenGRC;
dispatch_once(&tokenGRC, ^{
// get the model
NSManagedObjectModel *model = [self managedObjectModelWithName:dataModelName];
// get the coordinator
coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
NSString *sqliteExtension = #"sqlite";
NSString *sqliteFileWithExtension = [[NSString alloc]initWithFormat:#"%#.%#",fromSqliteFileNameWithOutExtension,sqliteExtension];
if (shouldCopyFromBundle == YES) {
NSError *error = nil;
[self copySqliteFromFileName:fromSqliteFileNameWithOutExtension toUserFolder:userFolderName withError:&error];
}
else
{
NSFileManager *defaultManager = [NSFileManager defaultManager];
NSArray* appDocumentDirList= [defaultManager URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask];
// Append the bundle ID to the URL for the
// Application Support directory
NSURL *dirPath = [[appDocumentDirList objectAtIndex:0] URLByAppendingPathComponent:userFolderName];
NSError *error = nil;
// If the directory does not exist, this method creates it.
// This method is only available in OS X v10.7 and iOS 5.0 or later.
if (![defaultManager createDirectoryAtURL:dirPath withIntermediateDirectories:YES
attributes:nil error:&error])
{
NSAssert2(NO, #"%s Directory could not be created %#",__func__,[error userInfo]);
// Handle the error.
}
}
NSString *customUserDirectory = [[NSString alloc]initWithFormat:#"%#/%#",userFolderName,sqliteFileWithExtension];
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:customUserDirectory];
NSError *error = nil;
// [[NSFileManager defaultManager] removeItemAtURL:databaseURL error:&error];
NSDictionary *options = #{
EncryptedStorePassphraseKey : SampleValue,
// EncryptedStoreDatabaseLocation : databaseURL,
NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES
};
NSPersistentStore *store = [coordinator
addPersistentStoreWithType:EncryptedStoreType
configuration:nil
URL:storeURL
options:options
error:&error];
// coordinator = [EncryptedStore makeStoreWithOptions:options managedObjectModel:model];
NSAssert(store, #"Unable to add persistent store!\n%#", error);
});
return coordinator;
}
+(A3ManagedObjectContext*)managedObjectContextFromCurrentUser:(NSString*)userName withConcurrencyType:(NSManagedObjectContextConcurrencyType)concurrencyType shouldCopySQLiteFromBundle:(BOOL)shouldCopyFromBundle withSqliteMode:(A3SqliteMode)sqliteMode
{
A3DataFetchMode dataFetchMode = [A3WebUtility currentDataFetchMode];
NSString *fromSqliteName = nil;
NSString *toFolderName = nil;
if (dataFetchMode == A3DataFetchModeOnline) {
fromSqliteName = #"DataBase";
toFolderName = userName;
}
else
{
fromSqliteName = #"DataBase-Demo";
toFolderName = #"DemoUser";
}
NSString *dataModelName = [self dataModelNameForSqliteMode:sqliteMode];
NSPersistentStoreCoordinator *coordinator = [A3ManagedObjectContext persistentStoreCoordinatorWithSqliteName:toFolderName fromSqliteFileName:fromSqliteName shouldCopySQLiteFromBundle:shouldCopyFromBundle withDataModelName:dataModelName];
if (coordinator != nil) {
A3ManagedObjectContext *tempManagedContext = [[A3ManagedObjectContext alloc]initWithConcurrencyType:concurrencyType];
[tempManagedContext setPersistentStoreCoordinator:coordinator];
tempManagedContext.sqliteMode = sqliteMode;
return tempManagedContext;
}
else
{
return nil;
}
}
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
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