Create a Folder (bundle) in Cocoa - objective-c

I'm trying to programmatically create a folder with Cocoa.
I've written an NSString category and we've got the following function there :
- (void)createAsFolder
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError* err = nil;
[fileManager createDirectoryAtPath:self withIntermediateDirectories:YES attributes:nil error:&err];
if (err)
{
NSLog(#"ERROR : %#",err);
}
}
So, in a few words, let's say we have an NSString* path = #"/some/path/is/here";, we can create it simply by :
[path createAsFolder];
The thing is, although it works PERFECTLY for normal folders, it does NOT when the path specified is a bundle (that is : WITH an extension). E.g.
NSString* path = #"/this/is/a/path/to/some/bundle.bun";
[path createAsFolder];
The above does NOT work.
Any ideas on how to fix that?

OK, here's the answer (thanks to #thundersteele), if you want to copy a full file tree from on place to another :
NSFileWrapper* w = [[NSFileWrapper alloc] initWithPath:initialPath];
[w writeToFile:destinationPath atomically:YES updateFilenames:YES];
And yep : it has ABSOLUTELY no problem whether the subfolders are packages/bundles or whatever. Not that hard, huh? Just 2 lines... lol

Try NSFileWrapper instead. I think it can do what you want to do.
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSFileWrapper_Class/Reference/Reference.html

Related

how to get /Users/username/Downloads path in a sandboxed app?

what I've done is setup the com.apple.security.files.downloads.read-write to true, and have looked up the Apple Sandbox related docs, but I can't figure out how to get the downloads folder path, what I get is still container path like this: /Users/username/Library/Containers/com.errpro.Snell/Data/Downloads
, the method I use is NSSearchPathForDirectoriesInDomains. I've seen someone use getpwent to get the path, but seems not appropriate. Any help would be appreciated.
You should use the method URLForDirectory. This method find the name of the current user and insert it in the URL path.
NSFileManager *fm = [NSFileManager defaultManager];
NSURL *downloadsURL;
downloadsURL = [fm URLForDirectory:NSDownloadsDirectory
inDomain:NSUserDomainMask appropriateForURL:nil
create:YES error:nil];
Swift 5
do {
let url = try FileManager.default.url(for: FileManager.SearchPathDirectory.downloadsDirectory, in: FileManager.SearchPathDomainMask.userDomainMask, appropriateFor: nil, create: true)
} catch{
print(error)
}

moveItemAtPath. No errors, but not working

