In my code how to launch application responsible for an UTI - objective-c

My Mac OS X application receives a file over the network (in this case, text/x-vcard). In my code, how can I open the related application (typically the Address Book) without hard-coding paths or application name so that it processes the file ?

You'll be able to do this by linking in the ApplicationServices framework, which has a really handy "LSCopyApplicationForMIMEType" function. It works like this:
CFURLRef appURL = nil;
OSStatus err = LSCopyApplicationForMIMEType(CFSTR("text/x-vcard"), kLSRolesAll, &appURL);
if (err != kLSApplicationNotFoundErr) {
NSLog(#"URL: %#", (NSURL *)appURL);
}
CFRelease(appURL);
I'll explain what the parameters mean. The first parameter is a CFStringRef of the MIME type you're looking up. The second parameter indicates what kind of application you're looking for, ie an app that can edit this file, or an app that can view this file, etc. kLSRolesAll means you don't care. The final parameter is a pointer to the CFURLRef where the function will stick the app's URL (if it can find one).
On my machine, this prints out:
2009-08-01 12:38:58.159 EmptyFoundation[33121:a0f] URL: file://localhost/Applications/Address%20Book.app/
One of the cool things about CFURLRefs is that they're toll-free bridged to NSURL. This means you can take a CFURLRef and cast it to an NSURL, and vice versa. Once you've got your NSURL of the app, it's pretty trivial to use something like NSWorkspace's -launchApplicationAtURL:options:configuration:error: method to open the application.
If you want to open a specific file in that application (like the file from which you got the MIME type), you could use something like -[NSWorkspace openFile:withApplication:].
If you can't get the MIME type (despite what you say in your question), there are a bunch of similar LaunchServices functions. You can read all about them here.

Rather than even bothering to try to find the application you can use LSOpenItemsWithRole.
//Opens items specified as an array of values of type FSRef with a specified role.
OSStatus LSOpenItemsWithRole (
const FSRef *inItems,
CFIndex inItemCount,
LSRolesMask inRole,
const AEKeyDesc *inAEParam,
const LSApplicationParameters *inAppParams,
ProcessSerialNumber *outPSNs,
CFIndex inMaxPSNCount
);

Related

Files Copied to Clipboard are empty and "Missing Sandbox Extension" When Pasted

I have an application that is copying files to the clipboard. Here is the relevant code (arguments is an array of NSStrings containing file paths):
NSMutableArray *filesToCopy = [[NSMutableArray alloc] init];
int i;
for (i=1; i < [arguments count]; i++) {
NSString* pathToFile = [arguments objectAtIndex:i];
NSURL* fileURL = [[NSURL alloc] initFileURLWithPath:pathToFile];
[filesToCopy addObject:fileURL];
}
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
NSInteger changeCount = [pasteboard clearContents];
BOOL OK = [pasteboard writeObjects:filesToCopy];
However, in testing when trying to paste a file copied to the clipboard by the program into an empty email in Mail, often the file shows up as zero bytes and the following error appears on the console:
11/13/13 6:27:12.173 AM Mail[627]: Failed to obtain a valid sandbox extension for item: [789514] of flavor: [public.file-url] from the pasteboard.
11/13/13 6:27:12.174 AM Mail[627]: Failed to get a sandbox extensions for itemIdentifier (789514). The data for the sandbox extension was NULL
and then that is followed by the following error on the console:
11/13/13 8:24:41.947 AM sandboxd[172]: ([627]) Mail(627) deny file-read-xattr [full path of file]
What is strange is that if I copy the file from Finder then it pastes just fine with no errors every time. In other words, somehow Finder copies the file to the clipboard with different information than the way I am doing it. To verify this, I did a simple AppleScript to return clipboard info. After I copy a file to the clipboard that returns the following:
{{«class furl», 115}, {«class utf8», 115}, {«class ut16», 232}, {string, 115}, {Unicode text, 230}}
After I copy the same file to the clipboard using Finder, the Applescript returns the following:
{{«class furl», 33}, {«class icns», 795020}, {«class ut16», 112}, {«class utf8», 55}, {«class 8BPS», 1630436}, {«class BMP », 4194358}, {«class TPIC», 1059291}, {TIFF picture, 4197954}, {«class PNGf», 392648}, {«class jp2 », 213480}, {GIF picture, 121307}, {JPEG picture, 116181}, {Unicode text, 110}, {string, 55}}
So Finder is putting more information about the file on the clipboard and different information. For example, the furl class has a different length. This extra information is obviously what is cause Mail to successfully past a file copied from Finder while it has an error pasting a file copied by my program.
Any clues to what information I'm missing when I put the file on the clipboard or what extra information I should be adding to the paste? I'm guessing that Finder is pasting not just an array of NSURL's, but an array of Dictionary Keys that includes other file information. It also seems to be creating the furl class differently than I am. I've spent time pouring over documentation and I'm stuck on this one.
I believe I found the problem. It seems like when command line applications copy to the pasteboard, there is a permission related to sandboxing that is not transferred. The files copied could be pasted fine into any non-sandboxed app, but not into a sandboxed application. The solution in this case was to just create a regular Cocoa based .app program. I'm still not sure how, but it copies the files in a way that the permissions are properly transferred and the file can be pasted in both non-sandboxed and sandboxed applications.
There is is lot written out there about avoiding triggering the Sandboxing mechanism in Sandboxed Apps.
But all answers lack the most obvious trouble that can trigger Sandboxing.
When you make a copy of a NSURL (which is still based on NSString) into NSPasteboard without properly escaped characters still containing non-valid signs and spaces before transforming into 'NSURL'.
This is one of the ways URLs can become malicious, so it is only a little bit annoying that the Error Message you get while trying to expose the link to pasteboard is not telling you what the obvious reason was in detail. But maybe this is also because, "never give too much informations how to circumvent the safety barriers".
It just says:
[sandbox] CreateSandboxExtensionData failed: urlData: 0x0000000000XYZ length: 0 (-1 means null)
then you have to properly escape the string with
NSString *encodedStringForURL = [yourString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
this will exchange special characters that should not be part of n URL.
After that you can transform to NSURL as usually.
or
Failed to obtain a valid sandbox extension
then you also used a wrong NSPasteboardType that exposes a URL without proper type. In example when you exposed a String that poses as Link because you tried to avoid the [sandbox] trigger by being clever and telling the pasteboard this is only a random string. The Sandbox is still more clever when you prepend the string with file://.
Then the solution is to use NSPasteboardTypeFileURL and/or NSPasteboardTypeURL. This will inform the sandbox that you exposed links on purpose with proper type.
There are more pitfalls, but also remember you have to use YourURL.absoluteString when you paste to Pasteboard.

How to dynamically typecast objects to support different versions of an application's ScriptingBridge header files?

Currently I'm trying to implement support for multiple versions of iTunes via ScriptingBridge.
For example the method signature of the property playerPosition changed from (10.7)
#property NSInteger playerPosition; // the player’s position within the currently playing track in seconds.
to (11.0.5)
#property double playerPosition; // the player’s position within the currently playing track in seconds
With the most current header file in my application and an older iTunes version the return value of this property would always be 3. Same thing goes the other way around.
So I went ahead and created three different iTunes header files, 11.0.5, 10.7 and 10.3.1 via
sdef /path/to/application.app | sdp -fh --basename applicationName
For each version of iTunes I adapted the basename to inlcude the version, e.g. iTunes_11_0_5.h.
This results in the interfaces in the header files to be prefixed with their specific version number.
My goal is/was to typecast the objects I'd use with the interfaces of the correct version.
The path to iTunes is fetched via a NSWorkspace method, then I'm creating a NSBundle from it and extract the CFBundleVersion from the infoDictionary.
The three different versions (11.0.5, 10.7, 10.3.1) are also declared as constants which I compare to the iTunes version of the user via
[kiTunes_11_0_5 compare:versionInstalled options:NSNumericSearch]
Then I check if each result equals NSOrderedSame, so I'll know which version of iTunes the user has installed.
Implementing this with if statement got a bit out of hand, as I'd need to do these typecasts at many different places in my class and I then started to realize that this will result in a lot of duplicate code and tinkered around and thought about this to find a different solution, one that is more "best practice".
Generally speaking, I'd need to dynamically typecast the objects I use, but I simply can't find a solution which wouldn't end in loads of duplicated code.
Edit
if ([kiTunes_11_0_5 compare:_versionString options:NSNumericSearch] == NSOrderedSame) {
NSLog(#"%#, %#", kiTunes_11_0_5, _versionString);
playerPosition = [(iTunes_11_0_5_Application*)_iTunes playerPosition];
duration = [(iTunes_11_0_5_Track*)_currentTrack duration];
finish = [(iTunes_11_0_5_Track*)_currentTrack finish];
} else if [... and so on for each version to test and cast]
[All code directly entered into answer.]
You could tackle this with a category, a proxy, or a helper class, here is a sketch of one possible design for the latter.
First create a helper class which takes and instance of your iTunes object and the version string. Also to avoid doing repeated string comparisons do the comparison once in the class setup. You don't give the type of your iTunes application object so we'll randomly call it ITunesAppObj - replace with the correct type:
typedef enum { kEnumiTunes_11_0_5, ... } XYZiTunesVersion;
#implementation XYZiTunesHelper
{
ITunesAppObj *iTunes;
XYZiTunesVersion version;
}
- (id) initWith:(ITunesAppObj *)_iTunes version:(NSString *)_version
{
self = [super self];
if (self)
{
iTunes = _iTunes;
if ([kiTunes_11_0_5 compare:_version options:NSNumericSearch] == NSOrderedSame)
version = kEnumiTunes_11_0_5;
else ...
}
return self;
}
Now add an item to this class for each item which changes type between versions, declaring it with whatever "common" type you pick. E.g. for playerPosition this might be:
#interface XYZiTunesHelper : NSObject
#property double playerPosition;
...
#end
#implementation XYZiTunesHelper
// implement getter for playerPosition
- (double) playerPosition
{
switch (version)
{
case kEnumiTunes_11_0_5:
return [(iTunes_11_0_5_Application*)_iTunes playerPosition];
// other cases - by using an enum it is both fast and the
// compiler will check you cover all cases
}
}
// now implement the setter...
Do something similar for track type. Your code fragment then becomes:
XYZiTunesHelper *_iTunesHelper = [[XYZiTunesHelper alloc] init:_iTunes
v ersion:_versionString];
...
playerPosition = [_iTunesHelper playerPosition];
duration = [_currentTrackHelper duration];
finish = [_currentTrackHelper finish];
The above is dynamic as you requested - at each call there is a switch to invoke the appropriate version. You could of course make the XYZiTunesHelper class abstract (or an interface or a protocol) and write three implementations of it one for each iTunes version, then you do the test once and select the appropriate implementation. This approach is more "object oriented", but it does mean the various implementations of, say, playerPosition are not together. Pick whichever style you feel most comfortable with in this particular case.
HTH
Generating multiple headers and switching them in and out based on the application's version number is a really bad 'solution': aside from being horribly complicated, it is very brittle since it couples your code to specific iTunes versions.
Apple events, like HTTP, were designed by people who understood how to construct large, flexible long-lived distributed systems whose clients and servers could evolve and change over time without breaking each other. Scripting Bridge, like a lot of the modern 'Web', was not.
...
The correct way to retrieve a specific type of value is to specify your required result type in the 'get' event. AppleScript can do this:
tell app "iTunes" to get player position as real
Ditto objc-appscript, which provides convenience methods specifically for getting results as C numbers:
ITApplication *iTunes = [ITApplication applicationWithBundleID: #"com.apple.itunes"];
NSError *error = nil;
double pos = [[iTunes playerPosition] getDoubleWithError: &error];
or, if you'd rather get the result as an NSNumber:
NSNumber *pos = [[iTunes playerPosition] getWithError: &error];
SB, however, automatically sends the 'get' event for you, giving you no what to tell it what type of result you want before it returns it. So if the application decides to return a different type of value for any reason, SB-based ObjC code breaks from sdp headers onwards.
...
In an ideal world you'd just ditch SB and go use objc-appscript which, unlike SB, knows how to speak Apple events correctly. Unfortunately, appscript is no longer maintained thanks to Apple legacying the original Carbon Apple Event Manager APIs without providing viable Cocoa replacements, so isn't recommended for new projects. So you're pretty much stuck with the Apple-supplied options, neither of which is good or pleasant to use. (And then they wonder why programmers hate everything AppleScript so much...)
One solution would be to use AppleScript via the AppleScript-ObjC bridge. AppleScript may be a lousy language, but at least it knows how to speak Apple events correctly. And ASOC, unlike Cocoa's crappy NSAppleScript class, takes most of the pain out of gluing AS and ObjC code together in your app.
For this particular problem though, it is possible to monkey-patch around SB's defective glues by dropping down to SB's low-level methods and raw four-char codes to construct and send the event yourself. It's a bit tedious to write, but once it's done it's done (at least until the next time something changes...).
Here's a category that shows how to do this for the 'player position' property:
#implementation SBApplication (ITHack)
-(double)iTunes_playerPosition {
// Workaround for SB Fail: older versions of iTunes return typeInteger while newer versions
// return typeIEEE64BitFloatingPoint, but SB is too stupid to handle this correctly itself
// Build a reference to the 'player position' property using four-char codes from iTunes.sdef
SBObject *ref = [self propertyWithCode:'pPos'];
// Build and send the 'get' event to iTunes (note: while it is possible to include a
// keyAERequestedType parameter that tells the Apple Event Manager to coerce the returned
// AEDesc to a specific number type, it's not necessary to do so as sendEvent:id:parameters:
// unpacks all numeric AEDescs as NSNumber, which can perform any needed coercions itself)
NSNumber *res = [self sendEvent:'core' id:'getd' parameters: '----', ref, nil];
// The returned value is an NSNumber containing opaque numeric data, so call the appropriate
// method (-integerValue, -doubleValue, etc.) to get the desired representation
return [res doubleValue];
}
#end
Notice I've prefixed the method name as iTunes_playerPosition. Unlike objc-appscript, which uses static .h+.m glues, SB dynamically creates all of its iTunes-specific glue classes at runtime, so you can't add categories or otherwise patch them directly. All you can do is add your category to the root SBObject/SBApplication class, making them visible across all classes in all application glues. Swizzling the method names should avoid any risk of conflict with any other applications' glue methods, though obviously you still need to take care to call them on the right objects otherwise you'll likely get unexpected results or errors.
Obviously, you'll have to repeat this patch for any other properties that have undergone the same enhancement in iTunes 11, but at least once done you won't have to change it again if, say, Apple revert back to integers in a future release or if you've forgotten to include a previous version in your complicated switch block. Plus, of course, you won't have to mess about generating multiple iTunes headers: just create one for the current version and remember to avoid using the original -playerPosition and other broken SB methods in your code and use your own robust iTunes_... methods instead.

Using file pointers in Objective-C for iOS

I am trying to use a library (cfitsio) in an iOS application that heavily depends on using a file pointer to open and maintain access to a data file. I have successfully built an Objective-C Mac app that does what I need to do on iOS. Cocoa touch has methods to load a file in to NSData but I don't see anyway to get a file pointer directly, probably because of the stricter privacy around the file system on iOS. Is there a way to get a file pointer directly or use NSData to make a file pointer for temporary use with the library?
The c function I would be using to open the file is declared below. The file pointer continues to be used in many of the other library functions.
int ffopen(fitsfile **fptr, /* O - FITS file pointer */
const char *name, /* I - full name of file to open */
int mode, /* I - 0 = open readonly; 1 = read/write */
int *status); /* IO - error status */
Try using the NSFileManager which creats a NSFileHandle when you open a file.
This handle has a getter for the fileDescriptor:
Returns the file descriptor associated with the receiver.
- (int)fileDescriptor
Return Value
The POSIX file descriptor associated with the receiver.
see also NSFileHandle
It seems this library declares a type "fitsfile" and uses pointers "fitsfile *".
The function ffopen looks very much like it opens the file and creates the fitsfile* which it returns using a fitsfile**. So you don't have to worry about these files at all, the library does it.
So you'd probably write something like
NSString* path = <Objective-C code to get a path>;
BOOL readOnly = <YES or NO, your choice>
fitsfile* fptr = NULL;
int fstatus = 0;
int fresult = ffopen (&fptr, path.UTF8String, (readonly ? 0 : 1), &fstatus);
Since this library wants the data stored in a file that has a path, the only way to do this is to store the data in a file and pass the path to that file to ffopen.

How do I programmatically find the user's logging directory?

Given an app called ExampleApp, I want to find "~/Library/Logs/ExampleApp" basically without using hard coded paths. There exists NSSearchPathForDirectoriesInDomains, which you can use to find things like "~/Library/Application Support/ExampleApp" using the NSApplicationSupportDirectory search term, but there doesn't seem to be a search term for logging.
I don't think ~/Library/Logs is non-standard, since CrashReporter puts its logs there.
Try this :
NSString* libraryPath = [NSHomeDirectory stringByAppendingPathComponent:#"Library/Logs"];
Update (~/Library/Logs/AppName) :
NSString* bundleName = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleName"];
NSString* logsPath = [NSString stringWithFormat:#"Library/Logs/%#",bundleName];
NSString* libraryPath = [NSHomeDirectory stringByAppendingPathComponent:logsPath];
Cocoa doesn't provide a means for finding all of the standard directories. The old FSFindFolder() function can provide many more, but does involve converting from an FSRef back to a path or URL. Apple discourages its use, but it's still the only way to get certain standard directories without hard-coding. That said, it won't ever incorporate your app name. You have to append that.
Edited to add: the link to the legacy docs.

Cocoa QTKit and recording movies

I'm new with the whole QTKit and I was looking for some feedback on the following code that I am attempting to use to display the camera's image and record movies.
- (void)initializeMovie {
NSLog(#"Hi!");
QTCaptureSession* mainSession = [[QTCaptureSession alloc] init];
QTCaptureDevice* deviceVideo = [QTCaptureDevice defaultInputDeviceWithMediaType:#"QTMediaTypeVideo"];
QTCaptureDevice* deviceAudio = [QTCaptureDevice defaultInputDeviceWithMediaType:#"QTMediaTypeSound"];
NSError* error;
[deviceVideo open:&error];
[deviceAudio open:&error];
QTCaptureDeviceInput* video = [QTCaptureDeviceInput deviceInputWithDevice:deviceVideo];
QTCaptureDeviceInput* audio = [QTCaptureDeviceInput deviceInputWithDevice:deviceAudio];
[mainSession addInput:video error:&error];
[mainSession addInput:audio error:&error];
QTCaptureMovieFileOutput* output = [[QTCaptureMovieFileOutput alloc] init];
[output recordToOutputFileURL:[NSURL URLWithString:#"Users/chasemeadors/Desktop/capture1.mov"]];
[mainSession addOutput:output error:&error];
[movieView setCaptureSession:mainSession];
[mainSession startRunning];
}
Also, I'm not sure about the whole error parameter that the methods keep calling for, I saw the "&error" symbol in an example but I don't know what it means.
I'm also getting an error "cannot initialize a device that is not open" when I explicitly open the devices.
If anyone could help me sort this out, it'd be a great help, thanks.
QTCaptureDevice* deviceVideo = [QTCaptureDevice defaultInputDeviceWithMediaType:#"QTMediaTypeVideo"];
QTCaptureDevice* deviceAudio = [QTCaptureDevice defaultInputDeviceWithMediaType:#"QTMediaTypeSound"];
Pass the actual constants here, not string literals containing their names. There's no guarantee that QTMediaTypeVideo is defined to #"QTMediaTypeVideo"; it could be #"Ollie ollie oxen free", and even if it is what you expect now, it could change at any time.
[output recordToOutputFileURL:[NSURL URLWithString:#"Users/chasemeadors/Desktop/capture1.mov"]];
Don't assume that the current working directory is /. Always use absolute paths. (I know this is test code; in real code, of course, you would have run an NSSavePanel and gotten the path from there.)
Also, I'm not sure about the whole error parameter that the methods keep calling for, I saw the "&error" symbol in an example but I don't know what it means.
The & means you're taking the address of a variable, which in this case is error. You're passing this address (a.k.a. pointer) to the error: argument of one of QTKit's methods. The method will, if it encounters an error, create an NSError object and store it at that address—i.e., in your variable. This is called “return-by-reference” (the “reference” being the pointer you provided).
I'm also getting an error "cannot initialize a device that is not open" when I explicitly open the devices.
Which method returns the error? Are you talking about an NSError, or just a Console message? If the latter, check your NSError variable and see what the problem method left behind.
This, incidentally, is why you should bail out if any of the QTKit methods returns an error: one of the subsequent messages may clobber it with a new error if you don't.
Also, you may want to look at the MyRecorder sample code. It's a fully functional video recorder based on the QTKit Capture API. The code is reasonably simple and should be easy to understand.