Standard temporary directory in OSX - objective-c

I'm trying to figure what is the best place where to store temporary files in the OSX version of my application.
The obvious answer /tmp is not good since it is cleaned up at boot and my application may need to continue an interrupted job also after a restart.
I tried also to use the path pointed by the environment variable TMPDIR, that is the same returned by NSTemporaryDirectory(), that changes every boot and is something like:
/var/folders/wx/p4rqqs8d1ws0wlpx9dkwsh_80000gn/T/
.. but also the contents of this path are removed at boot.
There is a standard path where I can place some temporary files, resilient to restarts, or I have to invent my own solution (ie ~/Library/myapplication/temp)?
In Windows I'm using GetTempPath() and it works the way it should.

I've found my answer googling harder that I did before asking here, in this excellent article:
https://www.cocoawithlove.com/2009/07/temporary-files-and-folders-in-cocoa.html
Reading the articles and the various options I found that the Caches directory (NSCachesDirectory) is the correct place where to store my files. Placing them in "Application Support" will cause them to be backed up by time machine.
So here is what I did:
const char *get_temporary_dir()
{
NSString *path = nil;
NSString *bundleName = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleIdentifier"];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
if ([paths count]) {
path = [[paths objectAtIndex:0] stringByAppendingPathComponent:bundleName];
} else {
path = NSTemporaryDirectory();
path = [path stringByAppendingPathComponent:bundleName];
}
return [path UTF8String];
}
... I'm not sure if the fallback to the standard, deletable, directory is needed but it doesn't hurt!

You should use the "application support directory" - this is typically ~/Library/Application Support or for a sandboxed application and equivalent within its container.
To obtain the URL for this directory you use URLForDirectory:inDomain:appropriateForURL:create:error: passing as first argument NSApplicationSupportDirectory.
Within this directory you need to create a directory just for your application, using your app's bundle ID is a common strategy for naming this directory.
This directory is intended to store files needed by your application, but not your user's files.

Related

Objective C - How to determine if a folder is a package?

App Packages are basically folders and you can read their contents just like any folders. I understood this.
My question is:
If I have a folder called folderA which has files and a package called PackageA which has files, how can I programmatically tell what is a normal folder and what is a package ?
In my application I need to exclude any packages but process any normal folders.
Any advise would be appreciated.
I have tried to check via NSBundle but both normal folder as well as package folder is an NSBundle. I was hoping one is not. I am sure there is a property that will tell me that?
I solved it as:
NSNumber *isPackage;
NSError *error = nil;
[filePath getResourceValue:&isPackage forKey:NSURLIsPackageKey error:&error];
if( [isPackage integerValue] == 1)
{
// treat as package
}
There are multiple ways to do this, the "modern" way is to use NSURL's getResourceValue:forKey:error: method (or one of its siblings). The key for determining whether the NSURL instance references a package is NSURLIsPackageKey.

Obj-C OS X Delete application from /Applications

