Create file with fixed size in Cocoa - objective-c

My MAC app need create file that has fixed size. Example : I want to create file has name : test.txt, fixed size : 1069 bytes. How can i do that? I use below code to write file :
NSError *err;
NSString* arrayText = [writeArray componentsJoinedByString: #"\n"];
[filemgr createFileAtPath:[NSString stringWithFormat:#"%#/test.txt",sd_url] contents:nil attributes:nil];
[arrayText writeToFile:[NSString stringWithFormat:#"%#/test.txt",sd_url] atomically:YES encoding:NSUTF8StringEncoding error:&err];
Thanks

This function will write some junk data to file.
+(BOOL)writeToFile:(NSString *)path withSize:(size_t)bytes;
{
FILE *file = fopen([path UTF8String], "wb");
if(file == NULL)
return NO;
void *data = malloc(bytes); // check for NULL!
if (data==NULL) {
return NO;
}
fwrite(data, 1, bytes, file);
fclose(file);
return YES;
}
If you dont want to use fwrite
+(BOOL)writeToFile:(NSString *)path withSize:(size_t)bytes;
{
void *data = malloc(bytes); // check for NULL!
if (data==NULL) {
return NO;
}
NSData *ldata = [NSData dataWithBytes:data length:bytes];
[ldata writeToFile:path atomically:NO];
return YES;
}

In order for the file to contain a certain number of bytes, you must write that many bytes to the file. There's no way to make a file's size be something other than the number of bytes it contains.
You can use FSAllocateFork (or fcntl with F_PREALLOCATE) to reserve space to write into. You'd use this, for example, if you were implementing your own download logic (if, for some reason, NSURLDownload wasn't good enough) and wanted to (a) make sure enough space is available for the file you're downloading and (b) grab it before something else does.
But, even that doesn't actually change the size of the file, just ensures (if successful) that your writes will not fail for insufficient space.
The only way to truly grow a file is to write to it.
The best way to do that is to use either -[NSData writeToURL:options:error:], which is the easy way if you have the data all ready at once, or NSFileHandle, which will enable you to write the data in chunks rather than having to build it all up in memory first.

Related

Persisting bookmark in core-data

I have an OSX application that is supposed to have a list of files from anywhere in the user's disk.
The first version of the app saves the path to these files in a core-data model.
However, if the file is moved or renamed, the tool loses its purpose and the app can crash.
So I decided to use bookmarks. It seems to be working, but every time I try to recover the data, I get the old path of the files. Why is that? What am I missing?
My core-data entity uses a binary data field to persist the bookmark.
The bookmark itself is done like this:
NSData * bookmark = [filePath bookmarkDataWithOptions:NSURLBookmarkCreationMinimalBookmark
includingResourceValuesForKeys:NULL
relativeToURL:NULL
error:NULL];
And on loading the application, I have a loop to iterate all the table and recover the bookmark like this:
while (object = [rowEnumerator nextObject]) {
NSError * error = noErr;
NSURL * bookmark = [NSURL URLByResolvingBookmarkData:[object fileBookmark]
options:NSURLBookmarkResolutionWithoutUI
relativeToURL:NULL
bookmarkDataIsStale:NO
error:&error];
if (error != noErr)
DDLogCError(#"%#", [error description]);
DDLogCInfo(#"File Path: %#", [bookmark fileReferenceURL]);
}
If I rename the file, the path is null. I see no difference between storing this NSData object and a string with the path. So I am obviously missing something.
Edit:
I also often get an error like this: CFURLSetTemporaryResourcePropertyForKey failed because it was passed this URL which has no scheme.
I appreciate any help, thanks!
I can't find any issues in my code, so I changed it.
After looking for the reason of the "no scheme" message, I came to the conclusion some third-party application is required for this code to work, and that's undesirable.
I am now using aliases. This is how I create them:
FSRef fsFile, fsOriginal;
AliasHandle aliasHandle;
NSString * fileOriginalPath = [[filePath absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
OSStatus status = FSPathMakeRef((unsigned char*)[fileOriginalPath cStringUsingEncoding: NSUTF8StringEncoding], &fsOriginal, NULL);
status = FSPathMakeRef((unsigned char*)[fileOriginalPath cStringUsingEncoding: NSUTF8StringEncoding], &fsFile, NULL);
OSErr err = FSNewAlias(&fsOriginal, &fsFile, &aliasHandle);
NSData * aliasData = [NSData dataWithBytes: *aliasHandle length: GetAliasSize(aliasHandle)];
And now I recover the path like this:
while (object = [rowEnumerator nextObject]) {
NSData * aliasData = [object fileBookmark];
NSUInteger aliasLen = [aliasData length];
if (aliasLen > 0) {
FSRef fsFile, fsOriginal;
AliasHandle aliasHandle;
OSErr err = PtrToHand([aliasData bytes], (Handle*)&aliasHandle, aliasLen);
Boolean changed;
err = FSResolveAlias(&fsOriginal, aliasHandle, &fsFile, &changed);
if (err == noErr) {
char pathC[2*1024];
OSStatus status = FSRefMakePath(&fsFile, (UInt8*) &pathC, sizeof(pathC));
NSAssert(status == 0, #"FSRefMakePath failed");
NSLog(#"%#", [NSString stringWithCString: pathC encoding: NSUTF8StringEncoding]);
} else {
NSLog(#"The file disappeared!");
}
} else {
NSLog(#"CardCollectionUserDefault was zero length");
}
}
However, I am still curious on why my previous code failed. I appreciate any thoughts on that. Thanks!

Parsing a binary MOBI file: best approach?

It contains METADATA in between binary data. I'm able to parse the first line with the title Agent_of_Chang2e, but I need to get the metadata on the bottom of the header as well. I know there are not standard specifics for it.
This code isn't able to decode the bottom lines. For example I get the following wrong formatted text:
FÃHANGE</b1èrX)¯­ÌiadenÕniverse<sup><smalÀ|®¿8</¡Îovelÿ·?=SharonÌeeándÓteveÍiller8PblockquoteßßÚ>TIa÷orkyfiction.Áll#eãacÐ0hðortrayedén{n)áreïrzus0¢°usly.Ôhatíean0authhmxétlõp.7N_\
©ß© 1988âyÓOOKãsòeserved.0ðart)publicaZmayâehproduc
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
char buffer[1024];
FILE* file = fopen([path UTF8String], "r");
if (file != 0)
{
while(fgets(buffer, 1024, file) != NULL)
{
NSString* string = [[NSString alloc] initWithCString: buffer encoding:NSASCIIStringEncoding];
NSLog(#"%#",string);
[string release];
}
fclose(file);
}
[pool drain];
nielsbot already posted a link to the format specification.
As you can read there, the file is not text file, but binary encoded. Parsing it with NSString instances is no good idea.
You have to read the file binary, i. e. using NSData:
NSData content = [NSData dataWithContentsOfFile:path];
Then you have to take out the relevant information by yourself. For example, if you want to read the uncompressed text length, you will find in the linked document that this information starts at position 4 and has a length of 4.
int32_t uncompressedTextLength; // 4 bytes are 32 bit.
[content getBytes:&uncompressedLenght range:NSMakeRange(4, 4)];
Maybe you have to deal with endianess.
Use NSTask or system() to pass the file through the strings utility and parse the output of that:
strings /bin/bash | more
...
...
677778899999999999999999999999999999:::;;<<====>>>>>>>>>>>????????
#(#)PROGRAM:bash PROJECT:bash-92
...
...
First, I am pretty sure the texts will be UTF-8 or UTF-16 encoded.
Second, you cannot just take random 1024 bytes and expect them to work as a text. What about byte order (big endian vs little endian)?

Data corruption when reading realtime H.264 output from AVAssetWriter

I'm using some tricks to try to read the raw output of an AVAssetWriter while it is being written to disk. When I reassemble the individual files by concatenating them, the resulting file is the same exact number of bytes as the AVAssetWriter's output file. However, the reassembled file will not play in QuickTime or be parsed by FFmpeg because there is data corruption. A few bytes here and there have been changed, rendering the resulting file unusable. I assume this is occurring on the EOF boundary of each read, but it isn't consistent corruption.
I plan to eventually use code similar to this to parse out individual H.264 NAL units from the encoder to packetize them and send them over RTP, however if I can't trust the data being read from disk I might have to use another solution.
Is there an explanation/fix for this data corruption? And are there any other resources/links you have found on how to parse the NAL units to packetize over RTP?
Full code here: AVAppleEncoder.m
// Modified from
// http://www.davidhamrick.com/2011/10/13/Monitoring-Files-With-GCD-Being-Edited-With-A-Text-Editor.html
- (void)watchOutputFileHandle
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
int fildes = open([[movieURL path] UTF8String], O_EVTONLY);
source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE,fildes,
DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE | DISPATCH_VNODE_EXTEND | DISPATCH_VNODE_ATTRIB | DISPATCH_VNODE_LINK | DISPATCH_VNODE_RENAME | DISPATCH_VNODE_REVOKE,
queue);
dispatch_source_set_event_handler(source, ^
{
unsigned long flags = dispatch_source_get_data(source);
if(flags & DISPATCH_VNODE_DELETE)
{
dispatch_source_cancel(source);
//[blockSelf watchStyleSheet:path];
}
if(flags & DISPATCH_VNODE_EXTEND)
{
//NSLog(#"File size changed");
NSError *error = nil;
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingFromURL:movieURL error:&error];
if (error) {
[self showError:error];
}
[fileHandle seekToFileOffset:fileOffset];
NSData *newData = [fileHandle readDataToEndOfFile];
if ([newData length] > 0) {
NSLog(#"newData (%lld): %d bytes", fileOffset, [newData length]);
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
NSString *movieName = [NSString stringWithFormat:#"%d.%lld.%d.mp4", fileNumber, fileOffset, [newData length]];
NSString *path = [NSString stringWithFormat:#"%#/%#", basePath, movieName];
[newData writeToFile:path atomically:NO];
fileNumber++;
fileOffset = [fileHandle offsetInFile];
}
}
});
dispatch_source_set_cancel_handler(source, ^(void)
{
close(fildes);
});
dispatch_resume(source);
}
Here are some similar questions I have found, but don't exactly answer my question:
Get PTS from raw H264 mdat generated by iOS AVAssetWriter
streaming video FROM an iPhone
Parsing h.264 NAL units from a quicktime MOV file
Realtime Audio/Video Streaming FROM iPhone to another device (Browser, or iPhone)
When I eventually figure this out, I will release an open source library to assist people who try to do this in the future.
Thank you!
Update: The corruption doesn't happen at the EOF boundary. It seems like parts of the file are re-written after finishWriting is called. This first file was chunked at 4KB, so the area changed isn't anywhere near an EOF boundary. It seems to be corrupted near new "moov" elements as well when movieFragmentInterval is enabled.
Correct file on the left, broken file on the right.
I ended up abandoning the "read while it's written" approach in favor of a manual chunking approach where I call finishWriting every 5 seconds on a background thread. I was able to drop a negligible number of frames using a method originally described here:
- (void) segmentRecording:(NSTimer*)timer {
AVAssetWriter *tempAssetWriter = self.assetWriter;
AVAssetWriterInput *tempAudioEncoder = self.audioEncoder;
AVAssetWriterInput *tempVideoEncoder = self.videoEncoder;
self.assetWriter = queuedAssetWriter;
self.audioEncoder = queuedAudioEncoder;
self.videoEncoder = queuedVideoEncoder;
//NSLog(#"Switching encoders");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[tempAudioEncoder markAsFinished];
[tempVideoEncoder markAsFinished];
if (tempAssetWriter.status == AVAssetWriterStatusWriting) {
if(![tempAssetWriter finishWriting]) {
[self showError:[tempAssetWriter error]];
}
}
if (self.readyToRecordAudio && self.readyToRecordVideo) {
NSError *error = nil;
self.queuedAssetWriter = [[AVAssetWriter alloc] initWithURL:[self newMovieURL] fileType:(NSString *)kUTTypeMPEG4 error:&error];
if (error) {
[self showError:error];
}
self.queuedVideoEncoder = [self setupVideoEncoderWithAssetWriter:self.queuedAssetWriter formatDescription:videoFormatDescription bitsPerSecond:videoBPS];
self.queuedAudioEncoder = [self setupAudioEncoderWithAssetWriter:self.queuedAssetWriter formatDescription:audioFormatDescription bitsPerSecond:audioBPS];
//NSLog(#"Encoder switch finished");
}
});
}
Full source code: https://github.com/chrisballinger/FFmpeg-iOS-Encoder/blob/master/AVSegmentingAppleEncoder.m
When reading a MOV file that is ACTIVELY recording on iOS, you MUST check the 4 bytes mentioned for changes, and re-write this four bytes, then check for additional data in file, and send additional data. Then when done, truncate the file to the file size written.
Obviously this depends on where you are sending the file. I use a send (offset,number of bytes) to receiver. So I send "additional data", "more additional data", ... , new data at (24,4), "more additional data".
Typically iOS only writes the 4 byte (size of data section) record when file is about to be closed (aka after last media write). (see info on "Quicktime atoms"). Unfortunately, this also means the MOV file is not PLAYABLE until recording is completed (and movie descriptors written at END of file).

Storing arbitrary metadata with a plain-text file

I am writing a text-editor and I would need to store a few pieces of information (generally just a few strings; the storage needn't be particularly durable) with each file the app saves (without that being part of the text-file as other apps might read it and the info is only specific to my app).
How would I go about this?
More info: I have a NSDocument set up and I would like to simply store a NSString instance variable as a per file meta-datum. Based on the answers below I've come up with this, which is currently buggy and causes the program to crash on startup:
#import <sys/xattr.h>
#interface MyDocument : NSDocument {
NSString *metadatum;
}
#implementation MyDocument
- (BOOL)writeToURL:(NSURL *)url ofType:(NSString *)type error:(NSError **)err
{
BOOL output = [super writeToURL:url ofType:type error:err];
if(!setxattr([[url path] cStringUsingEncoding:NSUTF8StringEncoding],
"eu.gampleman.xattrs.style",
[metadatum cStringUsingEncoding:NSUTF8StringEncoding],
sizeof(char) * [styleName length], 0, 0))
{
NSLog(#"Write failure");
}
return output;
}
- (BOOL)readFromURL:(NSURL *)url ofType:(NSString *)type error:(NSError **)err {
char *output;
ssize_t bytes = getxattr([[url path] cStringUsingEncoding:NSUTF8StringEncoding],
"eu.gampleman.xattrs.style", &output, 1024, 0, 0);
if (bytes > 0) {
metadatum = [[NSString alloc] initWithBytes:output length:bytes
encoding:NSUTF8StringEncoding]; // <- crashes here with "EXC_BAD_ACCESS"
}
return [super readFromURL:url ofType:type error: err];
}
// ...
// fairly standard -dataOfType:error: and
// -readFromData:ofType:error: implementations
PS: If your answer is really good (with sample code, etc.), I will award a 100rep bounty.
Use extended attributes. See setxattr().
Here's a sample call to write a string:
NSData* encodedString = [theString dataUsingEncoding:NSUTF8StringEncoding];
int rc = setxattr("/path/to/your/file", "com.yourcompany.yourapp.yourattributename", [encodedString bytes], [encodedString length], 0, 0);
if (rc)
/* handle error */;
To read a string:
ssize_t len = getxattr("/path/to/your/file", "com.yourcompany.yourapp.yourattributename", NULL, 0, 0, 0);
if (len < 0)
/* handle error */;
NSMutableData* data = [NSMutableData dataWithLength:len];
len = getxattr("/path/to/your/file", "com.yourcompany.yourapp.yourattributename", [data mutableBytes], len, 0, 0);
NSString* string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
PS: Don't you have to set the bounty on the question before it's answered?
There are several places to store this kind of information on Mac. The most simple, of course, is to store it in your own separate metadata database. Of course there are challenges if the file moves. Since 10.6, however, you can use Bookmarks to address this problem. A Bookmark (see NSURL) allows you to keep a reference to a file even if it is moved (even across application restarts). Prior to 10.6 there was the Alias Manager, but it couldn't create new aliases; just read ones that Finder created.
The next common solution is to create metadata files. So if I have foo.txt, then you create a sibling .foo.txt.metadata to hold the extra info. Several trade-offs there if the files can be moved around.
Next is Spotlight, which can be used to attach arbitrary information to files. The actual tool here is xattr (see the man pages for setxattr and its relatives). These basically absorb several things that used to be done with Resource Forks (except xattr is supposed to just be metadata).

AVAudioPlayer refuses to play modified wav file

The first time i call this method file1 will be nil and file2 will be returned. When this hapens the file will play normally (so the calling of this method should be fine). But when i call it for the second time it will return an NSURL which the AVAudioPlayer does not play. My guess is I have missed something in the header. In the debugging mode i have seen that the totalLength is exactly as long as the data's length.
+(NSURL *)mergeFile1:(NSURL *)file1 withFile2:(NSURL *)file2 {
if(file1 == nil) {
return [file2 copy];
}
NSData * wav1Data = [NSData dataWithContentsOfURL:file1];
NSData * wav2Data = [NSData dataWithContentsOfURL:file2];
int wav1DataSize = [wav1Data length] - 46;
int wav2DataSize = [wav2Data length] - 46;
if (wav1DataSize <= 0 || wav2DataSize <= 0) {
return nil;
}
NSMutableData * soundFileData = [NSMutableData dataWithData:[wav1Data subdataWithRange:NSMakeRange(0, 46)]];
[soundFileData appendData:[wav1Data subdataWithRange:NSMakeRange(46, wav1DataSize)]];
[soundFileData appendData:[wav2Data subdataWithRange:NSMakeRange(46, wav2DataSize)]];
unsigned int totalLength = [soundFileData length];
NSLog(#"Calculated: %d - Real: %d", totalLength, [soundFileData length]);
[soundFileData replaceBytesInRange:NSMakeRange(4, 4)
withBytes:&(UInt32){NSSwapHostIntToLittle(totalLength-8)}];
[soundFileData replaceBytesInRange:NSMakeRange(42, 4)
withBytes:&(UInt32){NSSwapHostIntToLittle(totalLength)}];
[soundFileData writeToURL:file1 atomically:YES];
return [file1 copy];
}
If anyone sees something that can be of help it would be much appreciated!
Any questions will be answered asap.
EDIT
I know there are 2 sorts of wav headers: 44 bytes or 46 bytes. I have tried both.
EDIT
I have looked at the Audio File Services Reference which contains a lot of nice stuff i might want to use, but i can't figure out how to use all this. I'm not really known with c. Hope anyone could help me out with this.
EDIT
An example of a merged wav file is found here: 7--443522512
Looks like your WAV file includes a broken FLLR chunk before the data chunk, or at least VLC thinks the FLLR chunk is over 2GB large so it tries to skip to the next chunk which is beyond the file end.
Maybe you should try to create WAV files without FLLR chunk before merging, the kAudioFileFlags_DontPageAlignAudioData seams to make Audio File Services skip it.
Another option is to extract the data chunks and write a new wav file, a did a proof of concept implementation here: https://gist.github.com/1555889