8

I store all my music on an external drive. I have iTunes set to start when I login. However, iTunes always starts before my encrypted drive has mounted, and so iTunes complains it can't find its library.

I've tried writing an Applescript which repeatedly checks to see if a drive is mounted, and when it is launches iTunes.

tell application "System Events" to set diskNames to name of every disk
set iTunesDiskName to "Media"
set iTunesDiskIsMounted to false
set iTunesLaunched to false
if iTunesDiskName is in diskNames then set iTunesDiskIsMounted to true


repeat while iTunesLaunched is false
    if iTunesDiskIsMounted is true then
        tell application "iTunes" to launch
        set iTunesLaunched to true
    end if
    delay 5
end repeat

This does work to launch iTunes if I run it with the drive mounted. However if I run it with the drive unmounted, it keeps running and never detects if the disk is subsequently mounted.

What am I doing wrong?

2
  • Should you re-evaluate the check for the disk names after the delay and before the end repeat? Otherwise it keeps rechecking a value already set to false...
    – Solar Mike
    Commented Jul 27, 2019 at 20:30
  • simply, how does iTunesLaunched ever get to be true. Explain. Commented Jul 27, 2019 at 21:29

4 Answers 4

3

This is a follow-up to TJ Luoma's answer, which I hope will fix the issues with the .plist definition that stops the job from being run in the correct manner.

From man launchd.plist:

WatchPaths <array of strings>

This optional key causes the job to be started if any one of the listed paths are modified.

IMPORTANT: Use of this key is highly discouraged, as filesystem event monitoring is highly race-prone, and it is entirely possible for modifications to be missed. When modifications are caught, there is no guarantee that the file will be in a consistent state when the job is launched.

StartOnMount <boolean>

This optional key causes the job to be started every time a filesystem is mounted.

Here is a revised .plist definition incorporating StartOnMount in favour of WatchPaths. It is currently labelled "local.startOnMount.iTunes", which, if unchanged, means the file should be saved at ~/Library/LaunchAgents/local.StartOnMount.iTunes.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>local.StartOnMount.iTunes</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/osascript</string>
        <string>-e</string>
        <string>if (list disks) contains "Media" then ¬
                run application id "com.apple.iTunes"</string>
    </array>
    <key>RunAtLoad</key>
    <false/>
    <key>StandardErrorPath</key>
    <string>/tmp/local.StartOnMount.iTunes.stderr</string>
    <key>StandardOutPath</key>
    <string>/tmp/local.StartOnMount.iTunes.stdout</string>
    <key>StartOnMount</key>
    <true/>
</dict>
</plist>

The ProgramArguments key can alternatively be more akin to TJ's original bash command:

<key>ProgramArguments</key>
<array>
    <string>/bin/bash</string>
    <string>-c</string>
    <string>[[ -d "/Volumes/Media" ]] &amp;&amp; open -jg -b "com.apple.iTunes"</string>
</array>

However, in testing, I found that, if iTunes is already running and is hidden, then callng the open command—even with the -j and/or -g option(s)—brings iTunes to the foreground. So I elected to do use osascript to perform the same actions, but in a more consistent manner.

This new .plist appears to resolve the issues, and only runs the job when a filesystem is mounted, which in turn only performs an action if it's the correct disk name. To replace:

cd ~/Library/LaunchAgents
launchctl unload com.tjluoma.itunes-on-mount.plist
launchctl load local.StartOnMount.iTunes.plist

However, if anything is not performing as expected, and it's not immediately obvious why, then look in the /tmp/ folder for file(s) named local.StartOnMount.iTunes.stderr and/or local.StartOnMount.iTunes.stdout:

cat local.StartOnMount.iTunes.stderr

EDIT: Post Catalina Bonus
Added by @dunxd on 2021-03-18

Apple split iTunes into separate apps (Music, TV and Podcasts) with the launch of Catalina. It is possible to adjust the above script to launch Music and TV in one go by adding extra strings to the ProgramArguments array like so:

<key>ProgramArguments</key>
<array>
    <string>/usr/bin/osascript</string>
    <string>-e</string>
    <string>if (list disks) contains "Media" then run application id "com.apple.music"</string>
    <string>-e</string>
    <string>if (list disks) contains "Media" then run application id "com.apple.TV"</string>
</array>

EDIT: Refinement of "Post Catalina Bonus"
Added by @CJK on 2021-04-17

<key>ProgramArguments</key>
<array>
    <string>/usr/bin/osascript</string>
    <string>-e</string>
    <string>
        if (list disks) does not contain "Media" then return
        run application id "com.apple.music"
        run application id "com.apple.TV"
    </string>
</array>
10
  • 1
    This works great, and doesn't require the presence of any extra files, so is much more transferable to other people, who just need to edit the name of the drive that houses their iTunes library. I've accepted this as the answer, as it is the one I'm now using. Thanks to TJ Luoma for his answer which moved things in the right direction.
    – dunxd
    Commented Aug 2, 2019 at 14:18
  • 1
    A month later, it is working very well. Thanks so much!
    – dunxd
    Commented Sep 9, 2019 at 13:15
  • 1
    This works great. I adapted it to run an rsync script to backup my photos library whenever I plug the appropriate disk in.
    – Mike Heinz
    Commented Mar 6, 2020 at 14:43
  • 1
    I figured out how to launch both Music and TV at the same time - added as an edit at the end of answer.
    – dunxd
    Commented Mar 18, 2021 at 22:44
  • 1
    @dunxd Well done for figuring it out. Sorry you went through the trouble: I started a reply on my phone, and it seems my Android settings are such that the current state of an app isn't saved once it becomes a background task. Anyway, I'll make a slight edit to your edit when I'm home, as calling the list disks function twice is going to take twice as long.
    – CJK
    Commented Mar 19, 2021 at 16:41
6

I didn't downvote wch1zpink's answer, but it's very much not the way that I would solve the problem.

Having an app run an AppleScript every 5 seconds is a very inefficient way to handle this situation, especially since Mac OS already has a built-in feature to do this, namely, launchd.

Save this as ~/Library/LaunchAgents/com.tjluoma.itunes-on-mount.plist (you can change the "com.tjluoma.itunes-on-mount" part to whatever you want):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.tjluoma.itunes-on-mount</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/bash</string>
        <string>-c</string>
        <string>test -d /Volumes/Media &amp;&amp; open -j -g -a iTunes</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>WatchPaths</key>
    <array>
        <string>/Volumes</string>
    </array>
</dict>
</plist>

Then either reboot your Mac or do


launchctl load ~/Library/LaunchAgents/com.tjluoma.itunes-on-mount.plist

in Terminal. Then, anytime /Volumes/ changes, launchd will look to see if /Volumes/Media exists, and if it does, it will launch iTunes.

11
  • Yes! This is one very good way to solve the OP's problem, and definitely how I would do it. Thank you for adding this solution. +1.
    – CJK
    Commented Jul 29, 2019 at 17:55
  • Thanks - this definitely works for me!
    – dunxd
    Commented Jul 30, 2019 at 17:43
  • I implemented it, and it worked but then iTunes started popping up and stealing focus every few minutes if it was minimised. I'm working out why and will revert when I figure it out.
    – dunxd
    Commented Jul 30, 2019 at 17:56
  • 1
    That addition of LaunchOnlyOnce seems to have resolved the issue - @tj-luoma please let me know your thoughts.
    – dunxd
    Commented Jul 30, 2019 at 18:28
  • 1
    I hope I'm not stepping on TJ's toes, but I've posted a revised .plist definition that I believe resolves the issues. If there are any further problems, let one of us know. Alternatively, there's still a third option available to you that is still active on my system and, for me, been extremely consistent.
    – CJK
    Commented Jul 31, 2019 at 14:35
3

A stay-open application (Polling)

