Custom Spotlight Importer and Finder's Get Info "More Info" section - macos

I have written a Spotlight Importer for the custom document type my application defines.
Everything is working fine, the metadata fields are correctly indexed by Spotlight (verified using mdls command), and a Spotlight search reveals my documents.
The only problem I have is that items I specify in the <displayattrs> section of the schema.xml file aren't displayed in the "More Info" section when I ask for informations about a file (Cmd+I in the Finder).
I expected these fields to appear there because I declared them both in the <allattrs> and <displayattrs> sections.
I found few questions here related to this problem, none of them helped me.
The importer is bundled into the app, loaded by the system (mdimport -L confirmed this).
Also, the bundle structure seems right, the schema.xml appears in the Resources folder, as well as the schema.strings in the en/lproj folder.
Here is what the schema.xml file look like :
<schema version="1.0"
xmlns="http://www.apple.com/metadata"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.apple.com/metadata file:///System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Resources/MetadataSchema.xsd">
<types>
<type name="com.mydomain.myapp.mydocument">
<allattrs>
kMDItemTitle kMDItemAuthors kMDItemAlbum
</allattrs>
<displayattrs>
kMDItemTitle kMDItemAuthors kMDItemAlbum
</displayattrs>
</type>
</types>
A couple of things more, my system is lacking the mdcheckschema command, but the XML file is so short, I doubt there is a problem with the syntax.
Sometimes, the "More Info" section display the file last opening date, sometimes nothing.
Finally, I tried reimporting the file (mdimport), to no avail.
I'm running Mac OS X Moutain Lion 10.8.3, Xcode 4.6.2.
So here my question, am I missing something to have those items displayed in the "More Info" section ?
Is there someone who has experienced such a problem and found a solution ?
Edit :
Nobody answered my question so far, maybe someone can point me to some tutorial or documentation about this problem ?

