i have an application on Xcode 4 for osx. In my program i have some places where i need to read and write to a plist file. currently i have used a file path of /users/my name/desktop/name of document plist. however naturally when i make the application into a app and transfer it to a different computer it is not able to find and read the files. what should i make the file path so that it works on any computer. below is my file path that i have
filepath = #"/Users/Gautambir/Desktop/CustomerNames.plist"
You should never, ever, hard-code paths. You should construct your paths using the various APIs available to do so.
There are several ways to construct valid paths. For instance, this works:
NSString* filePath = [[NSHomeDirectory() stringByAppendingPathComponent:#"Desktop"] stringByAppendingPathComponent:#"CustomerNames.plist"];
Alternatively, you could use:
NSString* filePath = [#"~/Desktop/CustomerNames.plist" stringByExpandingTildeInPath];
Although these are correct ways to build a path, to access special locations such as the Desktop, the Documents folder or the Application Support folder, you should use the NSSearchPathForDirectoriesInDomains() function or, preferably, the NSFileManager methods URLsForDirectory:inDomains: or URLForDirectory:inDomain:appropriateForURL:create:error:.
These URL-based method should always be preferred over their path-based equivalents. Apple recommends all developers move to support the URL-based methods as soon as practicable.
This is primarily because file URLs can store bookmark data, so that if a file moves the URL can still be resolved, which is not the case with paths as paths are just strings and can't store metadata.
Here's how you'd use the file manager to find your URL:
NSFileManager* fm = [NSFileManager defaultManager];
NSURL* desktopURL = [fm URLForDirectory:NSDesktopDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
shouldCreate:NO
error:nil];
NSURL* fileURL = [desktopURL URLByAppendingPathComponent:#"CustomerNames.plist"];
Related
I've been working with the iOS LoadPresetDemo sample code - if loads AUPreset files to configure different types of samplers (pretty cool) - and have run into a question/issue.
The demo code runs fine but when I try to reuse the .aupreset files in test project built from scratch, the Trombone.aupreset doesn't work. Digging into it, I noticed what seems like oddness with the audio sample paths in the .aupreset file.
The paths in the plist (images below) point to:
file://localhost//Library/Audio/Sounds/Tbone/1a%23.caf
but that is not the correct path - according to the project directory structure. There are no "Library/Audio" directories - virtual or real. So I'm confused. The Apple demo works fine but my from scratch project does not (get error -43 when trying to load the samples). The code that loads the samples (at bottom) is not doing anything to relativize the paths at runtime.
Does anyone see what I am misunderstanding here? - Thanks!
// Load a synthesizer preset file and apply it to the Sampler unit
- (OSStatus) loadSynthFromPresetURL: (NSURL *) presetURL {
CFDataRef propertyResourceData = 0;
Boolean status;
SInt32 errorCode = 0;
OSStatus result = noErr;
// Read from the URL and convert into a CFData chunk
status = CFURLCreateDataAndPropertiesFromResource (
kCFAllocatorDefault,
(__bridge CFURLRef) presetURL,
&propertyResourceData,
NULL,
NULL,
&errorCode
);
NSAssert (status == YES && propertyResourceData != 0, #"Unable to create data and properties from a preset. Error code: %d '%.4s'", (int) errorCode, (const char *)&errorCode);
// Convert the data object into a property list
CFPropertyListRef presetPropertyList = 0;
CFPropertyListFormat dataFormat = 0;
CFErrorRef errorRef = 0;
presetPropertyList = CFPropertyListCreateWithData (
kCFAllocatorDefault,
propertyResourceData,
kCFPropertyListImmutable,
&dataFormat,
&errorRef
);
// Set the class info property for the Sampler unit using the property list as the value.
if (presetPropertyList != 0) {
result = AudioUnitSetProperty(
self.samplerUnit,
kAudioUnitProperty_ClassInfo,
kAudioUnitScope_Global,
0,
&presetPropertyList,
sizeof(CFPropertyListRef)
);
CFRelease(presetPropertyList);
}
if (errorRef) CFRelease(errorRef);
CFRelease (propertyResourceData);
return result;
}
I had the same Problem and after 2 hours of comparing the "load preset"-Demo with my code I found the solution:
When adding the sound-folder check the options:
Copy items
Create folder references for any added folders -- and the added folders will be blue like in the "load preset"-demo-project!
The following is from Apple Technical Note TN2283 and discusses the paths issue that was initially asked about. You will obviously need to have the Sounds Folder, your assets and preset file part of your bundle.
Technical Note TN2283 AUSampler - Loading Instruments Excerpt
When the AUSampler attempts to load audio files via the paths provided in the external file refs portion of an .aupreset file or a set of individual file URLs, it will use the following rules to resolve each path:
If the audio file is found at the original path, it is loaded.
If the audio file is NOT found, the AUSampler looks to see if a path includes a portion matching "/Sounds/", "/Sampler Files/" or "/Apple Loops/" in that order.
If the path DOES NOT include one of the listed sub-paths, an error is returned.
If the path DOES include one of the listed sub-paths, the portion of the path preceding the sub-path is removed and the following directory location constants are substituted in the following order:
Bundle Directory
NSLibraryDirectory
NSDocumentDirectory
NSDownloadsDirectory
For example, in an iOS application if the original path was "/Users/geddy/Library/Audio/Sounds/MyFavoriteHeadacheSound.caf" and this path was not found, the AUSampler would then search for the audio file in the following four places:
<Bundle_Directory>/Sounds/MyFavoriteHeadacheSound.caf
<NSLibraryDirectory>/Sounds/MyFavoriteHeadacheSound.caf
<NSDocumentDirectory>/Sounds/MyFavoriteHeadacheSound.caf
<NSDownloadsDirectory>/Sounds/MyFavoriteHeadacheSound.caf
Therefore using the above example, if you were moving a preset created on the Desktop to an iOS application you could simply place the MyFavoriteHeadacheSound.caf file in a folder called "Sounds" within your application bundle and the AUSampler will find the audio file referenced by the preset.
You need to add both presets to your target so that they are part of the bundle:
That's what the line:
NSURL *presetURL = [[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle] pathForResource:#"Trombone" ofType:#"aupreset"]];
says.
Also you may want to have a look at Referencing External Audio Files at the bottom of the page.
I'm getting troubles getting the applications associated with a remote file how has a URL like: "http://servername/folder/file.png".
The code I use is:
...
NSURL *url = [NSURL fileURLWithPath:#"http://servername/folder/file.png"];
NSArray *apps = (NSArray *)LSCopyApplicationURLsForURL((CFURLRef) url,kLSRolesAll);
...
I also try using URLWithString: but the result is the same.
What is the correct approach for this?
Edit after Joshua replay:
Sorry for may bad redaction. The problem is that the LSCopyApplicationURLsForURL call return nil.
First, -fileURLWithPath: expects a file system path like #"/Users/me/Desktop/MyFile.png".
Second, you don't say what the "same" results are and what is wrong with them, so it's difficult to tell you what the problem is.
The docs say this:
If the item URL’s scheme is file
(designating either a file or a
directory), the selection of suitable
applications is based on the
designated item’s filename extension,
file type, and creator signature,
along with the role specified by the
inRolesMask parameter; otherwise, it
is based on the URL scheme (such as
http, ftp, or mailto).
So what are you getting and what do you expect to get?
In a Cocoa app generally we can install a plugin bundle in one of a number of places. If for example the app is called "MyApp" you'd be able to install the plugin at:
/Applications/MyApp.app/Contents/PlugIns
~/Library/Application Support/MyApp/PlugIns
/Library/Application Support/MyApp/PlugIns
/Network/Library/Application Support/MyApp/PlugIns
I'm building an NSArray of paths to search in the correct order but I'm pretty sure I'm doing this wrong since it feels like I'm doing too much work for something Apple seem to provide a lot of functions for.
NSArray *systemSearchPaths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSAllDomainsMask, YES);
NSMutableArray *searchPaths = [NSMutableArray array];
NSFileManager *fileManager = [NSFileManager defaultManager];
for (NSString *systemPath in systemSearchPaths) {
NSString *systemPluginsPath = [systemPath stringByAppendingPathComponent:#"PlugIns"];
// FIXME: Remove debug code
NSLog(#"Considering plugin path %#", systemPluginsPath);
if ([fileManager fileExistsAtPath:systemPluginsPath]) {
[searchPaths addObject:systemPluginsPath];
}
}
[searchPaths addObject:[[NSBundle mainBundle] builtInPlugInsPath]];
This results in the Array returned by NSSearchPathForDirectoriesInDomains, with the builtInPlugInsPath value appended to the end.
However, it actually searches directories like "~/Library/Application Support/PlugIns" (missing the "MyApp") folder. Before I start hacking the code to inject the name of my application (which is subject to change at any time), am I doing this wrong?
Is there a way to just tell Cocoa "give me all search paths for 'PlugIns'" directories for this application"?
Nope. You're doing it right.
You can get the name of your application at run time by asking the main bundle for its info dictionary and looking for kCFBundleNameKey therein. When you rename your application, change the bundle name in your Info.plist.
(Definitely do not use your application's filename, as that's much more fragile.)
Be aware that users might not like it if the plug-ins they installed stop working because you renamed your application.
Note that your code above will not catch the PlugIns folder inside the application bundle. For that, ask your main bundle for its built-in plug-ins path or URL.
In my Cocoa app how can I get the current user's domain when they're logged in via Active Directory?
I need to determine two things:
If the current user is logged on to an Active Directory domain (only need to handle Active Directory).
If 1, the domain of the user.
I've found references to Directory Services and the Open Directory Programming Guide but the latter is 10.6 only (I must support 10.5+) and I could not find any examples for the former that gave me an idea of what I needed to do.
dsconfigad -show
It will tell whether you are bound to a directory and details about that directory if bound. Then you will know how to script dscl.
I think you can use same idea as here:
Mac OSX: Determing whether user account is an Active Directory user vs. local user using objective-c
you just need to look for Network or Authentication node (it's kODNodeTypeAuthentication type) and query it for kODAttributeTypeRecordName with query value set to current user name. Then look at search result, you'll find many interesting things there :)
You can use this code, but note that I assume that you refer to the user that is running the current application, if you are running as root, it won't work.
In case you run it as root, simply change the NSUserName() in the query to the desired user you wish to query.
std::string getDomainForCurrentUser()
{
ODSession *session = [ODSession defaultSession];
ODNode *node = [ODNode nodeWithSession:session type:kODNodeTypeAuthentication error:NULL];
ODQuery *query = [ODQuery queryWithNode:node forRecordTypes:kODRecordTypeUsers attribute:kODAttributeTypeRecordName matchType:kODMatchEqualTo queryValues:NSUserName() returnAttributes:kODAttributeTypeStandardOnly maximumResults:0 error:NULL];
NSArray *records = [query resultsAllowingPartial:NO error:NULL];
for (ODRecord *record in records)
{
NSArray *recordLines = [record valuesForAttribute:kODAttributeTypePrimaryNTDomain error:nil];
if (recordLines)
{
NSString *domain = [recordLines firstObject];
std::string([domain UTF8String]);
}
}
return "";
}
Is there a way to mount a folder on the hard disk as a device in Finder. The intend here is to provide the user with an easy way to get to a folder that my application uses to store data. I don't want my user to go searching for data in Application Data. I would rather allow them to make this data available as a mounted volume or device in Finder. I would also like this volume or device to be read/write, so that if the user makes any changes to the data files, the changes will get reflected in the original folder.
Is there a way to do this in cocoa, carbon or applescript.
Try looking into FUSE. You can have all sorts of psuedo filesystems with that.
But I'd caution a little against what you are trying to do. It may make more sense to just have a button that opens the folder in your application, rather than create a new device. I personally would find it hard to continue to use an application that does such a thing. It doesn't really fit with the rest of the available applications.
You could also use an alias to point to your Application Data directory.
You can use sparse disk image to create "fake" drive.
But why not make data directory configurable in your application? Or use subdirectory in ~/Documents/?
Alias/symlink on desktop will be the easiest solution:
ln -s '~/Application Data/Yourapp' '~/Desktop/Yourapp Data'
Can I suggest rethinking this entirely? A symlink or alias would work, but, if possible, a better idea would be to register for the filetypes people will be moving into that folder, and then respond to opening them by moving or copying them to the correct folder. I'm thinking of something like the Dashboard interface, where if you double-click a downloaded .wdgt file, it asks if you want to 'install' the widget and then, if you do, copies it into ~/Library/Widgets. Obviously, if you're dealing with common types like images, folders, or generic text files, this might be impractical.
For implementation, you'd just add the document types to your Info.plist, and handle them in you App Delegate's -application:openFile: method.
I would also urge caution with this, seems potentially somewhat confusing to most users. That said, have you considered simply creating a softlink to the directory in question?
I Do it using NSWorkspace. In my case I make an initial check with the function -(BOOL)isMountedPath;
The code for mounting is:
NSURL *path=[NSURL URLWithString:#"smb://server.resource/KEYS_DB"];
if(NO==[self isMountedPath:[path absoluteString]])
{
NSWorkspace *ws=[NSWorkspace sharedWorkspace];
[ws openURL:path];
}
The code to check if a path is mounted is:
-(BOOL)isMountedPath:(NSString *)share
{
NSArray * keys = #[NSURLVolumeURLForRemountingKey];
NSArray * mountPaths = [[NSFileManager defaultManager] mountedVolumeURLsIncludingResourceValuesForKeys:keys options:0];
NSError * error;
NSURL * remount;
for (NSURL * mountPath in mountPaths) {
[mountPath getResourceValue:&remount forKey:NSURLVolumeURLForRemountingKey error:&error];
if(remount){
if ([[[NSURL URLWithString:share] host] isEqualToString:[remount host]] && [[[NSURL URLWithString:share] path] isEqualToString:[remount path]])
{
printf("Already mounted at %s\n", [[mountPath path] UTF8String]);
return YES;
}
}
}
return NO;
}
Other possible useful method is:
-(NSString *)mountedPath:(NSString *)share
{
NSArray * keys = #[NSURLVolumeURLForRemountingKey];
NSArray * mountPaths = [[NSFileManager defaultManager] mountedVolumeURLsIncludingResourceValuesForKeys:keys options:0];
NSError * error;
NSURL * remount;
for (NSURL * mountPath in mountPaths) {
[mountPath getResourceValue:&remount forKey:NSURLVolumeURLForRemountingKey error:&error];
if(remount){
if ([[[NSURL URLWithString:share] host] isEqualToString:[remount host]] && [[[NSURL URLWithString:share] path] isEqualToString:[remount path]])
{
printf("Already mounted at %s\n", [[mountPath path] UTF8String]);
return [mountPath path];
}
}
}
return nil;
}