I have a 250Mb encrypted PDF file in my X-Code project, which I need to decrypt and display during run time. Since the file size is large, I can't decrypt it. So I split the original file into chunks of NSData, encrypted them into different part files. In the code, I am decrypting these multiple chunks and writing them into the same file and then displaying it.
NSData *decryptedData = [[NSData alloc] init];
NSString *thepath = [NSString stringWithFormat:#"%#/decryptedfile.%#",[paths objectAtIndex:0], fileType];
[decryptedData writeToFile:thepath atomically:YES];
[decryptedData release];
NSFileHandle *myFile = [NSFileHandle fileHandleForUpdatingAtPath:thepath];
[myFile truncateFileAtOffset:0];
for (int k=1; k<50; k++) { //i had 49 parts, hence the condition
NSString *partFilePath = [NSString stringWithFormat:#"%#/%#-part%d.pdf",[paths objectAtIndex:0], [fileName objectAtIndex:i], k];
NSData *tempDData = [[NSData alloc] initWithContentsOfFile:partFilePath];
[myFile writeData:[tempDData AES256DecryptWithKey:#"secretkey"]];
[tempDData release];
}
[myFile closeFile];
I had 49 parts, and am decrypting them each, writing them into a single file. This program runs fine in the simulator and am able to recover my original PDF. But on the device, my app gets terminated.
Its getting terminated when the for loop iterates for the 31st time and when am trying write my decrypted data into the Documents folder of the app. In other words, when i try to add data more than 150MB, my app is terminated. Is there any other way i can implement this feature?
Try calling
[myFile synchronizeFile];
at every iteration; this flushes the buffer. If this doesn't work, I guess you need to go below Foundation and need to directly use unbuffered io at the BSD level.
Related
I have a "Write" button that takes whatever in a Textfild, and appends to a designated file. I also have a "Read" button that reads from the content of the designated file, and displays it on the screen. The "Write" button calls writeDataToFile:(id)sender, and the "Read" button calls readDataFromFile:(id)sender.
The problem is that the following simple code works fine on iPhone 6.1 Simulator (Xcode 4.6.2), but does not display anything in a read device, e.g., iPod with iOS 6.1.3.
I suspect the problem is something related to encoding:NSUTF8StringEncoding. However, I tried NSUTF16StringEncoding and NSUTF32StringEncoding, and it still didn’t work on the real device (but disply scrambled words on Simulator).
Where did I do wrong? I would appreciate very much if somebody can give me some pointers.
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [documentPaths objectAtIndex:0];
_fileName = [documentsDirectory stringByAppendingPathComponent:#"data.txt"];
}
- (IBAction)writeDataToFile:(id)sender {
NSString *str = tfData.text;
str = [str stringByAppendingFormat:#"\n"];
_fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:_fileName];
[_fileHandle seekToEndOfFile];
//convert NSString to NSData
[_fileHandle writeData: [str dataUsingEncoding:NSUTF8StringEncoding]];
[_fileHandle closeFile];
[tfData resignFirstResponder];
}
- (IBAction)readDataFromFile:(id)sender {
[tfData resignFirstResponder];
//lbOut is a UILabel
lbOut.text = [NSString stringWithContentsOfFile:_fileName encoding:NSUTF8StringEncoding error:NULL];
}
Try to diagnose whether the file exists when you try to read from it. You can do this with NSFileManager. Also check whether the file has the right size (or at least a non-zero size).
Also, have you checked whether stringWithContentsOfFile actually returns a string? If an error occurs it will return nil and not raise an exception. If you get nil you can pass an NSError object to stringWithContentsOfFile and it will let you know what went wrong.
Start by checking your return values. If I had to make a guess I would say that the file at _fileName does not exist. If the file doesn't exist fileHandleForUpdatingAtPath: will return nil. And each subsequent call on the fileHandle will fail silently.
So you should check if fileHandleForUpdatingAtPath: actually returns a fileHandle. If it doesn't, create a fileHandle by using fileHandleForWritingAtPath:.
In a similar way you should check if the conversion to NSData has failed. I used NSParameterAssert, which will crash your app if stringData is nil, because I think there is no way to recover from a failed NSString conversion. If you think there is a way to recover from this error replace the NSParameterAssert with a if (!stringData) statement.
_fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:_fileName];
if (!_fileHandle) {
NSLog(#"File does not exist at path \"%#\". Create it.", _fileName);
_fileHandle = [NSFileHandle fileHandleForWritingAtPath:_fileName];
}
// should have a valid fileHandle here
[_fileHandle seekToEndOfFile];
NSData *stringData = [str dataUsingEncoding:NSUTF8StringEncoding];
NSParameterAssert(stringData); // will raise an exception if stringData is nil
[_fileHandle writeData:stringData];
I found the problem. The problem is with the binding of _fileHandle. I declare this public variable, and binds it with a NSFileHandle associated with the file I intended to write using
[NSFileHandle fileHandleForWritingAtPath:_fileName]. I intended to keep using the _fileHandle from different ViewControllers without the need to bind it with the file every time I use it. It resulted the problem I described (i.e., works fine on Simulator, but not on real device).
Once I re-bind _fileHandle with NSFileHandle initialization every time I use it, it works fine now.
Due to cryptography export regulations, it is possible to use this library? or which one could I use to compress/decompress files?
I don't know if SSZipArchive is allowed to use in distributed apps, but the library I am using is Objective-Zip.
It can be easily integrated into any project.
Sample code for zipping:
// create a zip file for writing
ZipFile *zipFile= [[ZipFile alloc] initWithFileName:pathOfTheFileToBeZipped mode:ZipFileModeCreate];
// Add a file, write to its stream and close it
ZipWriteStream *stream1= [zipFile writeFileInZipWithName:#"abc.txt" fileDate:[NSDate dateWithTimeIntervalSinceNow:-86400.0] compressionLevel:ZipCompressionLevelBest];
NSString *text= #"abc";
[stream1 writeData:[text dataUsingEncoding:NSUTF8StringEncoding]];
[stream1 finishedWriting];
// Add another file, write to its stream and close it
ZipWriteStream *stream2= [zipFile writeFileInZipWithName:#"x/y/z/xyz.txt" compressionLevel:ZipCompressionLevelNone];
NSString *text2= #"XYZ";
[stream2 writeData:[text2 dataUsingEncoding:NSUTF8StringEncoding]];
[stream2 finishedWriting];
// Close the zip file
[zipFile close];
Sample code for unzipping:
// open the zip file for reading
ZipFile *unzipFile = [[ZipFile alloc] initWithFileName:pathOfTheFileToBeUnzipped mode:ZipFileModeUnzip];
// retrieve the info of the files inside
NSArray *infos= [unzipFile listFileInZipInfos];
// iterate over files
for (FileInZipInfo *info in infos) {
// locate the file in the zip
[unzipFile locateFileInZip:info.name];
// expand the file in memory
ZipReadStream *read= [unzipFile readCurrentFileInZip];
NSData *data = [read readDataOfLength:info.length];
[read finishedReading];
// construct the folder/file path structure
NSString *unzipPathFilename = [unzipPath stringByAppendingPathComponent:info.name];
NSString *unzipPathFoldername = [[unzipPathFilename stringByDeletingLastPathComponent] copy];
NSError *errorw;
// write the unzipped files, with some consistency checks
NSRange range = [unzipPathFoldername rangeOfString:#"__MACOSX"];
if (range.location == NSNotFound) {
if ([fileManager createDirectoryAtPath:unzipPathFoldername withIntermediateDirectories:YES attributes:nil error:&errorw]) {
if (![[unzipPathFilename pathExtension] isEqualToString:#""] && ![[[unzipPathFilename lastPathComponent] substringToIndex:1] isEqualToString:#"." ]) {
[data writeToFile:unzipPathFilename atomically:NO];
}
}
else {
NSLog(#"Directory Fail: %#", errorw);
}
}
}
// close the zip file
[unzipFile close];
Actually you are allowed to have encryption in iOS application.
You just have to submit application to NSA who you are and what kind of encryption do have in app.
Respond with your reg number usually comes in 30 minutes.
It is automatic or semi-automatic.
They just collect information about developers.
It is simpler then register as iOS developer.
My opinion:-
If you do not use encryption in ZIP library then you should submit any application.
Linker will remove that code after optimization.
That code is not used. But if you use encryption even that comes with iOS then you should apply.
e.g. UIWebView if it opens https:// URLs (e.g. Facebook) but if you use UIWebView to open non secure pages then you should not apply.
I'm looking for suggestions/implementations of how to create a comma separated log file on an iPhone when my data comes at a relatively fast rate (200 times per second). I expect to capture the timestamp and 2-3 integer numbers for each data point. Over a 15 minute period, there would be 15*60*200 = 180,000 rows of data, each one having a timestamp, a few integers and a newline character.
I want to make sure that writing of this data to disk happens in the correct sequential order.
My current implementation has been optimized for data coming in at 1 data point per second, and may not be very efficient for "fast" data. How can I tweak my code to make sure that it can run in a background thread without taking too much resources for each write? Alternatively, is there a fast "log to data file" implementation out there that I can just give numbers to and ask it for a log file at a later point?
NSString *appDataFile ;
NSFileHandle *aFileHandle;
-(NSString*)dataFilePath
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
appDataFile = [documentsDirectory stringByAppendingPathComponent:#"Data"];
aFileHandle = [NSFileHandle fileHandleForWritingAtPath:appDataFile];
return appDataFile;
}
//creates a new log file for each app run, or appends to existing log
-(void)writeStringToDataFile:(NSString *)csvLine
{
if(aFileHandle)
{
//telling aFilehandle what file write to
[aFileHandle truncateFileAtOffset:[aFileHandle seekToEndOfFile]]; //setting aFileHandle to write at the end of the file
[aFileHandle writeData:[csvLine dataUsingEncoding:NSUTF8StringEncoding]];
}else{
NSData* headers = [#"timestamp,waveform amplitude,score, connection, event\n" dataUsingEncoding:NSUTF8StringEncoding];
//clear the old log file
NSError *error = nil;
NSFileManager* fileManager = [NSFileManager defaultManager];
[fileManager removeItemAtPath:[self dataFilePath] error:&error];
//create CSV headers
aFileHandle = [NSFileHandle fileHandleForWritingAtPath:appDataFile];
[headers writeToFile:appDataFile atomically:YES];
aFileHandle = [NSFileHandle fileHandleForWritingAtPath:appDataFile];
//telling aFilehandle what file write to
[aFileHandle truncateFileAtOffset:[aFileHandle seekToEndOfFile]]; //setting aFileHandle to write at the end of the file
[aFileHandle writeData:[csvLine dataUsingEncoding:NSUTF8StringEncoding]];
}
}
One obvious change is to remove the needless call to truncate the file for every line you want to write. Just move the handle to the end once when you open the file.
The next round of changes would be to use C code instead of Objective-C. Use fopen to open the file. Use fputs to write a C-string.
Another place to check is how you build the csvLine. Using stringWithFormat is slow. Get low level and write each separate value as a C-string using fputs. No need for formatting. No need to convert to NSData.
Also take a look at how you do the timestamp. If you convert an NSDate to an NSString you will waste a lot of time.
Here's the big suggestion - If time is so critical, build a big mutable string in memory and then write the whole thing to a file in one shot when done. This will cut out file I/O during the time critical phase.
Make use of Instruments to find where the true bottlenecks are. Without it you could optimize the wrong code.
I am having the problem of writing large file (>200 MB) in iOS device (iPad) but in the simulator it works perfect.
I am using NSFileManager to create file and NSFileData to write file.
I think there is no problem in my code as it runs fine in the simulator.
Does anyone having the same problem?
To elaborate my situation:
I am saving chunk of files (3MB each) in my device which works fine. That means for a 300 MB file I have 100 chunks. Now, from the 100 chunks I want to create the actual file. So I am using the NSFileManager to create file in first iteration and then using NSFileData to write the 3MB data at the end of the file. While running the program it crashes after 61 chunks. I am guessing there might have some memory related issues in the iPad.
I am saving the chunk of files in fileDir in the format data-0, data-1, data-2...
I am applying the decrypt operation on data but for simplicity I have removed that portion.
// List of chunk files
NSArray *filelist= [[NSFileManager defaultManager] contentsOfDirectoryAtPath:fileDir error:err];
for(int i = 0; i < [filelist count]; i++) {
// Read the chunk of file
fileName = [[NSString alloc] initWithFormat:#"data-%d", i];
filePath = [fileDir stringByAppendingPathComponent:fileName];
fileReadHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
// Write in tempFile
if(offset == 0){
if([[NSFileManager defaultManager] createFileAtPath:tempFile contents:data attributes:nil]){
fileWriteHandle = [NSFileHandle fileHandleForWritingAtPath:tempFile];
NSLog(#"File was created!");
} else {
NSLog(#"File was not created.");
}
} else {
[fileWriteHandle seekToEndOfFile]; // Tried with comment out this line but same problem
// Write the decrypted data from chunk
[fileWriteHandle writeData:[[fileReadHandle readDataToEndOfFile] decryptedAES256DataUsingKey:AESEncryptionKey error:err]];
}
}
Edit (11.02.2013)
I tried with my previous code where I omitted the data decryption part.
Interestingly, the problem was in the decryption part I guess cause without the decryption it works fine. I have added the decryption code. For decryption I am using NSData+CommonCrypto library (it's non ARC) but my project is in ARC.
It could be an operating system issue because the NSFileHandle is never being closed for each chunk. I would recommend closing it.
Also, it looks like you have your variables declared outside the scope of the for loop. Unless you need those variables outside the loop, it's generally good to keep the scope of your variables as small as possible, especially if you are using ARC and are trying to think about when memory will be released.
If you think that the NSFileHandle is holding onto data in memory, try to use the -synchronizeFile method after writing each chunk to make sure in memory changes are reflected to disk.
Also, I moved the creation of the file you are writing to outside the loop, because it's easier to follow for me.
Try this adjustment:
// List of chunk files
NSArray *filelist= [[NSFileManager defaultManager] contentsOfDirectoryAtPath:fileDir error:err];
if([[NSFileManager defaultManager] createFileAtPath:tempFile contents:[NSData data] attributes:nil]){
NSLog(#"File was created!");
} else {
NSLog(#"File was not created.");
}
NSFileHandle *fileWriteHandle = [NSFileHandle fileHandleForWritingAtPath:tempFile];
for(int i = 0; i < [filelist count]; i++) {
// Read the chunk of file
NSString *fileName = [NSString stringWithFormat:#"data-%d", i];
NSString *filePath = [fileDir stringByAppendingPathComponent:fileName];
NSFileHandle *fileReadHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
NSData *data = [fileReadHandle readDataToEndOfFile];
// No longer using the file
[fileReadHandle closeFile];
// Write in tempFile
[fileWriteHandle writeData:data];
[fileWriteHandle synchronizeFile];// Flush any data in memory to disk
}
[fileWriteHandle closeFile];
Modifying the following code worked like a magic,
#autoreleasepool {
[fileWriteHandle writeData:[[fileReadHandle readDataToEndOfFile] decryptedAES256DataUsingKey:AESEncryptionKey error:err]];
}
I am looking for the library for splitting (divide) a binary file into multiple files.
If there is 20MB size of file named "test.m4v" in iOS temporary folder (NSTemporaryDirectory()),
I would like to split that to
test.m4v.000 (7MB)
test.m4v.001 (7MB)
test.m4v.002 (6MB)
Something like that (It doesn't have to be '7MB', could be 5MB like that)
like command line split command., I don't think we can call this command inside iOS app.
Is there iOS (free/paid) library to do that? I might need to just low level access and write it, but I am too lazy to do that ;)
This should work assuming the file isn't so large that it freaks out at dataWithContentsOfFile:filename. iOS might do caching in the background, but I don't know.
-(NSUInteger)splitFile:(NSString *)filename chunkSize:(NSUInteger)chunkSize {
NSUInteger chunksWritten;
NSFileManager *fm = [[[NSFileManager alloc] init] autorelease];
NSData *fileData = [NSData dataWithContentsOfFile:filename];
NSString *newFileName;
NSRange dataRange;
for (chunksWritten = 0; chunksWritten * chunkSize < [fileData length]; chunksWritten++) {
newFileName = [filename stringByAppendingPathExtension:[NSString stringWithFormat:#"%03d", chunksWritten]];
dataRange = NSMakeRange(chunksWritten * chunkSize, MIN(chunkSize, [fileData length] - chunksWritten * chunkSize));
if (![fm createFileAtPath:newFileName contents:[fileData subdataWithRange:dataRange] attributes:nil]) {
NSLog(#"Error writing chunk #%d", chunksWritten);
break;
}
}
return chunksWritten;
}
The error checking obviously needs to be more robust.