I know that Vince has probably long since solved this (or given up). But I've just spent a frustratingly long time working through various poorly documented or totally undocumented issues with writing an importer, so I thought I'd document my findings here. (I'm afraid this has turned into an essay - it's a complicated subject).
Let's assume:
You've read the documentation on how to write a Spotlight importer, in particular the troubleshooting guide.
You've written and debugged your importer.
To debug your importer in Xcode choose Product->Scheme->Edit Scheme and set:
Info->Executable to /usr/bin/mdimport
Arguments->Arguments to -n -d2 -g $(BUILT_PRODUCTS_DIR)/$(WRAPPER_NAME) /path/to/some/test/file.ext
Options->Working Directory to $(SRCROOT)
And set a breakpoint on your GetMetadataForURL() function.
The output from /usr/bin/mdimport -n -d2 -g /path/to/your/importer.mdimporter /path/to/some/test/file.ext correctly contains the standard and/or custom metadata attributes that you intended.
You've deployed your importer for testing (either standalone in /Library/Spotlight/ or embedded in an app bundle) and mdimport -L lists your importer.
But the output of mdls /some/other/file.ext and/or Finder's "Get Info" window doesn't show the metadata attributes that you expected.
Here's some things to check:
Someone else needs to declare the UTI(s) for the document type(s) that you're importing.
If you're importing a document of a system-declared type then OSX has declared the UTI for you.
If your importer is embedded in an app bundle, then the app should declare the UTI via a UTExportedTypeDeclarations key in the app's Info.plist.
If you're importing a third-party document type then check that the app that "owns" the document type has declared a UTI for it in a UTExportedTypeDeclarations key in the app's Info.plist. If the app hasn't declared a UTI (some don't and still use the old CFBundleDocumentTypes->CFBundleTypeExtensions key instead) or if you want your importer to work even if the app isn't installed then you will have to create a "dummy" app whose sole purpose is to declare the UTI(s) in a UTImportedTypeDeclarations key in the app's Info.plist. Install the "dummy" app somewhere like /Library/Application Support/myOrg/myApp.app. Your importer should be standalone and should not be embedded in this app's bundle since Spotlight won't run importers from an app that the user hasn't opened.
There's no point declaring the UTI(s) that you're importing in UTImportedTypeDeclarations or UTExportedTypeDeclarations keys in your importer's Info.plist - LaunchServices won't reliably read them from there so Spotlight won't recognise them. However you must register your interest in the UTI(s) by refering to them in CFBundleDocumentTypes->LSItemContentTypes key(s) in your importer's Info.plist.
Symptoms of someone else not having correctly declared a UTI are that mdimport -n -d1 /some/file.ext says:
Imported '/some/file.ext' of type 'dyn.xxx' ... or (confusingly):
Imported '/some/file.ext' of type 'the.correct.uti' with no plugIn.
.
If an attribute that your importer returns is not listed in the metadata schema for your document's UTI, or for any parent UTIs, then Spotlight throws that attribute away. Even if it's a standard attribute like kMDItemAuthors. To understand why, we need to look at how Spotlight works in detail:
An app declares one or more UTIs in a UTImportedTypeDeclarations or UTExportedTypeDeclarations key.
In each UTI declaration, the app specifies one or more 'parent' UTIs in a UTTypeConformsTo key. The parent UTI should be something specific if possible - e.g. "public.image" if the app is declaring a new type of image file - or just "public.data" if nothing else is appropriate.
You can see the current state of the UTI hierarchy by poring over the contents of the LaunchServices database: /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -dump .
But that's tricky to decipher. Fortunately you'll normally be more interested in the UTI hierarchy of a 'clean' machine which can be obtained by plutil -p /System/Library/CoreServices/CoreTypes.bundle/Contents/Info.plist .
Spotlight maintains a "schema" which lists the metatdata attributes of interest to it:
You can see the current state of the metadata schema with mdimport -X 2>&1 .
You can see the the metadata schema of a 'clean' machine in /System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Resources/schema.plist .
When Spotlight is deciding what to store it cross-references the output of your importer against both the UTI hierarchy and the metadata schema. So for each attribute that your importer returns:
Spotlight looks up the document's UTI in the metadata schema. If there exists an entry for the UTI then Spotlight checks whether the attribute that your importer returns is listed under the allattrs key. If it is then Spotlight records the value provided by your importer in its database.
Otherwise Spotlight looks up the parent UTI in the UTI hierarchy and repeats the process until it hits "public.data".
If Spotlight can't find the attribute listed in the allattrs key for your document's UTI, or for any parent UTIs, then it throws away the value provided by your importer.
.
If an attribute that is stored in the Spotlight database is not listed for display in the metadata schema for your document's UTI, or for any parent UTIs, then Finder's "Get Info" window won't display it. Even if it's a standard attribute like kMDItemAuthors.
Finder follows a similar process to Spotlight above, except that it consults the displayattrs keys instead of the allattrs keys in the metadata database.
The order in which attributes are displayed depends on their position in the metadata schema hierarchy.
.
If you want to control what Spotlight stores and/or what Finder's "Get Info" window displays then your importer needs to supply a custom schema.
The format for the custom schema.xml is fairly well documented. Unfortunately the mdcheckschema command mentioned in the documentation no longer ships with Xcode. If you have a machine with an older version of OSX & Xcode you can copy it from /usr/bin/mdcheckschema. If you have an Apple Developer account you can extract it from /Packages/DeveloperToolsCLI.pkg on the "Xcode 4.2 for Snow Leopard" dmg.
You don't have to list every attribute that your importer supports in the allattrs and displayattrs keys - only those attributes that aren't listed for a parent or grandparent UTI in /System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Resources/schema.plist .
However if you want to control the order in which attributes are displayed in the "Get Info" window you should list the attributes that you want displayed first in your desired order in the displayattrs key. (See for example "public.movie" in the schema, which duplicates some keys from its parent "public.audiovisual-content" so that they're displayed first).
Your schema must define at least one custom attribute in the attributes section and reference to it in an allattrs key, otherwise Spotlight ignores the whole schema. If your importer doesn't supply any custom attributes then just add a bogus custom attribute to the schema anyway. (This requirement arrived some time after Snow Leopard and is completely undocumented, and is probably where Vince was going wrong).
If your schema defines a custom attribute (and it should; see previous point) then you must supply an English schema.strings localization for it, otherwise Spotlight ignores the whole schema. (Of course you're welcome to provide other localizations too).
Check that you have a "Copy Bundle Resources" phase in your Xcode project that copies schema.xml and schema.strings into your product.
Double-check that Contents/Resources/schema.xml and Contents/Resources/en.lproj/schema.strings or Contents/Resources/English.lproj/schema.strings really exist in your built product; some older versions of Xcode didn't copy them across.
Check that file /path/to/your/built/importer.mdimporter/Contents/Resources/en.lproj/schema.strings says:
Little-endian UTF-16 Unicode c program text .
A symptom of getting any of the above wrong is that mdimport -X 2>&1 | grep -A20 uti.of.interest either returns nothing or returns an empty schema for the UTI that your importer's schema.xml is trying to define.
Spotlight doesn't always notice changes in a timely manner.
When testing an updated version of your importer first delete the old importer (or the entire app that contains it if it's embedded in an app bundle) and type mdimport -L to check that Spotlight has noticed that it's gone (this might take ~30s) before deploying your updated version. Type mdimport -L again to check that Spotlight has noticed the updated version (again this might take ~30s) before resuming testing.
If you're distributing a standalone importer in a .pkg file, then you should include a postinstall script to 1: tell LaunchServices that the bundle has been updated (Installer does this automatically for apps, but not for other bundle types) and 2: kick Spotlight into re-indexing for the current user the document types that your importer understands:
#!/bin/sh
touch -c "$2"
if [ -n "$USER" ]; then sudo -u "$USER" /usr/bin/mdimport -r "$2"; fi
true
LaunchServices doesn't always notice changes in a timely manner, and keeps old information lying around.
If you're making changes to the declaration of the UTI(s) in the app that declares them, or to the UTI(s) that your importer registers for then LaunchServices and Spotlight can get confused. You can completely reset LaunchServices and get it to re-read from standard places with:
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -v -kill -seed -domain system -domain network -domain local -domain user
This is also useful if you want to simulate a 'clean' install of your importer and/or app on your development system.
Edit: This project on gitHub illustrates points 1-5 above.

Related

NSCameraUsageDescription key error in Xcode

I'm trying to access a webcam for a project using OpenCV in Xcode, but I keep getting this error:
This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSCameraUsageDescription key with a string value explaining to the user how the app uses this data.
I'm using a command line tool which doesn't come with a default Info.plist file, so I created an Info.plist file and added it to my project directory and added the correct key and description, but it's still showing me the same error.
The key I'm using is: Privacy - Camera Usage Description
The value I'm using is: App requires camera use to do edge and line detection.
If you added the entry to the correct info.plist file, but it still failed, it may be because Xcode sometimes fails to notice changes to resource files like info.plist, images, audio, movies, etc. You may need to perform a clean or delete the app build product from the Derived Data folder to force Xcode to use the latest version
Alternatively open Info.plist as source code and add this:
<key>NSCameraUsageDescription</key>
<string>Camera usage description</string>

Xcode - failing to validate archive due to weird issues with resolving variables/placeholders in info.plist and entitlements

Something is really broken with my project or Xcode and I'm struggling since two days to figure out what is going on.
My application is running perfectly fine, it is an OS X application and it contains a Today Widget extension. I finalized the project recently and now I'm unable to publish the app due to validation errors.
It started with the validator complaining about my Application Group, which I use to share content between the main application and the Widget extension. The group was defined as $(TeamIdentifierPrefix)com.acme.GreatApp.
The validator was complaining that my Group name was in the wrong format and should rather start with my TEAMID. After 3 hours of running in circles I replaced the placeholder $(TeamIdentifierPrefix) with my team ID (e.g. S3F45A5S35.) – and that worked.
Then the validator complained about every kind of other placeholder in my info.plist and entitlements files, things like $(PRODUCT_NAME), com.acme.GreatApp.$(PRODUCT_NAME:rfc1034identifier) and $(EXECUTABLE_NAME). I replaced all those placeholders with their corresponding values, which then was accepted.
Then, for a while the validator complained about structural issues, which do not make too much sense to me:
iTunes Store operation failed.
Bad CFBundleExecutable. Cannot find executable file that matches the value of CFBundleExecutable in the nested bundle GreatAppWidget [com.acme.GreatApp.pkg/Payload/GreatApp.app/Contents] property list file
iTunes Store operation failed.
Bad bundle identifier. The bundle identifier 'com.acme.GreatApp.GreatAppWidget' of the application extension GreatApp.app/Contents/Plugins/GreatAppWidget.appex should extend the dotted path of the bundle identifier of its containing application (com.acme.GreatApp.GreatAppWidget)
Are you telling me my extension is contained in my extension?
iTunes Store operation failed.
Invalid bundle location. Bundle GreatApp.app/Contents/Plugins/GreatAppWidget.appex must be contained in parent bundle's Contents/Plugins directory.
Well, the error states that I should do what it claims is not correct. Obviously it IS in the parent apps folder Contents/Plugins. So what?
iTunes Store operation failed.
The CFBundleExecutable of two bundles may not point to the same file. The following shared bundle path have been found: GreatApp.app/Contents.
The thing is, my app and extension work perfectly fine. If there was anything broken like dependencies, paths or whatever, something most likely wouldn't work, no?
Furthermore, I did not modify any of the build settings or just anything which I do not understand. These are all the default settings. I created a new Cocoa application, added a new Today Widget Extension as target - and that is it. I did not mess around with any paths, locations, dependencies, anything.
Anyway, those errors were only visible for a while and I'm lucky I made a screenshot then. Because now, again it is showing raw placeholders in the error messages, even though I didn't change a thing that could cause that. The only thing I can recall I did was to delete/revoke all provisioning profiles and certificates locally on disk, in the Keychain, and in the Member Center and created everything from scratch through Xcode.
Now the errors look like this:
iTunes Store operation failed.
Bad CFBundleExecutable. Cannot find executable file that matches the value of CFBundleExecutable in the nested bundle ${bundleName} [${bundlePath}] property list file
iTunes Store operation failed.
Bad bundle identifier. The bundle identifier '${bundleIdentifier}' of the application extension ${bundlePath} should extend the dotted path of the bundle identifier of its containing application (${parentBundleIdentifier})
iTunes Store operation failed.
Invalid bundle location. Bundle ${bundlePath} must be contained in parent bundle's ${pluginsSubPath} directory.
iTunes Store operation failed.
The CFBundleExecutable of two bundles may not point to the same file. The following shared bundle path have been found: ${sharedBundlePath}.
So I thought maybe Apple just fuped it in the recent update and you just can't validate an app with extension. So I searched the web and found the really nice example Today-Scripts. I added app groups ($(TeamIdentifierPrefix)com.acme.Today-Scripts, the placeholder here perfectly works) to all targets, activated sandboxing, changed certs and provisioning profiles: And it perfectly validates!
So I compared all the build setting etc. And except that I had to replace the placeholders with their actual values, obviously the certs and profiles and the fact that in Today-Scripts there is an additional XPC target which is embedded into the Widget, they are atomically the same. Every single setting is the same.
I cleared all caches, really hard. Cleaned the project, cleared ~/Library/Developer/Xcode/DerivedData as well as ~/Library/Caches/com.apple.dt.Xcode, restarted several times, nothing helps.
I'm really desperate as I have no idea left what I can do. Please someone shed some light on what the heck Xcode is doing here and why.
What causes Xcode to fail to interpret all the placeholders in the info.plist and entitlements files?
How can these bundle errors above happen when I am working with
the exact settings XCode created by creating new cocoa app and adding an extension target?
the exact same settings/dependencies as the Today-Scripts example?
I finally found the culprit! The problem was, for some reason I can not explain, the info.plist of the extension was a member (Target Membership) of the main application. I just found that by analyzing the created archive file by file and found an additional info.plist inside the GreatApp.app/Contents/Resources folder. In that file the placeholders were not resolved. The archive validator then used this info.plist for validation.
That little checkbox cost me 3 days.
I was getting
"iTunes Store operation failed. Bad CFBundleExecutable. Cannot find executable file that matches the value of CFBundleExecutable in the nested bundle ${bundleName} [${bundlePath}] property list file"
error when I try to submit my adobe air app to mac app store.
The executable is located at "/Contents/Frameworks/Adobe AIR.framework/Versions/1.0/Resources/AdobeCP15.plugin/Contents/MacOS/AdobeCP"
In info.plist file located at "/Contents/Frameworks/Adobe AIR.framework/Versions/1.0/Resources/AdobeCP15.plugin/Contents/info.plist"
it is written as
CFBundleExecutable
adobecp
These files are generated automatically but it took time to discover the case-sensitivity. Therefore, I changed it into
CFBundleExecutable
AdobeCP
and error message disappeared.
I ended up at this post because I had a similar error, but a different cause. My issue was caused by having a WatchKit app and having the 'Embed Watch Content' Build Phase after my 'Run Script' Phase. Once I moved the 'Embed Watch Content' Build Phase in between 'Copy Bundle Resources' and 'Embed Frameworks', the validation of the WatchKit succeeded.

Overlapping UTI declarations for same file extension

Users of my application keep running into issues related to other applications declaring a different UTI for the same file extension as my application. I think I can best describe this with an example:
My application supports importing and exporting OPML files. For this to work I import the org.opm.opml UTI in my info.plist. This mostly works fine, but fails if an other application declares a different UTI for the opml file extension. For example com.redlex.opml or com.xwavesoft.cloudoutliner.opml.
Is there something I can do to work around this problem? Can I somehow tell launch services that my application accepts all files with an .opml extension?
I solved this issue with the following work around:
I removed the entire LSItemContentTypes entry from the list of CFBundleDocumentTypes
In my NSDocument subclass I use NSString's rangeOfString:: method to look for the #"opml" keyword in the typeName parameter.
This work around was confirmed by a user.
Cloud Outliner updated on the MAS today, and looking at the UTI for .opml files now, the com.xwavesoft.cloudoutliner.opml is now gone and com.redlex.opml is now in its place. Which would be uninteresting, except for the fact that OPML files now open in MindNode without a problem.
I can't find any release notes for Cloud Outliner other than "various fixes and improvements." but it would seem that something changed for the better. So: in my case - the simple answer was "update Cloud Outliner" but I'm not sure this solves the larger problem. I also have Mellel on my system (obviously, right?!) but this doesn't seem to be causing the same issue.
For those watching this thread - the issue presented as MindNode showing greyed-out icons for OPML files in its open dialog. Dragging OPML to the MindNode icon in the dock also produced no results.
Rob

How to register a custom non-document UTI / file type for OSX to recognize it?

I've read all the tickets about this issue, but I still don't get it right. I have a non-document OSX app (for OSX Lion and MountainLion). I want this app to export and import custom data, associated with a custom file extension ".iobs". Internally, these files are just data archived with [NSKeyedArchiver archivedDataWithRootObject:], and saved onto the disk with the "iobs" extension.
So, my check list is this:
1) Export mechanism: checked. My app create correctly .iobs files. If I run "file <filename.iobs>" in the Terminal, I get "iObserve_exportedItems.iobs: Apple binary property list"
2) Declaration of an exported UTI, checked. As shown in the image below. I did NOT declared a custom Document type, since it I never use NSDocument inside my app, and there is no point. Anyway, I already tried and failed. I've tried also different combinations of "Conforms To" entries, but with no success.
3) Is there any 3rd point??? Do I need to start my app once to let the system know? I just ran it in Debug from Xcode so far, and this has no effect. So I guess my Info.plist is wrong, but I filled it from within Xcode4 interface, so???
Thanks for any help, hint, question, suggestion.
Ok, so apparently, I do have to declare a document type even if I don't specify a document class. See the attached screenshot. Note that leaving only the Document UTI doesn't work. I do need the two (exported UTI and document type). Note also that if I say it conforms to com.apple.binary-property-list, I don't have the right icon.
And for those who wonder, there is nothing to do to "register" a type (and its subsequent changes) apart from launching the app once.

Spotlight UI unable to find Core Data external record files inside Sandbox

I've followed the instructions to integrate spotlight into a non-document based Core Data application using Record-Level Indexing. The spotlight importer imports the files correctly and those records can be found using mdfind "some string".
The problem is the spotlight user interface – the magnifying glass in the top right – doesn't show these records when running inside the sandbox.
The external record files are created in ~/Library/Containers/[my app]/Data/Library/Caches/Metadata/CoreData/[my app]
Am I doing something wrong?
Comparing the results of mdimport -d2 [external record file] showed this extra property:
kMDItemSupportFileType = (
MDSystemFile
);
Adjusting the spotlight importer to remove this key seems to fix it. The results are now shown in the spotlight UI. I'm guessing it hides system files from the results.
[spotlightData removeObjectForKey:#"kMDItemSupportFileType"];

Resources