Keeping a clean desktop and downloads folder

I decided I’d had enough of my Mac being slow to reboot because of too many items on the desktop. Why that should be a dealbreaker, I have no idea, but when the Finder is starting you can watch the things render one… by… one… I don’t keep a particularly messy desktop — there are 42 items on it right now — but apparently that’s too much.

It’s also a usability thing. The desktop and the downloads folder are where things get put by default. I rarely need the things there for more than a few days in the case of the desktop, or a few minutes in the case of the downloads folder.

I already have folders where I toss things when I clean up. Those folders are a mess. They have hundreds of items, but I don’t care. I’m going to use an AppleScript and put it in cron. I haven’t touched either of those technologies in years, so it should be interesting.

The AppleScript

First forgotten fact: Macs use “:” as a delimiter in paths. Everything else I use shields me from that. Also, AppleScript doesn’t understand “~geoffcanyon:Desktop” Still, getting all the files and folders on the desktop is easy:

       tell application “Finder” to every item of folder “Macintosh HD:Users:geoffcanyon:Desktop”

Ah, it’s been some time since I used AppleScript. I forgot about

       path to desktop folder

So it’s:

       tell application “Finder” to every item of folder (path to desktop folder)

Hmm, what’s the property name for “Date Modified”? Time to open the Finder’s dictionary.

RANT INTERLUDE

Many articles complain about AppleScript’s syntax. Here’s an example from John Gruber. He says, “…it turns out that an English-like programming language didn’t really enable a large number of users to become programmers. And conversely, AppleScript’s English-like syntax often made (and to this day continues to make) things more difficult and confusing for scripters, not less.” That’s typical of complaints against AppleScript, and it’s clearly wrong. There are two points:

Beginners
Look at HyperCard, or LiveCode, and it’s clear that an English-like language can ease non-programmers into programming. Thousands of people programming today got their start in HyperCard. So while it might be that, as Mr. Gruber says, “…the number of programmers in the world who consider AppleScript their favorite language could fit in a very small car…” the number of programmers who consider an English-like syntax their favorite would fill…okay, maybe not a stadium, but they’d crush that car.

Syntax
AppleScript syntax isn’t challenging, unclear, or ambiguous — the application dictionaries are. They can (and often must) be incredibly detailed, and the documentation is often non-existent. I once spent several hours figuring out how to add a page to a document in an application. It turned out that the application’s documents don’t contain pages — they contain spreads, and spreads contain pages. But blaming AppleScript for the complexities of application dictionaries is like blaming the english language for Gravity’s Rainbow.

END RANT

The Finder’s dictionary tells me “modification date” is what I’m looking for. A quick google search tells me that I can do date math using seconds. So to find everything on the desktop older than a day:

       tell application “Finder” to every item of folder (path to desktop folder) whose modification date < (current date) – 86400

Nice, this is starting to look like a one-liner, albeit a long one. There is one item I want to exclude from this. I keep an alias to one of our servers on my desktop, so I don’t want to lose that.

       tell application “Finder” to every item of folder (path to desktop folder) whose (modification date < (current date) – 86400 and name is not “Public”)

Wait, that’s not a good way to do this. I make it more flexible with labels. Back to the dictionary! Looks like it’s “label index” so:

       tell application “Finder” to every item of folder (path to desktop folder) whose (modification date < (current date) – 86400 and label index is not 6)

(It happens that green is 6 on my Mac) Okay, I’m selecting the items I want. Now it’s time to move them to the archive:

       tell application “Finder” to move (every item of folder (path to desktop folder) whose (modification date < (current date) – 86400 and label index is not 6)) to folder ((path to home folder as string) & “Desktop Archive”) with replacing

“With replacing” is easy — no error checking for duplicated file names — and it’s unlikely the archive will contain something that has the same name that isn’t the same file, and that I would want that earlier file back. Maybe that will bite me someday. I added a line to do the same for the downloads folder, but without the label restriction. The end result is:

       tell application “Finder”
              move (every item of folder (path to desktop folder) whose (modification date < (current date) – 86400 and label index is not 6)) to folder ((path to home folder as string) & “Desktop Archive”) with replacing
              move (every item of folder (path to downloads folder) whose modification date < (current date) – 86400) to folder ((path to home folder as string) & “Downloads Archive”) with replacing
       end tell

Now to cron this thing…

…okay, a little reading tells me that cron isn’t the preferred tool on Macs, so I’m looking at launchd and launchctl, particularly this tutorial. I saved the AppleScript as an app as “/Users/geoffcanyon/Scripts/Archive.app” I opened “/Users/geoffcanyon/Library/LaunchAgents” to see what examples were there. A little reading online and I created a new plist file at “/Users/geoffcanyon/Library/LaunchAgents/com.apple.cleanupfiles.plist” Viewed as text (I used the Property List Editor app to create it) it’s:

