X

AppleScript Fun: Automatically changing Safari's downloads folder

Here is a fun approach for automatically changing Safari's downloads folder to an external drive that uses AppleScript. If you like scripting on a Mac then this may be for you.

Topher Kessler MacFixIt Editor
Topher, an avid Mac user for the past 15 years, has been a contributing author to MacFixIt since the spring of 2008. One of his passions is troubleshooting Mac problems and making the best use of Macs and Apple hardware at home and in the workplace.
Topher Kessler
9 min read

While the various programs and services on a computer are meant to offer you convenience, there are times when you may need to do repetitive tasks using the various tools and programs on the system. In these cases, the use of scripting tools can be exceptionally useful. Unlike programming, scripting is when you take a set of current tools and utilities and automate their interactions with commands. For instance, with Javascript you can tell a browser to change the way it renders HTML content, or with Automator you can tell the Finder to organize files in a certain way.

Safari's Preferences showing the downloads folder
The only way to change Safari's downloads folder when the program is running is through the Preferences, but AppleScript can be used to do this automatically.

Recently a colleague here at CNET approached me with a unique situation, where he wanted to have his Mac automatically change Safari's downloads folder based on the presence of an external drive. The idea here would be to have Safari place downloaded files in the default Downloads directory, but when you attach a specific external drive (such as a thumbdrive called "Downloads"), then to have the system automatically switch Safari's downloads directory to the external drive, since it may be cumbersome to keep changing it manually.

Unfortunately Safari itself does not have the ability to do this, but OS X does have resources that can be used for this and be scripted using AppleScript for managing the Safari application and some OS X services, Shell scripting to perform some actions that do not use the application interface, and the system launcher for only running the scripts when a drive is attached.

Initially when thinking about this problem the solution may seem fairly straightforward, and the following scenario may seem like a good answer:

  1. Set up a script to run the command "defaults write com.apple.Safari DownloadFolder NAME" to change the downloads folder to the location specified by "NAME"
  2. Run this script using the system launcher only when a specific drive is attached.
  3. Repeat this with a similar command pointing to a different location when the drive is no longer attached.
Safari Preferences file showing download folder setting
If you manually edit the Download folder path in the Safari Preferences file when Safari is open, the change will not take effect and the program will revert it when closed.

While this basic approach may seem reasonable, it will not work for a number of reasons. First, once Safari is running then the only way to change its preferences is through the program itself. When the program loads it will read the preferences into RAM and manage its settings there, so if you edit the preferences file using TextEdit or the "defaults" command as mentioned above, then it will have no effect on the program. Furthermore, Safari writes its preferences back to disk when quit, which would overwrite any altered settings.

In addition, this approach assumes the system has resources available that will monitor the presence of a specific drive and trigger the script whenever that drive is present, which is not the case. The system can monitor whether or not drives are attached, but additional scripting would be needed to specify a drive name to check whenever a drive is attached.

Finally, there are a number of nuances to consider. For instance, since Safari itself needs to be used to change its preferences when running, doing so will force it to be the foremost application, where it will remain unless you first have the script detect the foremost application and restore it when the modifications to Safari are complete. Additionally, you do not want the script to run and swtich the active program around when any random drive is attached.

Because of these, the script would need to include the following five considerations:

  1. Launch only when a new drive is attached
  2. Only change settings if a drive named "Downloads" (or any desried name) is attached
  3. Be conditional in its approach for changing settings on whether or not Safari is currently launched
  4. Only change settings if a new desired download location is present
  5. Have the ability to restore the foremost application if the script needs to switch to Safari

To implement these options, two scripts would need to be created. The first is for the system launcher "launchd" which would be used to monitor the system's drive mount points, to see whether or not a new drive has been attached and mounted. If so, then it will launch the script that will check the drive name, see whether or not Safari is running, and then change Safari's settings only if they differ from primarily choosing the external drive but if it's not present then use the default Downloads folder.


Script 1: LaunchAgent for monitoring drives

The first script to make is for the system launcher, which is called a Launch Agent. To create it, open the Terminal and type the following command (copy and paste it for simplicity):

touch ~/Library/LaunchAgents/local.ChangeSafariDownload.plist

When this is done, open it in TextEdit by running the following command:

open -e ~/Library/LaunchAgents/local.ChangeSafariDownload.plist

You should now have a blank TextEdit document open, so copy the following text into it and save the document, but keep it open for now. You will need to change the "FILEPATH" section to change the script's location:


<?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.ChangeSafariDownloads</string>
	<key>QueueDirectories</key>
	<array/>
	<key>WatchPaths</key>
	<array>
		<string>/Volumes</string>
	</array>
	<key>Program</key>
	<string>/usr/bin/osascript</string>
	<key>ProgramArguments</key>
	<array>
		<string>osascript</string>
		<string>FILEPATH</string>
	</array>
</dict>
</plist>

When you log in, this script will be loaded into the system launcher process and give it conditions and instructions for run the main script for editing Safari's preferences. In this case, the launcher will be told to watch the directory /Volumes to see if a any changes occur within that directory. Since this is where filesystems are mounted, anytime a drive is attached or detached from the system this will trigger the main script to run.


