1

For a given extension, how do you check whether directories with that extension will be shown by the Finder as a package?

I figured the method below is an effective implementation for this, but creating a temporary directory feels like a hack. I'm guessing I should be able to implement this properly through the Launch Services API, but I can't quite figure out how to do it (I might be overlooking the obvious though).

// Extension method on NSWorkspace
@implementation NSWorkspace (MyExtraMethods)
- (BOOL) isPackageExtension: (NSString*) extension
{
    NSString * pathToTemp = [NSTemporaryDirectory() stringByAppendingPathComponent:[@"Untitled" stringByAppendingPathExtension: extension]];
    [[NSFileManager defaultManager] createDirectoryAtPath:pathToTemp withIntermediateDirectories:NO attributes:nil error:NULL];
    BOOL result = [[NSWorkspace sharedWorkspace] isFilePackageAtPath: pathToTemp];
    [[NSFileManager defaultManager] removeItemAtPath:pathToTemp error:NULL];
    return result;
}
@end

// Basic test for the above
- (void) testIsPackageExtension
{
    STAssertFalse([[NSWorkspace sharedWorkspace] isPackageExtension: @"txt"], @"");
    STAssertFalse([[NSWorkspace sharedWorkspace] isPackageExtension: @"rtf"], @"");

    STAssertTrue([[NSWorkspace sharedWorkspace] isPackageExtension: @"rtfd"], @"");
    STAssertTrue([[NSWorkspace sharedWorkspace] isPackageExtension: @"app"], @"");
    STAssertTrue([[NSWorkspace sharedWorkspace] isPackageExtension: @"kext"], @"");
    STAssertTrue([[NSWorkspace sharedWorkspace] isPackageExtension: @"clr"], @"");

    /* The following tests depend on having applications installed
       that are not included in Mac OS X:
        .esproj   Espresso, tested with version 2.0.5 ( http://macrabbit.com/espresso/ )
        .dtps     Instruments, included in Xcode, tested with version 4.5 (4523) */
    STAssertTrue([[NSWorkspace sharedWorkspace] isPackageExtension: @"esproj"], @"");
    STAssertTrue([[NSWorkspace sharedWorkspace] isPackageExtension: @"dtps"], @"");
}

Edit: The test above has been edited to include additional example extensions (original post only used "rtf" and "rtfd").

Rinzwind
  • 1,173
  • 11
  • 23

1 Answers1

1

You can query the Uniform Type Identifier information for your extension.
You first have to find out the correct UTI and then test if it conforms to com.apple.package (which is declared as kUTTypePackage):

- (BOOL)isPackageTypeForExtension:(NSString*)extension
{
    CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)(extension), NULL);
    return UTTypeConformsTo(UTI, kUTTypePackage);
}

Update:
Additionally you could check against kUTTypeBundle:

 UTTypeConformsTo(UTI, kUTTypeBundle)

and OR (|) the result into the return value of above method.

The following Apple docs describe how to define the UTI for a document based app (which often use NSFileWrappers & document bundles): https://developer.apple.com/library/mac/documentation/DataManagement/Conceptual/DocBasedAppProgrammingGuideForOSX/ApplicationCreationProcess/ApplicationCreationProcess.html#//apple_ref/doc/uid/TP40011179-CH6-997699

Update2:
Additionally to the UTI conforming to kUTTypePackage or kUTTypeBundle, there is a Package Bit file attribute, which can also mark a folder as package:

From the Apple's documentation:

How the System Identifies Bundles and Packages The Finder considers a directory to be a package if any of the following conditions are true:

  • The directory has a known filename extension: .app, .bundle, .framework, .plugin, .kext, and so on.
  • The directory has an extension that some other application claims represents a package type; see “Document Packages.”
  • The directory has its package bit set.

Update3:
The UTI-based approach described above, does not cover all the cases the Finder takes into account when testing if a folder is a package or bundle.
It can be used to test if a specific UTI conforms to kUTTypeBundle or kUTTypePackage though.

Thomas Zoechling
  • 34,177
  • 3
  • 81
  • 112
  • Thanks for the answer. I tested your implementation and found that it gives a different result for the extension "esproj" (I have Espresso 2.0.5 installed, http://macrabbit.com/espresso/): the “temporary directory” implementation returns YES (corresponding to the Finder's behavior) while the UTI implementation returns NO. Do you have some idea why that may be? – Rinzwind Jun 05 '13 at 09:33
  • Probably that depends on what the UTI declaring application (Espresso in your case) defines in it's plist. I updated my answer to also check against kUTTypeBundle. – Thomas Zoechling Jun 05 '13 at 09:40
  • I tried editing the code to also check against kUTTypeBundle but it still returns NO for "esproj". I added an NSLog for UTI and found that it starts with "dyn."; which, if I understand the documentation correctly, means UTTypeCreatePreferredIdentifierForTag did not find a matching UTI and just created a “fake” dynamic one. Can you confirm this behavior after installing Espresso 2.0.5? – Rinzwind Jun 05 '13 at 10:10
  • Does it work for other packages? Maybe it just doesn't return a "preferred" type. You could try to use UTTypeCreateAllIdentifiersForTag and iterate over it, testing if one identifier is a package. – Thomas Zoechling Jun 05 '13 at 10:24
  • Extensions for which the “temporary directory” and UTI implementations give the same result: rtf, rtfd, app, txt. Extensions with different results (first implementation YES, second NO): esproj, dtps, kext. For these three UTTypeCreateAllIdentifiersForTag returns an array with a single element, a string which starts with "dyn.". Can you confirm the same behavior on your machine for "kext"? It does not require any app to be installed ("kext" = Kernel extension). – Rinzwind Jun 05 '13 at 10:51
  • I get the same results for kexts. Maybe you have to use your tmp file workaround. The UTI based approach I came up with doesn't cover all use cases. I found out that there is also a "Package Bit" that could mark a folder as package - But it's not set for kexts. Details: http://stackoverflow.com/questions/2009687/make-mac-package-bundle-programmatically – Thomas Zoechling Jun 05 '13 at 11:28
  • Thanks for confirming the "kext" example and for your help! For future reference, I've edited the test in my question to reflect the extra examples we discussed, in case someone comes up with another implementation approach (which should at least pass this test). For now, I'll stick to the “hack” implementation. – Rinzwind Jun 05 '13 at 11:38