Although this question has already been marked as having a satisfactory solution, I'm compelled to provide an alternative. I completely agree with @TJ Luoma, who has provided, what I believe to be, by far the optimal solution, noting that a Stay Open application that polls for disk names every 5 seconds, and has to call out to an external application to do this, is not the way to approach this problem. It's inefficient, resource intensive, and entirely cumbersome to have an applet live permanently in the dock, or even just invisibly in the background, performing only one function in a terrible way, when macOS has a built-in means to monitor file system events, and a service manager to respond to these events and perform tasks in a scheduled and efficient manner.

launchd

TJ Luoma's method takes advantage of launchd to solve the problem in what is, as far as I am aware, the recommended way. It monitors for changes in the /Volumes folderpath, and acts accordingly. It's the method I would choose and know I didn't have to worry about what was going on behind the scenes.

Folder Actions

Some might not be aware that macOS's Folder Actions can also monitor the /Volumes folder, and if AppleScript feels more at home to someone than a .plist and a shell script, then this solution may be more appealing. As far as I'm aware, folder actions actually run as a process under launchd, so although they operate in overtly different ways to the user, the underlying mechanisms are possibly the same, or similar, and one is not likely to see any performance differences between the two.

Implementation

Setting up is very easy, even if you haven't done it before. It involves two steps:

  1. Save the following AppleScript, naming it anything you want, and saving it in the Folder Action Scripts folder located at ~/Library/Scripts/Folder Action Scripts. I saved mine as a plain .applescript text file using Script Editor, and named it New Volume Mounted.applescript:

    property diskName : "Media" -- whatever the disk is named
    property _objRef : a reference to reference
    
    
    script disks
        property path : missing value
        property kind : "public.volume"
        property list : missing value
    
        on _get(mountPoints)
            local mountPoints
    
            set my list to mountPoints
    
            tell application id "com.apple.systemevents" to ¬
                repeat with fURL in (a reference to my list)
                    if fURL's type identifier = my kind ¬
                        then set fURL's contents to ¬
                        the disk named fURL
                end repeat
    
            return every reference in my list
        end _get
    
        on _eval(key)
            if my list = missing value then return
    
            tell application id "com.apple.systemevents"
                tell {}
                    repeat with _disk in (a reference to my list)
                        set _objRef to _disk
                        set the end to contents of the key
                    end repeat
    
                    return it
                end tell
            end tell
        end _eval
    end script
    
    
    on adding folder items to mountedVolumes after receiving mountPoints
        local mountedVolumes, mountPoints
    
        disks's _get(mountPoints)
    
        set propertyKey to a reference to the name of my _objRef
        set disknames to disks's _eval(propertyKey)
    
        if the diskname is in the disknames then exec()
    end adding folder items to
    
    
    on removing folder items from mountedVolumes after losing diskNames
        local mountedVolumes, diskNames
    
        -- display alert "Disk removed: " & diskNames
    end removing folder items from
    
    
    on exec()
        -- display alert "Disk attached"
        run application id "com.apple.iTunes"
    end exec
    
  2. There is a conventional method to pair a folder action script with a watched folder, but by far the simplest is to copy and paste the script below into Script Editor and, if necessary, edit the third line the declares property script. This wants to be set to the same name (with file extension) as your script above. If you chose the same name as I did in the first step, you don't need to make any adjustments at all. Simply run the script and everything will be set up, taking immediate effect. There's no need to save this script, and no need to restart the computer.

    use sys : application id "com.apple.systemevents"
    
    property path : "/Volumes"
    property script : "New Volume Mounted.applescript"
    
    property name : a reference to the name of folder path
    property folder action : a reference to folder action named (my name)
    property folder : a reference to Folder Action scripts folder
    property file : a reference to the file named (my script) in my folder
    
    set folder actions enabled to true
    
    if not (my file exists) then return open my folder
    
    tell my folder action
        if (exists) then return its scripts
    
        make new folder action with properties my {name:name, path:path}
        make new script with properties my file's ¬
            {name:name, POSIX path:POSIX path}
    
        set [its enabled, its scripts's enabled] to [true, true]
    end tell
    

    This script has only been tested in High Sierra

Postscript

The script in step 1 above is one I wrote a while back as a generalised script that those who are comfortable with AppleScript could easily adapt and extend to perform whatever tasks one wishes, and already have available a list of all new mount points created in /Volumes in the form of a validated list of disk objects.

It is, however, actually more than is necessary for this task at hand. You are welcome to stick to it, and it ought to work just fine. But, if you would prefer a simpler script, then this one below performs exactly the same function as the one above, but with all the fat trimmed away:

property diskName : "Media" -- whatever the disk is named
property path : "/Volumes"
property diskVolumePath : [path, "/", diskName, "/"] as text


on adding folder items to mountedVolumes after receiving mountPaths
    local mountedVolumes, mountPoints

    repeat with mountPath in mountPaths
        set mountPath's contents to ¬
            mountPath's POSIX path
    end repeat

    if the mountPaths contains the diskVolumePath then exec()
end adding folder items to


on removing folder items from mountedVolumes after losing diskNames
    local mountedVolumes, diskNames

    -- display alert "Disk removed: " & diskNames
end removing folder items from


on exec()
    -- display alert "Disk attached"
    run application id "com.apple.iTunes"
end exec
4
  • 1
    Thanks for the additional info on Folder Actions. I like the plist option better, but this may come in useful in future.
    – dunxd
    Commented Jul 30, 2019 at 17:44
  • I’m glad you are going with the .plist option. It’s how I would I do it too.
    – CJK
    Commented Jul 31, 2019 at 11:16
  • currently the plist option is causing some problems - see comments under the other answer.
    – dunxd
    Commented Jul 31, 2019 at 11:29
  • OK, I think I can see why, Bear with me and I'll update you with a fix on behalf of TJ.
    – CJK
    Commented Jul 31, 2019 at 14:00
1

Save this following AppleScript code as a "stay open" application in Script Editor.app. Disable iTunes starting up at login but enable your new "stay open" application to start at login.

Be sure to grant appropriate permissions in System Preferences for your new applet to control your computer.

on run
    --  Executed when the script is launched
    checkForMedia()
end run

on idle
    -- checks every five seconds if diskNames contains "Media"
    checkForMedia()
    return 5 -- in seconds
end idle

on checkForMedia()
    tell application "System Events" to set diskNames to name of every disk
    if diskNames contains "Media" then
        tell application "iTunes" to launch
        quit me -- Quits this applet after diskNames contains "Media" and launches itunes
    end if
end checkForMedia

on quit
    continue quit -- allows the script to quit
end quit
8
  • 1
    It works very well - in fact it seems to launch iTunes faster at boot, and in a working state, than when it was directly enabled as a login item. Thanks very much.
    – dunxd
    Commented Jul 28, 2019 at 21:53
  • The odd thing is (especially since my code efficiently solved your problem)... Someone downvoted my solution with absolutely no explanation as to why.
    – wch1zpink
    Commented Jul 28, 2019 at 22:06
  • The quit handler is not needed, as it only goes on to continue quit, anyway. Well done for using System Events and not Finder to retrieve the disk volumes. Rather than enumerate every disk, you could simply: tell app "System Events" to if disk "Media" exists then. Cont'd...
    – CJK
    Commented Jul 29, 2019 at 17:42
  • ...Cont'd However, this is not the way to solve the OP's issue. As TJ Luoma points out, it is incredibly inefficient: you've created an AppleScript process that now exists permanently in the background, and this is polling the names of every computer disk by calling out to an external application every 5 seconds.No! I didn't downvote, but I'm tempted. However, the script itself is fine and, in fairness, it took what the OP was trying to do and showed him how to do it. But even if better solutions didn't exist (and they do), I still wouldn't do it this way. @dunxd, avoid please.
    – CJK
    Commented Jul 29, 2019 at 17:47
  • @CJK Wow… you guys are just way too critical. It's not like my solution is meant to just continually run in the background, chewing resources. I chose the solution only for this particular circumstance (only because the external disk containing the media and iTunes were both set to be enabled at login). It was only meant to buy a few extra seconds for the external disk to be mounted before iTunes started looking for the library. So, really, all of this fuss over a stay open application which is meant to only run for a couple seconds?
    – wch1zpink
    Commented Jul 30, 2019 at 16:13

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .