Xcode 5 [NSString writeToFile] without absolute path - objective-c

I've checked out a few other posts about this topic, but I am still left with some doubt on whether or not [NSString writeToFile] is writing to the relative path.
NSError *error = nil;
BOOL success = [str writeToFile:#"someFile.txt"
atomically:YES
encoding:NSUTF8StringEncoding
error:&error];
NSString *status = success ? #"Success" : #"Failure";
if(success){
NSLog(#"Done Writing: %#",status);
}
else{
NSLog(#"Done Writing: %#",status);
NSLog(#"Error: %#",[error localizedDescription]);
}
writeToFile works when given the path to a certain folder and by NSLogging the error, I can see what kind of error occurs. However, when running the above code, no error occurs and after having done a thorough search, I think I can safely say that a file was never created. What's going on behind the scenes?

Well it's certainly working, which you confirm yourself as your code traps and reports errors very nicely. Your only issue is that you don't know where the file is being written to, and in this case, as no path has been specified it will be to the current working directory, which is a concept in pretty much all operating systems (even Windows!).
I must admit that I don't know what the default current working directory is under iOS, but you can find out yourself with:
NSString *cwd = [[NSFileManager defaultManager] currentDirectoryPath];
NSLog(#"cwd='%#'", cwd);

Related

Why is NSURL's NSURLDocumentIdentifierKey (almost) always nil?

OSX Yosemite introduced a very handy attribute on NSURL: NSURLDocumentIdentifierKey.
Quoting from the documentation:
NSURLDocumentIdentifierKey
The document identifier returned as an NSNumber (read-only).
The document identifier is a value assigned by the kernel to a file or directory. This value is used to identify the document regardless of where it is moved on a volume. The identifier persists across system restarts. It is not transferred when the file is copied, but it survives "safe save” operations. For example, it remains on the path to which it was assigned, even after calling the replaceItemAtURL:withItemAtURL:backupItemName:options:resultingItemURL:error: method. Document identifiers are only unique within a single volume. This property is not supported by all volumes.
Available in OS X v10.10 and iOS 8.0.
Unfortunately, the value seems to be mostly nil (except rare examples that seem completely disconnected one to the other).
In particular, this code will throw an exception at the last line (tested on Yosemite 10.10.3):
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *attributesFlags = #[NSURLNameKey, mNSURLDocumentIdentifierKey];
NSDirectoryEnumerator *en = [fileManager enumeratorAtURL:[NSURL URLWithString:NSHomeDirectory()]
includingPropertiesForKeys:attributesFlags
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:^BOOL(NSURL *url, NSError *error) {
NSAssert(NO, #"An error has occured");
return YES;
}];
for(NSURL *URL in en) {
NSNumber *documentID = nil;
NSError *error = nil;
BOOL result = [URL getResourceValue:&documentID forKey:NSURLDocumentIdentifierKey error:&error]; \
NSAssert(result == YES && error==nil, #"Unable to read property. Error: %#", error); \
NSLog(#"Processing file: %#", URL);
// This will break most of the times
NSAssert(documentID != nil, #"Document ID should not be nil!!");
}
Perhaps I misunderstood the documentation but it seems to me NSURLDocumentIdentifierKey should be available on every file on disk.
I filed a bug with Apple on this issue and got a feedback on my report. As of today, the information on tracking the DocumentIdentifier is not part of the documentation yet, but the ticket is still open.
The missing information is, that the filesystem does not track the DocumentIdentifier by default. You'll have to enable the tracking by setting a flag on each file that you want to track using chflags with the UF_TRACKED flag.
The following script will print the DocumentIdentifier for a file:
https://gist.github.com/cmittendorf/fac92272a941a9cc64d5
And this script will enable tracking the DocumentIdentifier:
https://gist.github.com/cmittendorf/b680d1a03aefa08583d7
Apparently Yosemite assigns a DocumentIdentifier to a file only when it knows something is trying to track its identity (like Versions or iCloud).
I don't see any way to talk to the kernel and tell it to start tracking files you're interested on. I hope this changes in future releases, since the API has been made public on OS X 10.10 and it's mostly useless at this point.
This issue still exists in macOS 10.14. It probably won't change.
The work-around is to get the inode from NSFileManager, like this:
NSFileManager *fmgr = [NSFileManager defaultManager];
NSDictionary *attributes = [fmgr attributesOfItemAtPath:url.path error:nil;
if (attributes != nil) {
NSNumber *inode = [attributes objectForKey:NSFileSystemFileNumber];
...
}

NSFileManager copyItemAtPath complains about a nonexistent file that does exist

I am trying to copy a file using [[NSFileManager defaultManager] copyItemAtPath: toPath: error:] but it is failing with the following error:
4: The file does not exist.
The relevant code is below, and the file does exist and the path string is correct because it is created beforehand with the exact same file path string.
NSFileManager* manager = [NSFileManager defaultManager];
NSError* error;
NSString* fileName = [Sound getFileName:Title];
NSString* oldDirectory = [NSString stringWithFormat:#"%#%#/", [settings stringForKey:#"downloadFolder"], authorFolder];
NSString* oldFile = [oldDirectory stringByAppendingFormat:#"%#.mp3", fileName];
NSString* newFile = [NSString stringWithFormat:#"%#/iTunes/iTunes Media/Automatically Add to iTunes/%#.mp3", [NSSearchPathForDirectoriesInDomains(NSMusicDirectory, NSUserDomainMask, YES) objectAtIndex:0], fileName];
BOOL result = [manager copyItemAtPath:oldFile toPath:newFile error:&error];
if (!result && error)
{
NSLog(oldFile);
NSLog(#"There was an error copying the file to the iTunes directory! %#", [error localizedDescription]);
}
It's not the exact code, but all relevant code should be above. If I use [manager fileExistsAtPath:oldFile] the result is YES.
What could cause the copy to fail and say the file doesn't exist, even if it does?
UPDATE:
Issue fixed. Turns out the output folder was really Automatically Add to iTunes.localized, but I didn't notice this initially when just paging through the finder. Fixing the output path solved the issue! Thanks for the help.
If any of the directories in the path of the destination don't exist, you'll get a similar error to what you'd get if the source doesn't exist. Check what [manager fileExistsAtPath:[newFile stringByDeletingLastPathComponent] isDirectory:&isDir] returns.
You're using the API wrong. You need to look at the return value of -copyItemAtPath:toPath:error:. Only if that returns NO does that mean an error occurred.
If you're using ARC, your error variable should be nil if no error occurred (although this isn't technically guaranteed), but if you're using MRR it probably won't, because you never initialized it.

NSSavePanel is not saving a file after sandboxing an app

I'm having a problem saving a string file with NSSavePanel after sandboxing the app for the Mac App Store. I set com.apple.security.files.user-selected.read-write to YES and the NSOpenPanel is working as it should.
When I try to save a new file, though, it seems that everything is working fine but then there is no saved file where it should be....
This is the code I am using to save the file:
NSSavePanel *save = [NSSavePanel savePanel];
long int result = [save runModal];
if (result == NSOKButton)
{
NSString *selectedFile = [save filename];
NSString *fileName = [[NSString alloc] initWithFormat:#"%#.dat", selectedFile];
NSString *arrayCompleto = [[NSString alloc]initWithFormat:#"bla bla bla"];
[arrayCompleto writeToFile:fileName
atomically:NO
encoding:NSUTF8StringEncoding
error:nil];
}
First of all, the -[NSSavePanel filename] selector has been deprecated. Use -[NSSavePanel URL] instead. Second, the way that the -[NSString writeToFile:atomically:encoding:error] tells you what you're doing wrong is with the error:(NSError**) argument.
You should also handle errors for file I/O in particular, because even if your code is 100% correct, there still might be errors on the user's system (insufficient privileges, etc.) and presenting the error to the user will allow them to see it failed (and have some idea why). Handling the error in code will also allow your app to recover. For instance, if you tried to read in the file below the code you pasted (after writing it to disk), but the user tried writing it to a network share they didn't have access to, your app might crash. If you know the write failed, you can proceed accordingly (perhaps prompting for a different save location).
In this case, though, I believe the following line is your problem:
NSString *fileName = [[NSString alloc] initWithFormat:#"%#.dat", selectedFile];
When your app is sandboxed, the user needs to give you permission for either a specific file or a specific directory through the open/save panels to bring them into your sandbox. What you're doing is taking the file the user gave you permission to write and saying "that's great, but I want to save a different file", which violates the sandbox. What you should do instead is set the extension in the Save Panel. The complete fixed solution would be:
NSSavePanel *save = [NSSavePanel savePanel];
[save setAllowedFileTypes:[NSArray arrayWithObject:#"dat"]];
[save setAllowsOtherFileTypes:NO];
NSInteger result = [save runModal];
if (result == NSOKButton)
{
NSString *selectedFile = [[save URL] path];
NSString *arrayCompleto = #"bla bla bla";
NSError *error = nil;
[arrayCompleto writeToFile:selectedFile
atomically:NO
encoding:NSUTF8StringEncoding
error:&error];
}
if (error) {
// This is one way to handle the error, as an example
[NSApp presentError:error];
}
If in the future something else is wrong, you can check the value of error at runtime. While debugging, set a breakpoint inside the if (error) statement to check error object's value (do a po error in Xcode's debugger). That should help you figure out what's wrong.

Xcode IOS5 retrieve filenames

I wish to retrieve all filenames from the root directory with the extension *.gs and store them in an array.
I tried using directoryContentsAtPath.. but it says that this method has been deprecated in ios5. Do you know any alternatives?
You should use NSFileManager's:
– contentsOfDirectoryAtPath:error:
(See Apple's documentation on NSFileManager.)
You will end up with something like:
NSString *path = #"your/path";
NSError *error = nil;
NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error];
If you feel you don't need to check for a potential error, you may pass nil for the error argument. However, I would recommend that you check whether an error occurred and display an appropriate error message in that case. You could do it like so:
if (error) {
// display some error message here
} else {
// process filenames returned by NSFileManager
}

Zipping a folder in Objective C

Are there any libraries that work in Objective C for zipping entire folders (and decompressing them)? I have looked at some of them by searching but they look like they require adding files individually and some of them supposedly crash...
It looks like this library might work:
http://bitbucket.org/dchest/osxzip/overview
I don't know if it supports folders, however. Anyone know if it does or have any other libraries that support zipping folders? Even sample code for interacting with the command line libz would be fine with me...
You could use NSTask to run the command line ditto program. Be sure to look at the ditto man page for the right combination of flags to get Finder-compatible zipping.
According to this example: http://www.raywenderlich.com/1948/how-integrate-itunes-file-sharing-with-your-ios-app you can get a NSData Object with the Zipped Data and then just write it with [data writeToFile....]
- (NSData *)exportToNSData {
NSError *error;
NSURL *url = [NSURL fileURLWithPath:_docPath];
NSFileWrapper *dirWrapper = [[[NSFileWrapper alloc] initWithURL:url options:0 error:&error] autorelease];
if (dirWrapper == nil) {
NSLog(#"Error creating directory wrapper: %#", error.localizedDescription);
return nil;
}
NSData *dirData = [dirWrapper serializedRepresentation];
NSData *gzData = [dirData gzipDeflate];
return gzData;
}