Im trying to move files. Below I'm testing if the paths exist. They do, however both copyItemAtPath and moveItemAtPath don't seem to work.
NSString *testUrl = #"/Users/justinshulman/Documents/test2";
if ([[NSFileManager defaultManager]fileExistsAtPath:testUrl]) {
NSLog(#"yes");
}
NSString *testUrl2 = #"/Users/justinshulman/Documents/test1";
if ([[NSFileManager defaultManager]fileExistsAtPath:testUrl2]) {
NSLog(#"yes");
}
NSLog(#"%#",testUrl);
NSLog(#"%#",testUrl2);
[[NSFileManager defaultManager]copyItemAtPath:testUrl2 toPath:testUrl error:nil];
[[NSFileManager defaultManager]moveItemAtPath:testUrl2 toPath:testUrl error:nil];
That is exactly your problem, both move and copy will not actually overwrite the destination file if it already exists. You'll have to remove it first and then copy (or move) the other file to that URL.
Try with
[[NSFileManager defaultManager] removeItemAtPath:testUrl error:nil];
[[NSFileManager defaultManager]copyItemAtPath:testUrl2 toPath:testUrl error:nil];
and it should work fine.
You should also be checking for the error instead of passing nil.
NSError* error = nil;
[[NSFileManager defaultManager]copyItemAtPath:testUrl2 toPath:testUrl error:&error];
if (error != nil) {
NSLog(#"%#", [error localizedDescription]);
}
It also returns a bool on whether the copy was successful.
Adding to #micantox answer, always read the class reference. See class reference for NSFileManager:
If a file with the same name already exists at dstPath, this method
aborts the copy attempt and returns an appropriate error.
You should pass NSError object in error filed.
[[NSFileManager defaultManager]copyItemAtPath:testUrl2 toPath:testUrl error:&error];
Error Domain=NSPOSIXErrorDomain Code=17 UserInfo=0x100457e80 "The operation couldn’t be completed.
[[NSFileManager defaultManager]moveItemAtPath:testUrl2 toPath:testUrl error:&error];
Error Domain=NSCocoaErrorDomain Code=512 UserInfo=0x1004a2270
Use replaceItemAtURL:withItemAtURL:backupItemName:options:resultingItemURL:error:
Replaces the contents specified by the first URL with the contents of
the second URL in a manner that insures no data loss occurs.
#justin, First thing it never works only. Because you are trying to copy the source path to destination path where both path are same. Second thing, how NSFileManager copy or move api works is, you have to copy or move source path to different destination path with appending your appropriate path component. For example see the code below:--
NSString *testUrl = #"/Users/home/Documents/source.rtf";
//
if ([[NSFileManager defaultManager]fileExistsAtPath:testUrl]) {
NSLog(#"yes");
}
//Below destination is folder name which should be exist on your machine or else you can create programmatically as well
NSString *testUrl2 = #"/Users/home/Documents/destination";
NSLog(#"%#",testUrl);
NSLog(#"%#",testUrl2);
NSError *err=nil;
//Now we are copying the souce path to destination folder with appending file name (it can be any your name becuase file manager copy source file contents to your destination file contents)
//Here given file name is a destination.rtf where you can give any your name. Also this is for copying source contents to destination contents
NSFileManager *fm=[NSFileManager defaultManager];
if ([fm copyItemAtPath:testUrl toPath:[testUrl2 stringByAppendingPathComponent:#"destination.rtf"] error:&err])
{
NSLog(#"success");
}
else
{
NSLog(#"%#",[err localizedDescription]);
}

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.

Where can a sandboxed Mac app save files?

My Mac app is sandboxed and I need to save a file. Where do I save this file? I can't seem to find the specific place where this is allowed without using an open panel. This is how I do it on iOS:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
What is the equivalent for the sandboxed directory on Mac?
That code snippet works on the Mac regardless of whether your application is sandboxed.
In a non-sandboxed Mac app, path will refer to the Documents folder in your home: e.g. /Users/username/Documents.
In a sandboxed app, it refers to a folder within the app sandbox: e.g. /Users/username/Library/Containers/com.yourcompany.YourApp/Documents
See the docs for details.
Apple's Sandboxing guide is very useful, found here.
You basically have a folder dedicated for your app, as described by theAmateurProgrammer in reply to my question here.
~/Library/Container/com.yourcompany.yourappname/
Here is what I have so far, I will improve it later:
//Create App directory if not exists:
NSFileManager* fileManager = [[NSFileManager alloc] init];
NSString* bundleID = [[NSBundle mainBundle] bundleIdentifier];
NSArray* urlPaths = [fileManager URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask];
NSURL* appDirectory = [[urlPaths objectAtIndex:0] URLByAppendingPathComponent:bundleID isDirectory:YES];
//TODO: handle the error
if (![fileManager fileExistsAtPath:[appDirectory path]]) {
[fileManager createDirectoryAtURL:appDirectory withIntermediateDirectories:NO attributes:nil error:nil];
}
Converting #Mazyod's answer into Swift (5.1):
var appPath: URL? {
//Create App directory if not exists:
let fileManager = FileManager()
let urlPaths = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask)
if let bundleID = Bundle.main.bundleIdentifier, let appDirectory = urlPaths.first?.appendingPathComponent(bundleID,isDirectory: true) {
var objCTrue: ObjCBool = true
let path = appDirectory.path
if !fileManager.fileExists(atPath: path,isDirectory: &objCTrue) {
do {
try fileManager.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil)
} catch {
return nil
}
}
return appDirectory
}
return nil
}
However, the directory has changed and I am not sure that the additonal repetition of the bundle ID is needed as the path is
"/Users/**user name**/Library/Containers/**bundleID**/Data/Library/Application Support/**bundleID**".
But it seems to work.
Is is even easier. For sandboxed apps on macOS the function NSHomeDirectory gives you the path where you have read and write access and can save all your files. It will be a path like this
/Users/username/Library/Containers/com.yourcompany.YourApp

NSFilemanager doesn't create file

I have a problem with NSFileManager, because i only can store a file into Application Documents Directory, but i want to create a file into a sub directory this i don't think why, i couldn't create. my code below:
+(NSString *)applicationDocumentsDirectory {
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}
+(BOOL)storeFile:(NSData*)file withName:(NSString*)name atDirectory:(NSString*)dir{
NSFileManager *filemgr;
NSString *docsDir;
NSString *newDir;
BOOL create=NO;
filemgr =[NSFileManager defaultManager];
docsDir = [StorageManager applicationDocumentsDirectory];
newDir = [docsDir stringByAppendingPathComponent:dir];
if(![filemgr fileExistsAtPath:newDir]){
if([filemgr createDirectoryAtPath:newDir withIntermediateDirectories:NO attributes:nil error:nil]){
create=YES;
}
}else
create=YES;
if(create){
if(![filemgr createFileAtPath:newDir contents:file attributes:nil]){
[filemgr release];
return YES;
}
}
[filemgr release];
return NO;
}
I am not sure why the file is not created. At first glance, it looks like your code should work. But I may be overlooking something. It also depends on what exactly you are passing as arguments to the storeFile:withName:atDirectory: method.
Nevertheless I am posting an answer because I did spot another error in your code: you should not send release to filemgr since you also did not send retain to it first and you did not create the object. In this case, there is no need to send retain to it either, since you are only using it locally within your method. You may want to review the Apple Developer Connection document "Cocoa Core Competencies: Memory Management".
I don't think this error explains why the file is not created; though I'm surprised your application doesn't crash because of it.