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

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)
}

Related

Why can't NSFIleManager -fileExistsAtPath not find an existing file when path is correct?

I know that the iOS Simulator is found in a different directory each time it is run; with that in mind, I have this code which gives me the directory of the Core Data sqlite files:
// find current directory for saori.sqlite
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *documentDirectory = [[fileManager URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask]firstObject];
NSString *sqliteFilePath = [[documentDirectory URLByAppendingPathComponent:#"Application Support/SalonBook/saori.sqlite"] absoluteString];
if ([fileManager fileExistsAtPath:sqliteFilePath])
[MagicalRecord cleanUp]; // set stack, etc to 'nil'
else {
NSLog(#"\n\n-->sqlite files not found"); // log message "unable to find sqlite files
return;
}
This is the printout of the sqliteFilePath object:
Printing description of sqliteFilePath:
file:///Users/rolfmarsh/Library/Developer/CoreSimulator/Devices/1EE69744-255A-45CD-88F1-63FEAD117B32/data/Containers/Data/Application/C8FF20F0-41E4-4F26-AB06-1F29936C2208/Library/Application%20Support/SalonBook/saori.sqlite
And this is the image of the file from Finder:
The problem is: I go to the sqliteFilePath and the saori.sqlite file is indeed there! Why is -fileExistsAtPath failing?
Because it is still a URL. A file path doesn't have a protocol, so the prefix of your path file:/// is invalid and can't be resolved. Since an invalid path doesn't contain any files, fileExistsAtPath: returns NO.
Not to worry though, instead of calling absoluteString on the URL object, you can just call path instead and it will return the path.

NSURL path not working, but NSString path working

I saved an object to file and I am now trying to run a check on whether or not that file exists. I have confirmed the path of the file and concluded that the IF statement works when I hard code the path as a NSString, see first block. However, when I try saving the path as a NSURL, and convert it to an NSString so that fileManager can run it's method on it, it does not locate the file at the path. Anything that I am missing here?
LOCATES FILE HERE USING HARD CODE NSSTRING:
[NSKeyedArchiver archiveRootObject:employees toFile:#"/Users/xxx/Documents/employees.plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *employeesPath = #"/Users/xxx/Documents/employees.plist";
if ([fileManager fileExistsAtPath:employeesPath]) {
NSLog(#"It exists! yes!");
}
else {
NSLog(#"Doesn't exist, sorry bud");
}
DOES NOT LOCATE FILE USING NSURL:
[NSKeyedArchiver archiveRootObject:employees toFile:#"/Users/xxx/Documents/employees.plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *employeesPath = [NSURL fileURLWithPath:#"/Users/xxx/Documents/employees.plist"];
NSString *employeesString = [employeesPath absoluteString];
if ([fileManager fileExistsAtPath:employeesString]) {
NSLog(#"It exists! yes!");
}
else {
NSLog(#"Doesn't exist, sorry bud");
}
EDIT
-- if I wanted to use the NSURL method, I could by making a function to store the path into a NSString the proper way. This ended up working:
NSString* getPropertyListPath() {
NSURL *documentDir = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
NSURL *plist = [documentDir URLByAppendingPathComponent:#"employees.plist"];
return plist.path;
}
You are correct to use NSURL, giuseppe, instead of string literals.
NSURL is more compatible and robust than a string literal. NSURL types will give you access to more methods and functionality.
The trick that you stumbled into is that you needed the file path without the "scheme" or "domain" included in the format.
You are correct to call the path method on your NSURL object to retrieve the correct path format for what you need. The path method only returns the path component to the NSURL address path. It doesn't return the scheme or domain components.
NSString *correctPathFormat = [yourNsurlObject path];
For Example:
If I have a file in the following directory path:
NSString* myDirPath = #"/Users/yourUserName/imageFolder";
and load this into a NSURL object:
NSURL *nsurlDirPath = [NSURL fileURLWithPath:myDirPath];
then append the file name and file type:
NSURL *nsurlFilePath = [nsurlDirPath URLByAppendingPathComponent:#"employee.plist"];
If you call the [nsurlFilePath absoluteString] method you will get an NSString value in the format of "scheme://domain/path"
NSString *retrievePath = [nsurlFilePath absoluteString];
NSLog(#"%#",retrievePath);
This logs out:
file:///Users/yourUserName/imageFolder/employee.plist
Special Note: This is the equivalent to the file path:
file://localhost/Users/yourUserName/imageFolder/employee.plist
The "localhost" is just omitted because this is by default implied, so that is why you see the tripple "///" in "file:///Users/...".
"localhost" is an alias that refers to the local device's ip address, or in other words, the device the code is running on.
Finally, to get the correct path format you need you would run the 'path' method on the NSURL object, which takes us back to the answer at the very beginning of my response:
NSString *correctPathFormat = [nsurlFilePath path];
This logs out the correct "path" component, minus the "scheme" & "domain":
/Users/yourUserName/imageFolder/employee.plist
Further Explanation:
NSURLS have three parts:
scheme : [http, https, ftp, file]
domain : [www.stackoverflow.com, localhost, ipAddress]
path : [/questions/26663573/, /Users/youUserName/subDirName]
scheme | domain | path
file://localhost/Users/youruserName/file.txt
Don't use a NSURL as a file path intermediary.
NSURL *employeesPath = [NSURL fileURLWithPath:#"/Users/xxx/Documents/employees.plist"];
NSString *employeesString = [employeesPath absoluteString];
NSLog(#"employeesString: %#", 'employeesString');
Output:
employeesString: 'file:///Users/xxx/Documents/employees.plist'
Which is clearly not a file path.

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

Create a Folder (bundle) in Cocoa

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