I am writing an application to remove a piece of software and its files from OS X. I can delete everything else except the .app and a folder in /Applications. I am using AuthetificationExecuteWithPriviledges, I know that it is deprecated, so could that be giving me admin issues when attempting to move the .app to the trash bin? When I step through the process I see an NSCocoaErrorDomain, with code 513. I did a quick search for it, and saw it is related to a permissions error. My other theory is that my path to the application is incorrect. I am using:
NSString *ibmNotesApplication = #"/Applications/IBM Notes.app";
If you are wondering why I am trying to delete Lotus Notes, it is because the Notes uninstaller provided by IBM doesn't have all of the functionality I would like. So, I am writing one that removes notes and all files related to it so we can have a fresh install.
Here is a snippet of code where I move an item in the array to the trash bin.
if ([filemanager fileExistsAtPath:object])
{
NSURL *objectURL = [NSURL fileURLWithPath:object];
NSURL *trashedObject;
NSError *error;
//Moves item to trash bin and update log
if([filemanager trashItemAtURL:objectURL resultingItemURL:&trashedObject error:&error])
{
NSLog(#"Trashed %# to %#",objectURL, trashedObject);
self.textfield.stringValue = [NSString stringWithFormat:#"%#%#%#%#%#%#", self.textfield.stringValue, #"Trashed ", objectURL, #" to ", trashedObject, #"\n"];
}
As you mentioned in the question, the function AuthorizationExecuteWithPrivileges is a deprecated function and has been for quite a while now. I suggest you start by following Apple's advice of factoring out the privileged process into a separate privileged helper tool, which is launched with the help of launchd.
You can follow the example code here.
If the permission problem still exists, then at least you've gone about refactoring your code to the right way of doing things, knowing that it will be supported for future versions of the OS.

Create temp file for a sandboxed Cocoa application

My app is sandboxed (as per the latest App Store guidelines), and I want to create some temporary files.
Am I allowed to do so? If "yes", WHERE am I allowed to do it? Is there any prespecified path? (And a command to access that path?)
You should use the NSTemporaryDirectory() function, which will find and return the appropriate temporary folder for your application (regardless of sandbox status, OS version, and a host of other things). Take a look at this Cocoa With Love post for much more detail about NSTemporaryDirectory() and other temporary directory-related details.
There is a good article about temporary directories on NSHipster:
http://nshipster.com/nstemporarydirectory/
The author suggests this code which is working perfectly with sandboxed apps as well:
NSError *error;
NSString *globallyUniqueString = [[NSProcessInfo processInfo] globallyUniqueString];
NSString *tempDirectoryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:globallyUniqueString];
NSURL *tempDirectoryURL = [NSURL fileURLWithPath:tempDirectoryPath isDirectory:YES];
[[NSFileManager defaultManager] createDirectoryAtURL:tempDirectoryURL withIntermediateDirectories:YES attributes:nil error:&error];

NSSearchPathForDirectoriesInDomains returns the wrong directory

I'm using NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) to get the application documents directory in my app, but the array that's returned contains only one object, and it's an unwritable, incorrect directory:
/Users/me/Library/Application Support/iPhone Simulator/Documents
I found this question which indicates that the problem is related to provisioning, but the answer says little more than that. So I guess I'm asking a duplicate question, but the answer to that question is insufficient, so I'm hoping to get an actual answer in this one.
Hmm ... so one reason that the directory returned might be different that what you'd expect for an app could be related to the Xcode target type. This wouldn't happen to be a testing target would it? in which case the correct answer could well be an answer w/o an application GUID in it, since in fact it's not an application. This google group discussion implies that if this is the case, you'd be good with simply creating the directory.
Just for grins, I created the directory /Users/me/Library/Application Support/iPhone Simulator/Documents from the terminal window, and now it appears to run. There are still test errors, but those might be real.
I'd recommend that you change your test app to create the documents directory if it's missing - something like:
if(![[NSFileManager defaultManager] createDirectoryAtPath:NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) withIntermediateDirectories:YES attributes:nil error:NULL])
NSLog(#"Error: Create folder failed %#", NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES));
This works for me:
[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask] lastObject];
You can try this:
/**
Returns the path to the application's Documents directory.
*/
- (NSString *)applicationDocumentsDirectory {
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}
I believe you are only missing the lastObject message.
I'm having the exact same issue, and in my case it's because I'm running the code from an XCTest class.
My guess is that the tests are not run in the application sandbox.
That's because my tests are related to a static library project, included in the main project.
As soon as I run the exact same code from a run of my app, the paths are returned correctly.
Hope this helps.

Problem writing plist in local vs. admin mode

In my application, I am storing user preferences (which are applicable for all users) into a plist file,
which is attached as a form of bundle.
The problem is, it runs fine in admin mode, but when I run the application, it's not writing the file. Do I need to set some attribute to write to the plist in local mode? Or is it not possible at all?
My code for writing the file is below:
-(void)SavePrefrence:(NSString *)fileName PrefrenceOption:(NSMutableDictionary *)pDict{
NSString *filePath = [[[NSBundle mainBundle] pathForResource:fileName ofType:#"plist"] retain];
NSDictionary *pTemp = [[NSDictionary alloc]initWithDictionary:pDict];
bool bRet = [pTemp writeToFile:filePath atomically:YES];
if(bRet==YES){
NSLog(#"File Saved ");
}
else {
NSLog(#"File not saved ");
}
}
This is the code which calls it:
-(void)SaveListSettings:(NSMutableDictionary *)pListSettings{
[ self SavePrefrence:#“MyList" PrefrenceOption:pListSettings];
if(pListInfo)
[pListInfo release];
[self LoadListProfile];
}
The application bundle will, by default, only be writable by the owner (if it's installed by dragging into the Applications folder, this'll be whoever installed it; if it's installed by a .pkg, it should probably be root). The way you're doing this, if I follow it properly, requires a user to have write access to the app's Contents/Resources folder, which is a really bad idea (not that it's unheard of -- see the University of Utah's documentation about "Poorly-Made Applications" for examples). Actually, saving preferences inside the application is a bad idea anyway; that's what the various Library/Preferences folders are for (~/Library/Preferences for personal settings, /Library/Preferences for system-wide settings. But, of course, /Library/Preferences is only writable by admins (for very good reasons). I don't really know of a good way to handle this, as system-wide settings modifiable by non-admins is not exactly normal.
You could use an installer which asks for an admin password and then create "/Library/Application Support/MyApp" and then either make this world writable, or make a sub-folder inside it which is world-writeable. Now MyApp running under a non-admin account can still write to this shared folder.
If you don't want to make the folder world-writeable then include a helper app to the bundle to do the writing and make the helper setuid root by using an installer which asks for an admin password...
BTW: Both of those options will fail Mac App Store rules. Maybe you can use '/Users/Shared', but I don't know if it is allowed by MAS, and anyway it is far from standard. Which would leave you with storing it on a web server...