Finding other apps' paths - deterministically!

  • The methods for finding applications,

        -[NSWorkspace fullPathForApplication:]
        -[NSWorkspace absolutePathForAppBundleWithIdentifier:]
        AppleScript's  'path to application'

    all return only ONE result.  If there is more than installation of the
    application, which often happens out here in real life, the above
    methods will often pick different installations, and even if you run
    the same methods several minutes apart I sometimes get different
    answers.

    Is there any way to make these methods behave deterministically?  If
    not, what's a better way?  Do I have to do a Spotlight search?  I know
    that unix 'find' would be way too slow.

    Thanks,

    Jerry Krinock
  • On Sep 18, 2008, at 1:47 PM, Jerry Krinock wrote:

    > The methods for finding applications,
    >
    > -[NSWorkspace fullPathForApplication:]
    > -[NSWorkspace absolutePathForAppBundleWithIdentifier:]
    > AppleScript's  'path to application'
    >
    > all return only ONE result.  If there is more than installation of
    > the application, which often happens out here in real life, the
    > above methods will often pick different installations, and even if
    > you run the same methods several minutes apart I sometimes get
    > different answers.
    >
    > Is there any way to make these methods behave deterministically?  If
    > not, what's a better way?  Do I have to do a Spotlight search?  I
    > know that unix 'find' would be way too slow.

    The above methods all depend on Launch Services.  I've seen the same
    non-deterministic behavior that you have.

    One solution, which is a complete hack, involves tagging the
    application with a claim to handle a certain custom URL scheme.  Then
    you can use LSCopyApplicationURLsForURL with a fake URL of that
    scheme.  That's the only Launch Services API that I'm aware of that
    actually returns a list of applications (rather than, for example, a
    list of application bundle IDs which doesn't address your problem).
    This, of course, only works if the application is one you're
    developing rather than a third-party app.

    Cheers,
    Ken
  • On Sep 18, 2008, at 8:47 PM, Jerry Krinock wrote:

    > The methods for finding applications,
    >
    > -[NSWorkspace fullPathForApplication:]
    > -[NSWorkspace absolutePathForAppBundleWithIdentifier:]
    > AppleScript's  'path to application'
    >
    > all return only ONE result.  If there is more than installation of
    > the application, which often happens out here in real life, the
    > above methods will often pick different installations, and even if
    > you run the same methods several minutes apart I sometimes get
    > different answers.
    >
    > Is there any way to make these methods behave deterministically?  If
    > not, what's a better way?  Do I have to do a Spotlight search?  I
    > know that unix 'find' would be way too slow.
    >
    > Thanks,
    >
    > Jerry Krinock

    Use NSTask with the command line find -x /  -name '*.app'
    Do this on a separate thread, and cache the result.
    > ------

    What is a woman that you forsake her, and the hearth fire and the home
    acre,
    to go with the old grey Widow Maker.  --Kipling, harp song of the Dane
    women
    Tommy Nordgren
    <tommy.nordgren...>
  • On Sat, Sep 20, 2008 at 12:57 PM, Tommy Nordgren
    <tommy.nordgren...> wrote:
    > Use NSTask with the command line find -x /  -name '*.app'
    > Do this on a separate thread, and cache the result.

    I highly recommend against this approach. One problem is that it will
    fail badly if any of the returned paths contain the \n character,
    which is a perfectly legal path character. For another it will fail if
    the new process would exceed the user's process limit, a limit which
    tends to be much lower than most other resource limits and thus much
    easier to encounter in realistic situations.

    For the original problem, I'd recommend using something like
    LSCopyApplicationURLsForURL() if it's at all possible. Of course maybe
    you have data that isn't good for that, but if you can use it then
    that's the way to go.

    If you must search the disk, use Spotlight if you can. It will be
    vastly faster than anything else. The downside is that it won't work
    if indexing is disabled or if the desired application is in an
    excluded directory.

    If you must search the whole disk without Spotlight, use
    NSDirectoryEnumerator. Or if you're the sort who likes to use
    unsupported private functions, you may be interested in the discussion
    near the bottom of http://www.cocoadev.com/index.pl?AllApplications

    Mike
  • On Sep 21, 2008, at 6:36 AM, Michael Ash wrote:

    > On Sat, Sep 20, 2008 at 12:57 PM, Tommy Nordgren
    > <tommy.nordgren...> wrote:
    >> Use NSTask with the command line find -x /  -name '*.app'
    >> Do this on a separate thread, and cache the result.
    >
    > I highly recommend against this approach. One problem is that it will
    > fail badly if any of the returned paths contain the \n character,
    Not true. With the given command line, find will return absolute paths.
    So this anomaly is easily detected when parsing find output
    >
    > which is a perfectly legal path character. For another it will fail if
    > the new process would exceed the user's process limit, a limit which
    > tends to be much lower than most other resource limits and thus much
    > easier to encounter in realistic situations.
    Firstly you can just increase the soft limit. Hard limits on Mac OS X
    is very generous.
    Secondly it would fail because maximum number of processes have been
    reached,
    you can simply retry later.
    I have never had a process break because it has reached the maximum
    allowed cpu-time,
    except in situations where I've deliberately set it very low,
    >
    >
    > For the original problem, I'd recommend using something like
    > LSCopyApplicationURLsForURL() if it's at all possible. Of course maybe
    > you have data that isn't good for that, but if you can use it then
    > that's the way to go.
    >
    > If you must search the disk, use Spotlight if you can. It will be
    > vastly faster than anything else. The downside is that it won't work
    > if indexing is disabled or if the desired application is in an
    > excluded directory.
    >
    Using Spotlight to search for applications won't work, because some
    open-source
    packages install apps in directories that's not searched by spotlight,
    For example - the default install path for Qt is in directory
    /usr/local/Trolltech/Qt-<version-nr>
    > If you must search the whole disk without Spotlight, use
    > NSDirectoryEnumerator. Or if you're the sort who likes to use
    > unsupported private functions, you may be interested in the discussion
    > near the bottom of http://www.cocoadev.com/index.pl?AllApplications
    >
    > Mike

    > -----------------------------------

    See the amazing new SF reel: Invasion of the man eating cucumbers from
    outer space.
    On congratulations for a fantastic parody, the producer replies :
    "What parody?"

    Tommy Nordgren
    <tommy.nordgren...>
  • Tommy Nordgren wrote:
    >
    > On Sep 21, 2008, at 6:36 AM, Michael Ash wrote:
    >
    >> On Sat, Sep 20, 2008 at 12:57 PM, Tommy Nordgren
    >> <tommy.nordgren...> wrote:
    >>> Use NSTask with the command line find -x /  -name '*.app'
    >>> Do this on a separate thread, and cache the result.
    >>
    >> I highly recommend against this approach. One problem is that it will
    >> fail badly if any of the returned paths contain the \n character,
    > Not true. With the given command line, find will return absolute paths.
    > So this anomaly is easily detected when parsing find output
    >>

    Don't do find /, please! It could end up looking through every directory
    entry on a remote petabyte filesystem. Even if you restrict it to
    "local" storage, you could be going to the network for iSCSI devices,
    and if you avoid that, some people have multi-terabyte filesystems with
    a lot of directory entries, they will be disappointed in any software
    that does a find /.

    Peter
  • Peter O'Gorman wrote:
    > Tommy Nordgren wrote:
    >> On Sep 21, 2008, at 6:36 AM, Michael Ash wrote:
    >>
    >>> On Sat, Sep 20, 2008 at 12:57 PM, Tommy Nordgren
    >>> <tommy.nordgren...> wrote:
    >>>> Use NSTask with the command line find -x /  -name '*.app'

    Dunno how I missed the -x, does not matter though, find -x / on a large
    filesystem with many files will take too long.

    Peter

    >>>> Do this on a separate thread, and cache the result.
    >>> I highly recommend against this approach. One problem is that it will
    >>> fail badly if any of the returned paths contain the \n character,
    >> Not true. With the given command line, find will return absolute paths.
    >> So this anomaly is easily detected when parsing find output
    >
    > Don't do find /, please! It could end up looking through every directory
    > entry on a remote petabyte filesystem. Even if you restrict it to
    > "local" storage, you could be going to the network for iSCSI devices,
    > and if you avoid that, some people have multi-terabyte filesystems with
    > a lot of directory entries, they will be disappointed in any software
    > that does a find /.
  • On Sep 21, 2008, at 11:25 PM, Peter O'Gorman wrote:

    > Peter O'Gorman wrote:
    >> Tommy Nordgren wrote:
    >>> On Sep 21, 2008, at 6:36 AM, Michael Ash wrote:
    >>>
    >>>> On Sat, Sep 20, 2008 at 12:57 PM, Tommy Nordgren
    >>>> <tommy.nordgren...> wrote:
    >>>>> Use NSTask with the command line find -x /  -name '*.app'
    >
    > Dunno how I missed the -x, does not matter though, find -x / on a
    > large
    > filesystem with many files will take too long.
    >
    > Peter

    Sure, but you don't need to do the scan every time. See the comment
    on caching.

    >
    >
    >>>>> Do this on a separate thread, and cache the result.
    >>>> I highly recommend against this approach. One problem is that it
    >>>> will
    >>>> fail badly if any of the returned paths contain the \n character,
    >>> Not true. With the given command line, find will return
    >>> absolute paths.
    >>> So this anomaly is easily detected when parsing find output
    >>
    >> Don't do find /, please! It could end up looking through every
    >> directory
    >> entry on a remote petabyte filesystem. Even if you restrict it to
    >> "local" storage, you could be going to the network for iSCSI devices,
    >> and if you avoid that, some people have multi-terabyte filesystems
    >> with
    >> a lot of directory entries, they will be disappointed in any software
    >> that does a find /.

    ------
    What is a woman that you forsake her, and the hearth fire and the home
    acre,
    to go with the old grey Widow Maker.  --Kipling, harp song of the Dane
    women
    Tommy Nordgren
    <tommy.nordgren...>
  • On Sun, Sep 21, 2008 at 1:52 PM, Tommy Nordgren
    <tommy.nordgren...> wrote:
    >
    > On Sep 21, 2008, at 6:36 AM, Michael Ash wrote:
    >
    >> On Sat, Sep 20, 2008 at 12:57 PM, Tommy Nordgren
    >> <tommy.nordgren...> wrote:
    >>>
    >>> Use NSTask with the command line find -x /  -name '*.app'
    >>> Do this on a separate thread, and cache the result.
    >>
    >> I highly recommend against this approach. One problem is that it will
    >> fail badly if any of the returned paths contain the \n character,
    >
    > Not true. With the given command line, find will return absolute
    > paths.
    > So this anomaly is easily detected when parsing find output

    And what if it's the last character in a directory name?

    >> which is a perfectly legal path character. For another it will fail if
    >> the new process would exceed the user's process limit, a limit which
    >> tends to be much lower than most other resource limits and thus much
    >> easier to encounter in realistic situations.
    >
    > Firstly you can just increase the soft limit. Hard limits on Mac OS X
    > is very generous.
    > Secondly it would fail because maximum number of processes have been
    > reached,
    > you can simply retry later.
    > I have never had a process break because it has reached the maximum
    > allowed cpu-time,
    > except in situations where I've deliberately set it very low,

    You seem to have misunderstood the limit I'm talking about. I'm
    talking about the limit on the number of processes, not anything else.
    On my system, the number of processes per user is set at 266, and this
    appears to be a hard limit. That limit is *extremely* easy to reach,
    especially if you're running a bunch of poorly-written apps that spawn
    subprocesses when they don't need to.

    Retrying later is just silly. The amount of code to run 'find' with an
    NSTask, read all its output, make some attempt at correcting \n
    embedded in paths, and retry when the process can't spawn because
    you've hit the process limit, is far more than the amount of code
    you'd have to write to just directly iterate over the filesystem using
    NSDirectoryEnumerator. What's the advantage supposed to be?

    >> For the original problem, I'd recommend using something like
    >> LSCopyApplicationURLsForURL() if it's at all possible. Of course maybe
    >> you have data that isn't good for that, but if you can use it then
    >> that's the way to go.
    >>
    >> If you must search the disk, use Spotlight if you can. It will be
    >> vastly faster than anything else. The downside is that it won't work
    >> if indexing is disabled or if the desired application is in an
    >> excluded directory.
    >>
    > Using Spotlight to search for applications won't work, because some
    > open-source
    > packages install apps in directories that's not searched by spotlight,
    > For example - the default install path for Qt is in directory
    > /usr/local/Trolltech/Qt-<version-nr>

    That's why I pointed this out when I suggested it.

    Mike
  • Incidentally, this is something Microsoft's Office 2008 update
    installers do.  If you're not careful you can walk away and come back
    hours later with the search still running.

    Jeffrey R. Kelley
    <slauncha...>
    ITCS - Campus Computing Sites
    University of Michigan

    On Sep 21, 2008, at 3:49 PM, Peter O'Gorman wrote:

    > Don't do find /, please! It could end up looking through every
    > directory
    > entry on a remote petabyte filesystem.
  • Michael Ash wrote:

    > For the original problem, I'd recommend using something like
    > LSCopyApplicationURLsForURL() if it's at all possible.

    Good idea but I'm looking for a helper app which is not registered to
    open any URLs.  Also, I don't see any indication that
    LSCopyApplicationURLsForURL() would return multiple installations of
    the same app.

    > If you must search the disk, use Spotlight if you can. ... The
    > downside is that it won't work if indexing is disabled or if the
    > desired application is in an
    > excluded directory.

    Also, it seems to not find helper applications which are inside other
    applications' packages.

    On 2008 Sep, 21, at 15:08, Michael Ash wrote:

    > On my system, the number of processes per user is set at 266, and this
    > appears to be a hard limit. That limit is *extremely* easy to
    > reach ...

    Amen.  My rule is: "Use NSTask, or spawn processes, as little as
    possible".

    EPILOG

    The programmatic Spotlight search executes quickly and was fairly easy
    to write, but then I found it didn't look inside packages.  So I ended
    up using Carbon's File Manager.  It's not as fast as Spotlight, and
    writing the code was not a cup of Cocoa, but it's done and has been
    working in my tests for the last week or so.

    I've posted the classes and demos for both solutions in case anyone
    has similar needs.  They're fairly well-documented with HeaderDoc.

    http://www.sheepsystems.com/sourceCode/fileSysSearch.html
  • On Tue, Sep 30, 2008 at 1:00 PM, Jerry Krinock <jerry...> wrote:
    > Michael Ash wrote:
    >
    >> For the original problem, I'd recommend using something like
    >> LSCopyApplicationURLsForURL() if it's at all possible.
    >
    > Good idea but I'm looking for a helper app which is not registered to open
    > any URLs.  Also, I don't see any indication that
    > LSCopyApplicationURLsForURL() would return multiple installations of the
    > same app.

    Note that if you give it a file URL, it will look up the application
    based on extension, UTI, HFS file type, etc. and not just the URL
    scheme. So your helper doesn't need to be registered to open URLs,
    just documents. Of course if it isn't registered to open documents
    then this doesn't work so great.

    As for multiple installations, I don't see any evidence that it
    *wouldn't* return multiple installations, and it's a fair assumption
    that it would based on the probability that the Finder uses this API
    for things like the Open With... menu. It's a moot point if the helper
    can't open documents, but if it does then it would bear testing.

    >> If you must search the disk, use Spotlight if you can. ... The downside is
    >> that it won't work if indexing is disabled or if the desired application is
    >> in an
    >> excluded directory.
    >
    > Also, it seems to not find helper applications which are inside other
    > applications' packages.

    Craptacular! Anyway, I'm glad you found an acceptable solution in the end.

    Mike
  • On Sep 30, 2008, at 12:00 PM, Jerry Krinock wrote:

    > Also, it seems to not find helper applications which are inside
    > other applications' packages.

    Is there any reason why you couldn't just search for the main
    application which contains the helper application inside its bundle
    and then just go from there to find the helper?

    Charles
  • On 2008 Sep, 30, at 16:47, Charles Srstka wrote:

    > On Sep 30, 2008, at 12:00 PM, Jerry Krinock wrote:
    >
    >> Also, it seems to not find helper applications which are inside
    >> other applications' packages.
    >
    > Is there any reason why you couldn't just search for the main
    > application which contains the helper application inside its bundle
    > and then just go from there to find the helper?

    OK, do a programmatic Spotlight search for the main application, get
    the path and tack on /Contents/Resources/MyHelper.app.  I hadn't
    thought about that but, yes, it that would overcome the package
    problem, Charles.

    However it still wouldn't work for users who've turned off Spotlight
    indexing or excluded stuff in their Spotlight prefs.  So I'm happy
    with my Carbon File Manager search, although admittedly not happy with
    the several days I spent getting the stupid thing working.
  • On 2008 Sep, 30, at 12:50, Michael Ash wrote:

    > Of course if [your helper] isn't registered to open documents
    > then this doesn't work so great.

    Yes, that's the problem.  It's a background kind of a guy.  It doesn't
    open documents of any kind.

    > As for multiple installations, I don't see any evidence that
    > [LSCopyApplicationURLsForURL()]
    > *wouldn't* return multiple installations, and it's a fair assumption
    > that it would based on the probability that the Finder uses this API
    > for things like the Open With... menu.

    Well, based on the fact that Finder only launches one app, I was
    expecting the opposite.  But since I started this thread, I did a
    test, and to my surprise, Michael is correct!

    I copied OmniOutliner.app to my desktop, gave
    LSCopyApplicationURLsForURL the url of an OmniOutliner document, and
    it found both copies of the app...

      NSURL* url = [NSURL fileURLWithPath:@"/Users/jk/Desktop/
    Firefox3Database.oo3"] ;
      CFArrayRef finds = LSCopyApplicationURLsForURL (
                                                      (CFURLRef) url,
                                                      kLSRolesAll
                                                      ) ;
      NSLog([(NSArray*)finds description]) ;

    *** Output:

    (
        file://localhost/Applications/OmniOutliner.app/,
        file://localhost/Users/jk/Desktop/OmniOutliner.app/
    )

    So, this is very easy solution, for finding document-based apps.
  • On Oct 1, 2008, at 2:47 PM, Jerry Krinock wrote:

    >
    > On 2008 Sep, 30, at 16:47, Charles Srstka wrote:
    >
    >> On Sep 30, 2008, at 12:00 PM, Jerry Krinock wrote:
    >>
    >>> Also, it seems to not find helper applications which are inside
    >>> other applications' packages.
    >>
    >> Is there any reason why you couldn't just search for the main
    >> application which contains the helper application inside its bundle
    >> and then just go from there to find the helper?
    >
    > OK, do a programmatic Spotlight search for the main application, get
    > the path and tack on /Contents/Resources/MyHelper.app.  I hadn't
    > thought about that but, yes, it that would overcome the package
    > problem, Charles.

    Actually, it'd be better to use +[NSBundle bundleWithPath:] and then
    use that to find MyHelper.app rather than just tacking on the subpath.
    You are correct that this would not work if users had excluded a
    directory from the search, though.

    Charles
previous month september 2008 next month
MTWTFSS
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30          
Go to today