<?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.apple.cleanupfiles</string>
    <key>Program</key>
    <string>/Users/geoffcanyon/Scripts/Archive.app</string>
    <key>StartInterval</key>
    <integer>86400</integer>
</dict>
</plist>

To load that, in the terminal use:

    launchctl load /Users/geoffcanyon/Library/LaunchAgents/com.apple.cleanupfiles.plist

To test it immediately (again, in the terminal):

    launchctl start com.apple.cleanupfiles

Note that the load command uses the actual file path, while the start command uses the label string from within the file.

I tried the above, and it doesn’t work. For some reason launchd doesn’t want to run the AppleScript app. Well, you can also execute an osa script. So I changed the plist to try that. I saved the script itself as “/Users/geoffcanyon/Scripts/Archive.scpt” With that in place, here is the plist to execute the script. I also changed the interval to 3600 so it runs every hour:

<?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.apple.cleanupfiles</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/osascript</string>
        <string>/Users/geoffcanyon/Scripts/Archive.scpt</string>
    </array>
    <key>StartInterval</key>
    <integer>3600</integer>
</dict>
</plist>

Save the files, then

    launchctl unload /Users/geoffcanyon/Library/LaunchAgents/com.apple.cleanupfiles.plist
    launchctl load /Users/geoffcanyon/Library/LaunchAgents/com.apple.cleanupfiles.plist
    launchctl start com.apple.cleanupfiles

That works. While I was at it I modified the AppleScript so it checks to see if a backup is needed, then creates backup folders and names them with a time-stamp. That takes care of duplicate file names. I also changed the label filter to exclude all labeled items. Here’s the AppleScript:

       set myDateString to ((10000 * the (year of the (current date)) + 100 * the (month of the (current date)) + the (day of the (current date))) as string) & “-” & the time of the (current date)
       tell application “Finder”
              set itemList to (every item of folder (path to desktop folder) whose (modification date < (current date) – 86400 and label index is 0))
              if itemList is not {} then
                     make new folder at ((path to home folder as string) & “Desktop Archive”) with properties {name:myDateString}
                     move itemList to folder ((path to home folder as string) & “Desktop Archive:” & myDateString)
              end if
              set itemList to (every item of folder (path to downloads folder) whose modification date < (current date) – 86400)
              if itemList is not {} then
                     make new folder at ((path to home folder as string) & “Downloads Archive”) with properties {name:myDateString}
                     move itemList to folder ((path to home folder as string) & “Downloads Archive:” & myDateString)
              end if
       end tell

Conclusion

Now my desktop and downloads folder are neat all the time, with just the most recent things I’ve been working with in them. Here are the steps without all the commentary:

  1. Create a folder “Desktop Archive” in your home folder.
  2. Create a folder “Downloads Archive” in your home folder.
  3. Create an AppleScript with the above script in it, and save it someplace. Note the path to the file.
  4. Create a plist file with the above plist in it. Substitute the path to your .scpt file, and save it in your /Library/LaunchAgents/ in your home folder. Note that your Library folder is likely not visible, so this part can be a bit tricky.
  5. In the Terminal, use launchctl load <your plist> to schedule the agent to run.
  6. If you want to see immediate results, use launchctl start com.apple.cleanupfiles in the Terminal (assuming you haven’t change the label string).

That’s it!

UPDATE:
I found a flaw in the above: if the above script runs while a file is downloading, it will go ahead and archive it, screwing up the download. This script should fix that (pending testing):

       set myDateString to ((10000 * the (year of the (current date)) + 100 * the (month of the (current date)) + the (day of the (current date))) as string) & “-” & the time of the (current date)

       tell application “Finder”
              set itemList to (every item of folder (path to desktop folder) whose (modification date < (current date) – 86400 and label index is 0))
              if itemList is not {} then
                     make new folder at ((path to home folder as string) & “Desktop Archive”) with properties {name:myDateString}
                     move itemList to folder ((path to home folder as string) & “Desktop Archive:” & myDateString)
              end if
              set itemList to (every item of folder (path to downloads folder) whose (modification date < (current date) – 86400 and label index is 0 and not (name extension is “download”)))
              if itemList is not {} then
                     make new folder at ((path to home folder as string) & “Downloads Archive”) with properties {name:myDateString}
                     move itemList to folder ((path to home folder as string) & “Downloads Archive:” & myDateString)
              end if
       end tell

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s