Script 2: AppleScript for changing settings

The second script is the main one that will perform all the actions, including checking drive names, whether or not Safari is open, and any other conditions for altering the preferences. To create it, go to the /Applications/Utilities/ folder and open the AppleScript Editor program. A blank editor window will open, and when it does copy and paste the following script into it:


-- Set alternative drive name to use as downloads folder here
set dlFolder to convertHome("/Volumes/Downloads")

tell application "Finder"
     if exists [POSIX file dlFolder] then
          -- Do Nothing and use the folder
     else
          -- set dlFolder to default home directory
          set dlFolder to (POSIX path of (path to home folder as text)) & "Downloads"
     end if
end tell

-- get current path from safari plist
set currentpath to convertHome(do shell script "defaults read com.apple.Safari DownloadsPath")

-- change path only if the current setting does not match the input path (there is something to change)
if (currentpath as text) is not equal to (dlFolder as text) then
     -- if Safari is running, change settings through Safari's preferences
     if appIsRunning("Safari") then
          -- determine the frontmost application to restore when script is done
          set frontApp to path to frontmost application as text
          -- copy the download folder path to the clipboard
          set the clipboard to dlFolder as text
          activate application "Safari"
          tell application "System Events" to tell application process "Safari"

               -- open the preferences or bring to front (window 1)
               keystroke "," using {command down}
               delay 1 -- pauses in window display may result in an error, so we wait a second

               -- Change to the General tab
               click button "General" of tool bar 1 of window 1

               -- Change download location to Other
               tell pop up button "Save downloaded files to:" of group 1 of group 1 of window "General"
                    click
                    click menu item "Other..." of menu 1 -- three dots is an ellipsis, not three periods
               end tell

               -- Open the "go to folder" dialogue box
               keystroke "g" using {shift down, command down}

               -- Paste the folder path into the location field
               tell window "Go To Folder"
                    keystroke "v" using {command down}
                    keystroke return
               end tell
               click button "Select" of sheet 1 of window "General"

               -- close the preferences
               keystroke "w" using {command down}
          end tell
          -- restore frontmost application after job is complete
          tell application frontApp to activate
     else
          -- if Safari is NOT running, then use the defaults command to change the preferences
          do shell script ("defaults write com.apple.Safari DownloadsPath " & dlFolder)
     end if
end if

-- Function to convert home path references to full paths
on convertHome(currentpath)
     if currentpath contains "~/" then
          set oldDelimiter to AppleScript's text item delimiters
          set AppleScript's text item delimiters to {"~/"}
          set textPath to every text item of currentpath
          set AppleScript's text item delimiters to oldDelimiter
          set currentpath to POSIX path of (path to home folder as text) & item 2 in textPath
     end if
     return currentpath
end convertHome

-- Function to check if application "appName" is running (in this case, used for Safari)
on appIsRunning(appName)
     tell application "System Events" to (name of processes) contains appName
end appIsRunning

The script's text will initially be purple, but when you click the Compile toolbar button or if you save the script then it will become colorized similar to what you see here. Save the script with a name like "ChangeDownloads" to a location of your choice (you can place it within a "Scripts" folder in your Documents directory, or within your user library) and close the AppleScript editor.

Now open a new Terminal window and drag the newly made script file to it. When you do this the full file path to the script will be entered into the window, which will look something like "/Users/username/Documents/ChangeDownloads.scpt." Using the mouse, select the the entire file path, press Command-C to copy it to the clipboard, and go back to Script 1 in TextEdit and replace the "FILEPATH" entry with your copied text. Then save and close the document.

At this point the scripts are all set up. Just log out and log back in to load the launch agent script (Script 1), and now when you mount a drive that is named "Downloads" Safari's settings will be changed automatically so it uses that drive as the downloads folder. If you then unmount the drive the setting will be reverted. The same will also happen if you rename a drive to be named "Downloads" or change its name from "Downloads" to something else.

Do keep in mind that while this script works, it has only been tested on OS X 10.6 "Snow Leopard" and OS X 10.7 "Lion" running Safari 5.1; however, it should work just fine on other system configurations running at least Safari 5.0. The main requirements are when the script is manipulating Safari's preferences from within the program, it uses button indexes to open various menus and click the various buttons on screen. If Apple changes the preferences organization in Safari in the future, then this part of the script will not work until it is edited to point to the proper buttons again. However, for now that is a limitation we will have to deal with. One workaround for this in Lion is to take advantage of Apple's Resume feature in a script to quit Safari, make the preferences changes, and then relaunch the program instead of having to program the interface.

Lastly, if you decide you do not want this script anymore, then you can uninstall it by deleting the AppleScript file and then removing the LaunchAgent file either manually or by running the following command in the Terminal. When this is done just log out and log back in, and you will be back to square one:

rm ~/Library/LaunchAgents/local.ChangeSafariDownload.plist



Questions? Comments? Have a fix? Post them below or e-mail us!
Be sure to check us out on Twitter and the CNET Mac forums.