My app has turned on Data Protection and I created a file with NSFileProtectionComplete
+ (void)createLogFile {
NSString *deviceModel = [Utils getDeviceModel];
NSString *appVersion = [Utils getAppVersion];
NSData *initData = [[NSString stringWithFormat:#"%#-%#\n================================\n\n\n", deviceModel, appVersion] dataUsingEncoding:NSUTF8StringEncoding];
[[NSFileManager defaultManager] createFileAtPath:[self logFilePath]
contents:initData
attributes:#{NSFileProtectionKey: NSFileProtectionComplete}];
}
and when I lock my device applicationProtectedDataWillBecomeUnavailable: will be called.
- (void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSData *key = [MyKeychain getKey];
NSString *log = [NSString stringWithFormat:#"The key is:\n %#", key];
[MyFileLogger logInfo:log];
});
}
Then I can find the result in the file, which means I was able to write that file when my device is locked.
Shouldn't Data Protection prevents from accessing files when device is locked? What's wrong?
--updated-- (add method logInfo:)
+ (void)logInfo:(NSString *)str {
NSString *info = [self wrapWithTimestamp: str];
NSString *logFilePath = [Utils logFilePath];
if (![[NSFileManager defaultManager] fileExistsAtPath:logFilePath]) {
[Utils createLogFile];
}
NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:logFilePath];
[handle truncateFileAtOffset:[handle seekToEndOfFile]];
[handle writeData:[info dataUsingEncoding:NSUTF8StringEncoding]];
[handle closeFile];
}
According to the answer to this question, after the applicationProtectedDataWillBecomeUnavailable method is called there is a 10 second "grace period" before data protection activates.
If you increase your time delay from 5 to 11 seconds you should see that your data is not written to your log file.
I was able to observe this with sample code and an 11 second delay.
Related
Since writeData call is synchronous, what is the best way to use it when we call writeData from a different thread other than main queue?
For instance, a web service is called to fetch some data and the completionHandler is assigned to the web service call. Now this completion handler will be executed on a different thread (not on main queue).
I have seen my app getting stuck, on writeData method for 5 to 6 mins. This is the only thing I can suspect right now.
I tried wrapping around my writeData call with dispatch_async(mainQueue) but it did not work.
- (void) writeToFile: (NSString *) targetString
{
//_loggingString holds the data, which keeps on accumulating as the user performs operations. At some point of time (callbacks from API's I call this method, to actually, write this string in the file and clear this string afterwards.)
NSString *oldString = [_loggingString copy];
_loggingString = [oldString stringByAppendingString:targetString];
if (![[NSFileManager defaultManager]fileExistsAtPath:#"somePath"])
{
[[NSFileManager defaultManager]createFileAtPath:#"somePath" contents:nil attributes:nil];
}
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:#"somePath"];
[fileHandle seekToEndOfFile];
[fileHandle writeData:[_loggingString dataUsingEncoding:NSUTF8StringEncoding]];
_loggingString = #"";
}
You can do the saving part in a BackGround thread
- (void) writeToFile: (NSString *) targetString
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *oldString = [_loggingString copy];
_loggingString = [oldString stringByAppendingString:targetString];
if (![[NSFileManager defaultManager]fileExistsAtPath:#"somePath"])
{
[[NSFileManager defaultManager]createFileAtPath:#"somePath" contents:nil attributes:nil];
}
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:#"somePath"];
[fileHandle seekToEndOfFile];
[fileHandle writeData:[_loggingString dataUsingEncoding:NSUTF8StringEncoding]];
_loggingString = #"";
});
}
It is not preferred to do file write operation in main thread. Also, there will be issue in performance with default global queue, as the system cannot prioritize the task.
So try to create 4 types of background queues:
dispatch_queue_t GlobalUserInteractiveQueue(void) {
return dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
}
dispatch_queue_t GlobalUserInitiatedQueue(void) {
return dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0);
}
dispatch_queue_t GlobalUtilityQueue(void) {
return dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
}
dispatch_queue_t GlobalBackgroundQueue(void) {
return dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0);
}
In your code just do this:
create custom queue.
queue = dispatch_queue_create("customQueueName", NULL);
then write code in dispatch async
dispatch_async( queue ,
^ {
// execute asynchronously
[fileHandle seekToEndOfFile];
[fileHandle writeData:[_loggingString dataUsingEncoding:NSUTF8StringEncoding]];
});
Check the working process of each queue here:
https://gist.github.com/ankitthakur/dd945a66924fbd697169
We are using the SSZipArchive method createZipFileAtPath:withFilesAtPaths: in order to compress a single log file, which is then uploaded to Amazon S3. All files are located in our mobile app's NSTemporaryDirectory().
Sometimes, when I download the resulting zip files from Amazon to my mac and double-click them, I get the following error:
Archive Utility
Unable to expand "[file name].zip" into "[folder name]".
(Error 1 - Operation not permitted.)
When this happens, I always notice that the file size is 22 bytes. This seems too small. When the files behave OK they are usually 1 mb or more.
Here is the relevant code from our app:
-(void) sendConsoleLog
{
//NSError * error;
//NSString * sTemp = [[NSString alloc] initWithContentsOfFile:[self logFilePath] encoding:NSUTF8StringEncoding error:&error];
[SSZipArchive createZipFileAtPath:[self compressedLogFilePath] withFilesAtPaths:#[[self logFilePath]]];
[self upload];
}
-(void) upload
{
_sDestinationFileName = [NSString stringWithFormat:#"logs/%#.zip", [[NSUUID UUID] UUIDString]];
AWSS3TransferUtilityUploadExpression *expression = [AWSS3TransferUtilityUploadExpression new];
expression.progressBlock = self.progressBlock;
[expression setValue:#"public-read" forRequestHeader:#"x-amz-acl"];
AWSS3TransferUtility *transferUtility = [AWSS3TransferUtility defaultS3TransferUtility];
NSString * sFileURL = [NSString stringWithFormat:#"file://%#", [self compressedLogFilePath]];
[[transferUtility uploadFile:[NSURL URLWithString:sFileURL]
bucket:S3_BUCKET_NAME
key:_sDestinationFileName
contentType:#"application/octet-stream"
expression:expression
completionHander:self.completionHandler] continueWithBlock:^id(AWSTask *task) {
if (task.error) {
NSLog(#"cclChatSendLog continueWithBlock Error: %#", task.error);
}
if (task.exception) {
NSLog(#"cclChatSendLog continueWithBlock Exception: %#", task.exception);
}
if (task.result) {
AWSS3TransferUtilityUploadTask *uploadTask = task.result;
NSUInteger iTaskID = uploadTask.taskIdentifier;
NSLog(#"cclChatSendLog.h %lu continueWithBlock: Uploading...", (unsigned long)iTaskID);
}
return nil;
}];
}
-(NSString*) logFilePath
{
NSString *fileName =[NSString stringWithFormat:#"console%lu.log",(unsigned long)(appDelegate.hash)];
return [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
}
-(NSString*) compressedLogFilePath
{
return [NSString stringWithFormat:#"%#.zip", [self logFilePath]];
}
I wonder if the 22 bytes zip is created in case there is no file found in [self logFilePath]. Has anyone encountered a similar behaviour?
I am tying and succeeded to save a photo using CloudKit, at least I think I did, because I don't know a user friendly way to check if I am right. I am asking these in the scenario that the users makes a few photos and the photos are saved using cloudKit and then he deletes the app but theoretically he should still be able to access the data via some sort of interface provided by apple, am I right?
And here is the core that I am using:
- (void) saveToiCloudImageNamed: (NSString *)imageName andTimeCreated:(NSString *)timeCreated{
CKContainer *container = [CKContainer defaultContainer];
CKDatabase *privateDatabase = [container publicCloudDatabase];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths firstObject];
NSString *fullPath = [documentsDirectory stringByAppendingPathComponent:imageName];
NSURL *fileUrl = [[NSURL alloc] initFileURLWithPath:fullPath];
CKAsset *photoAsset = [[CKAsset alloc] initWithFileURL:fileUrl];
CKRecord *record = [[CKRecord alloc] initWithRecordType:#"Photo"];
record[#"asset"] = photoAsset;
record[#"name"] = timeCreated;
[privateDatabase saveRecord:record
completionHandler:^(CKRecord *record, NSError *error) {
if (error==nil) {
NSLog(#"The save was successful");
//Do something
}else{
NSLog(#"Error saving with localizedDescription: %#", error.localizedDescription);
NSLog(#"CKErrorCode = %lu", (long)[error code]);
if ([error code]==CKErrorNetworkFailure) {
double retryAfterValue = [[error.userInfo valueForKey:CKErrorRetryAfterKey] doubleValue];
NSLog(#"Error code network unavailable retrying after %f", retryAfterValue);
// NSTimer *timer = [NSTimer timerWithTimeInterval:retryAfterValue
// target:self
// selector:#selector(testOutCloudKit)
// userInfo:nil
// repeats:NO];
// [timer fire];
}
}
}];
}
At the moment there is no interface for accessing CloudKit data other than what you create yourself. Maybe you want to use iCloud documents instead.
When you save to the public database (as you are doing in the sample above) then you could access the data using the CloudKit dashboard. But that is only accessible by member in your Apple developer account.
I'm still learning iOS , just i finish my first application but i want to add some function when the application running , here I'm using did finish launching with option method in appdelegate , i want to change this code , first check if the user have internet or not if not show uialertView also , if there is no internet i need a function can stop the application like
Alert ( this application need internet and you dont have internet right now pls try later ) and the application will exit .
or also in some case maybe the web service out of work
if possible explain me where i should put the if statement and how i can exit the application
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[UINavigationBar appearance] setBarTintColor:[UIColor lightGrayColor]];
NSFileManager *fileManger=[NSFileManager defaultManager];
NSError *error;
NSArray *pathsArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *doumentDirectoryPath=[pathsArray objectAtIndex:0];
NSString *destinationPath= [doumentDirectoryPath stringByAppendingPathComponent:#"istudentDatabase.plist"];
// NSLog(#"plist path %#",destinationPath);
//if Plist not exists will copy new one
if ([fileManger fileExistsAtPath:destinationPath]){
NSLog(#"Settings File exists ");
}else{
// Copy New Plist
NSString *sourcePath=[[[NSBundle mainBundle] resourcePath]stringByAppendingPathComponent:#"istudentDatabase.plist"];
[fileManger copyItemAtPath:sourcePath toPath:destinationPath error:&error];
}
settingsClass * settings =[[settingsClass alloc]init];
NSNumber * userid = [settings loadPlist:[NSString stringWithFormat:#"userid"]];
if ([userid intValue] == 0)
{
//NSLog(#"You Dont Have USerid ");
// Send a synchronous request
NSURLRequest * urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://fahads-macbook-pro.local/ios/newuser.php"]];
NSURLResponse * response = nil;
NSError * error = nil;
NSData * data = [NSURLConnection sendSynchronousRequest:urlRequest
returningResponse:&response
error:&error];
if (error == nil)
{
NSDictionary * mydata = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
[settings saveNewUserId:[mydata[#"userid"] intValue]];
NSLog(#"%#",mydata[#"userid"]);
}else{
NSLog(#"Error please Check Your Connections ");
}
}else{
NSLog(#"You Have Userid : %#",userid);
}
NSMutableDictionary * itemsPlist = [[NSMutableDictionary alloc]initWithContentsOfFile:destinationPath];
NSLog(#"Items : %#",itemsPlist);
return YES;
}
also if there is no way to exit the application , i have view controller and on this view controller there is push button i want to hide this button from appdelegate with If statement for example if no connection hide the start button and show some hint there is no connection.
thanks advance
I'm trying to sync files created in my app to Dropbox, however it seems the syncing only happens after the app quits, and not in real time when files are created and moved between locations in different folders in the app or created/deleted. Is there a certain call I have to make for instance? Appreciate your help!
Below is the code I am using for syncing:
-(void)createFilePathinFolder:(NSString *)folderName FileName:(NSString *)fileName {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *folder = [self localDocumentsRootPath];
if (![folderName isEqualToString:#"root"]) {
folder = [folder stringByAppendingPathComponent:folderName];
}
NSString *file = [folder stringByAppendingPathComponent:fileName];
if (![fileManager fileExistsAtPath:file]) {
[fileManager createFileAtPath:file contents:[#"0" dataUsingEncoding:NSUTF8StringEncoding] attributes:nil];
}
//Insert to FileTable
[[DBHelper shared]insertToFileTableWithFolder:folderName FileName:fileName MetaFileName:nil Tag:nil Title:nil];
if ([NetworkHelper shared].canSyncWithCloud) {
NSString *filePathStr = [folderName stringByAppendingPathComponent:fileName];;
if ([folderName isEqualToString:#"root"]) {
filePathStr = fileName;
}
DBPath *filePath = [[DBPath root] childPath:filePathStr];
DBError *error;
DBFile *destFile =[[DBFilesystem sharedFilesystem] createFile:filePath error:&error];
NSData *fileData = [NSData dataWithContentsOfFile:file];
[destFile writeData:fileData error:&error];
//[destFile writeContentsOfFile:file shouldSteal:NO error:&error];
[destFile close];
if (error) {
NSLog(#"Error when creating file %# in Dropbox, error description:%#", fileName, error.description);
}
}
}
Your error checking is all wrong. Your code should be more like this:
DBPath *filePath = [[DBPath root] childPath:filePathStr];
DBError *error = nil;
DBFile *destFile =[[DBFilesystem sharedFilesystem] createFile:filePath error:&error];
if (destFile) {
NSData *fileData = [NSData dataWithContentsOfFile:file];
if (![destFile writeData:fileData error:&error]) {
NSLog(#"Error when writing file %# in Dropbox, error description: %#", fileName, error);
}
[destFile close];
} else {
NSLog(#"Error when creating file %# in Dropbox, error description: %#", fileName, error);
}
The file should sync right away with the code that you have. This assumes you have properly linked your app to an account and all.
What version of the Dropbox Sync API are you using? 1.0.7 has some potential networking issues. I have a beta of 1.0.8 that seems to solve these issues. You may need to wait until 1.0.8 comes out.
You can verify if Dropbox is hung. While running your app in the debugger, wait a minute after the file has been created. If the file doesn't appear, pause your app in the debugger and look at all of the threads. You should see one or more dropbox related threads. If one looks blocked with a reference to dbx_cfhttp_request then you have hit a bug in the Dropbox framework. Putting your device in Airplane mode for 10-15 seconds then turning Airplane mode off again should kick it back into gear.