Mounting a folder as a device in Finder using Cocoa - cocoa

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

Related

Can't see DataBase in device after copied in bundle

I finished my App for a distance calculation. I'll update my database before to copy it.
I did some tests and when I'm moving my database (DataBase.sqlite) into "Copy Bundle Ressources" and running App on my device, I can't see my data.
Looks like the device is using his own database with same name (DataBase.sqlite).
(DataBase will update a TableView during launch)
I created a AddButton to see if I can update my DataBase from an iPhone and it works.
I mean is I closed the App an re launched it, I can see the created data from the AddButton.
Have you any idea?
When you include the database into your bundle via Xcode's "Copy Bundle Resources", it's part of the bundle, i.e. you get the path via:
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:#"DataBase" ofType:#"sqlite"];
If you want to copy that to your Documents folder so that you can use it, you get the Documents path via:
NSString *documentsFolder = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *documentsPath = [documentsFolder stringByAppendingPathComponent:#"DataBase.sqlite"];
So, you might check for existence in Documents, and if not there, copy from bundle to Documents:
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:documentsPath]) {
NSError *error = nil;
BOOL success = [fileManager copyItemAtPath:bundlePath toPath:documentsPath error:&error];
NSAssert(success, #"%s: copyItemAtPath failed: %#", __FUNCTION__, error);
}
You can now open the database in the Documents folder (e.g. using documentsPath).
Now, if you ever attempted to open a database in Documents without first copying from the bundle, the standard sqlite3_open will create a blank database. So, you probably want to delete the app from the device/simulator and then reinstall, to get rid of that blank database in Documents (otherwise the above logic, testing for the existence of the database in Documents, will result in false positive).

Cocoa App that deletes itself

I'm writing a Mac OSX application using Cocoa that is designed to stop working after a specified date, to avoid the user simply changing their system clock and then re-running the app I would like the program to close and delete itself if it is loaded after the expiry date. Is this possible?
I am distributing the application directly not through the app store. Also, checking the date using the internet isn't really an option because the app needs to be useable offline.
Thanks,
Matthew
It's possible, but not reliably so. To delete your app, just get your main bundle's URL and tell NSFileManager to delete it. But your app bundle may not be writable — and thus not deletable either — and the user may have any number of backups even if you do manage to delete it. I would not write anything that depends on this being possible unless I had tight control over the systems the program will run on. (I mean, I probably wouldn't write something that does this anyway, because it's a little crazy. But if I were going to write something like this, it would have to be something that only runs on my own systems.)
You could perform some sanity checks in the system to get an idea whether the user manually set the clock back to the past.
Note that I still don't think the plan of (maliciously) deleting user files is a great idea in general and the following approach in particular will certainly break under Sandboxing..
..but out of curiosity: Here's a snippet that will check all files in /var/log and return whether some of them have been modified in the future (= the system is quite likely running "in the past")
- (bool)isFakeSystemTime
{
int futureFileCount = 0;
// let's check against 1 day from now in the future to be safe
NSTimeInterval secondsPerDay = 24 * 60 * 60;
NSDate *tomorrow = [[[NSDate alloc] initWithTimeIntervalSinceNow:secondsPerDay] autorelease];
NSString *directoryPath = #"/var/log";
NSArray *filesInDirectory = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:directoryPath error:nil];
for (NSString* fileName in filesInDirectory)
{
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[directoryPath stringByAppendingPathComponent:fileName] error:nil];
NSDate *date = [attributes valueForKey:#"NSFileModificationDate"];
if (!date)
continue;
if ([date compare:tomorrow] == NSOrderedDescending)
{
NSLog(#"File '%#' modified >=1 day in the future", fileName);
futureFileCount++;
}
}
// again, some heuristic to be (more) on the safe side
return futureFileCount > 5;
}

set uniform filepaths

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"];

How to properly save a QTMovie after editing using QTKit?

I am making minor edits to a QTMovie in an application using NSDocument architecture (such as adding a track, as shown below). After the edit, I want to save to the original file. However, I keep getting a 'file is busy' error. I assume this is due to some oversight I made in the handling of the files, or a failure in how I am using NSDocument. Any tips would be helpful! Here is (some of) the relevant code:
// open file that has the track I want to add to my movie
QTMovie* tempMovie = [QTMovie movieWithURL:outputFileURL error:nil];
// Use old API to add the track
AddMovieSelection([movie quickTimeMovie], [tempMovie quickTimeMovie]);
// get the filename from the NSDocument
NSString *filename = [[self fileURL] path];
NSLog(#"writing to filename: %#", filename);
// FLATTEN the movie file so I don't get external references
NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:1];
[attributes setObject:[NSNumber numberWithBool:YES] forKey:QTMovieFlatten];
// attempt to write
NSError *error;
// this is where I get the "file is busy"
if (![movie writeToFile:filename withAttributes:attributes error:&error]) {
NSLog(#"Error: %#", [error localizedDescription]);
NSRunAlertPanel(#"Error", [error localizedDescription], nil, nil, nil);
}
Do I have to first release the movie in my NSDocument? What is the "proper" way to do that? Keep in mind, I am not necessarily finished with this document, I am not closing it. I have just finished this operation, and I want the file on disk to reflect my changes. I would love to use [movie updateMovieFile], but that call doesn't seem to flatten the movie. I don't want any external references in my file.
I am not too familiar with the QuickTime C API, so I honestly can't tell you anything about what is going wrong there. Absolute guesswork: Maybe a call to EndMediaEdits is missing?
Though that shouldn't be required by AddMovieSelection, you said "[...] such as adding a track [...]". So maybe there is something else going on, like AddMediaSample or something similar?
That said, if you don't need to target anything below 10.5 and all you need to do is add some segment from another movie, you can achieve that without dropping down to the C API:
Have a look at
-[QTMovie insertSegmentOfTrack:fromRange:scaledToRange:]
and
-[QTMovie insertSegmentOfMovie:fromRange:scaledToRange:], if you want to have the inserted segment "overlayed" (temporally speaking).
-[QTMovie insertSegmentOfMovie:timeRange:atTime:] and -[QTMovie insertSegmentOfTrack:timeRange:atTime:], if you want { movieA.firstPart, movieB, movieA.secondPart }.
Do I have to first release the movie in my NSDocument?
You mean in order to write it to disk? No: That should even result in a crash.
The role of release is to handle memory-management. It doesn't have much to do with the busy-state of a file.
Turns out I just wasn't using the NSDocument architecture properly. When I changed it to use Save/SaveAs properly, this problem went away.

Cocoa: Correct way to get list of possible PlugIns directories for an app?

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.

Resources