Tag Archives: AppleScript

AppleScript Folder Action to auto-trim folder

[UPDATE] This does not work as expected. Doesn’t seem to run as a plain AppleScript and when re-worked into an Automator folder action workflow (word salad anyone?) it might work… It has and it also hasn’t. But, even when it has I have the sneaking suspicion iCloud does not propagate deletes from macOS. Wouldn’t be the only inexplicable behavior from that service.

[UPDATE]This has been moved to a Google Drive folder. I’ve had enough of ghost folders taking up all the space. The principle is however the same, except now it just works. Boom. Roasted.

I have a security camera that FTPs a new folder everyday into a local iCloud folder on my home server. FYI, the real location of your iCloud Drive is within ~/Library/Mobile Documents, which is a package folder.

This storage process has not proved ideal since iCloud doesn’t handle automation very well. It works with Finder windows and Save dialogs, but background processes not so much. The result for me has been constant reminders that my iCloud drive is “full” despite deleting the bulk of folders. Most of the space never comes back and many times folders just “deleted” reappear.

Maybe adding another automated process to this soup is not going to help and may make things worse (will bird even notice when items are removed this way?), but what the hell. This script will delete files oldest first until the number of items it counted when it was called reaches the “keep” count (set to “5” here).

This may be attached to any folder, and items may be sent to the Trash or deleted immediately with rm. but be aware that it does not send files to the Trash. They are removed with rm.

-- http://strawhousepig.net/

-- Number of items to keep.
property keep : 5

-- Number of seconds until the script may run again (incase of runaway process adding to folder).
property cycle : 10

-- plist to store time last run.
property the_dom : "applescript.folderaction.trimfolder"

-- Should items be deleted immediately (set to false) or sent to the Trash (set to true)?
property trashit : true

on adding folder items to this_folder after receiving added_items
do_it(this_folder)
end adding folder items to

on run
set this_folder to choose folder
do_it(this_folder)
end run

on do_it(this_folder)
set the_key to "epoch"
set the_val to (do shell script "date '+%s'")
if my read_array(the_dom, the_key, the_val, cycle) is true then return
do shell script "defaults write " & the_dom & " " & the_key & " " & quoted form of the_val
tell application "Finder"
set _items to items of this_folder
set _items to (sort _items by creation date)
end tell
if (count of _items) is greater than keep then
try
repeat until (count of _items) is equal to keep
if trashit is true then
delete item 1 of _items
else
do shell script "rm -R " & quoted form of POSIX path of (item 1 of _items as alias)
end if
set _items to rest of _items
end repeat
on error theErr number theNum
if theNum is not -128 then display dialog theErr giving up after 60
end try
end if
end do_it

on read_array(the_dom, the_key, the_val, cycle)
try
set last_run to (do shell script "defaults read " & the_dom & " " & the_key)
on error -- Will error if there is nothing to read.
return false
end try
if the_val - last_run is less than cycle then
return true
else
return false
end if
end read_array

Auto-eject drive Folder Action

This script is meant to auto-eject a drive after the amount of time set in the script. It will prompt you first, so you may cancel at that time.

Why would this be useful? I have a Garmin Edge 130 bicycle computer. I had been using my phone, but every app/service has binding arbitration garbage in their 50 page click contract (aka baloney), even so far as with Strava flat out stating they may delete your account if you opt-out of it. Via real mail, of course. Outrageous. So I went with Garmin, who actually have a much smaller click contract. Like a breath of fresh air.

At any rate, my wife also has a Garmin Forerunner 35 watch and, like my Edge 130, includes a drive which mounts on your desktop when plugged into a computer. I believe this drive is used for manually installing watch faces or “widgets” and data screens. Although Garmin makes software to load those sort of things.

We typically plug these devices into a computer to charge them and when they are unplugged the Finder complains about the drive having not been ejected properly and file damage or something. Who even reads those things? That’s where this script comes in and why the trigger is “GARMIN”.

This script is meant to be attached as a Folder Action on the Volumes folder.
-- http://strawhousepig.net/

-- Name of disks to watch out for.
property triggers : {"GARMIN"}

-- Number of seconds until the script executes its mission unless user cancels.
property TKO : 30

on adding folder items to this_folder after receiving added_items
do_it()
end adding folder items to

on run
do_it()
end run

on do_it()
tell application "Finder"
try
set these_hds to every disk whose name is in triggers
if these_hds is not {} then
set now to current date
display dialog "The following volumes will be ejected in " & (TKO as text) & " seconds." & return & return & these_hds as text with icon 2 giving up after TKO
set the_res to the result
if gave up of the_res is true then
my inspected_detected_and_ejected(these_hds)
else
set now_now to current date
delay TKO - (now_now - now)
my inspected_detected_and_ejected(these_hds)
end if
end if
on error theErr number theNum
if theNum is not -128 then display dialog theErr giving up after TKO -- Error -128 is the cancelled by user error.
end try
end tell
end do_it

on inspected_detected_and_ejected(these_hds)
repeat with this_hd in these_hds
try
tell application "Finder" to eject this_hd
end try
end repeat
end inspected_detected_and_ejected

Notable is that this script delays the full set time whether the user clicks “OK” or not.

Sync folders when drive is plugged in.

[UPDATE] Didn’t realize how dangerous this is if you actually need to get a backup file(s) off the drive rather than restore from catastrophic failure. Now prompts to confirm sync before proceeding. Should also prompt to eject when complete.

The previous AppleScript post is an earlier version of nearly the same thing. I posted it here to be able to write a new script for where I work. Its intention is to keep an on-site duplicate of an off-site backup. This can of course be used for simple backups and/or folder syncing operations if given the appropriate trigger(s).

To have this script run automagically attach it as a Folder Action to the Volumes folder (/Volumes).

Also, as the previous post, this script calls an rsync binary in a custom install location. Please be aware of that.

Because I can: rsync manual

-- The name of the volume to watch for.
property trigger : "Backup"

-- The paths of the folders to sync. Drag-n-drop supported (choose "Alias").
property theFolders : {"/Do not/forget to/add quotes around", "/And/commas between/items"}

-- rsync options. I use '-rtE' (recursive, preserve times, copy Extended attributes/resource forks)
property rsync_options : "-rtE --delete" -- For mirroring the contents of source dir.
-- property rsync_options : "-rtE --ignore-existing" -- For keeping deleted files on backup. WARNING: also dupes files moved within the source dir.
-- See the rsync manual (man rsync) for more info.

property skip : {} -- Leave empty. Meant to hold items from theFolders which gave rsync an error so we can start over but ignore items in this list.

on adding folder items to this_folder after receiving added_items
try
repeat with i in added_items
if name of (info for i) is trigger then
if the result of (display dialog trigger & " drive connected. Would you like to sync available source folders now?" giving up after 600) is not false then
do_it(skip)
do shell script "logger 'Sync backup folders: Complete'"
ejectit(trigger)
end if
exit repeat
end if
end repeat
on error theErr
do shell script "logger 'Sync backup folders: " & theErr & "'"
end try
end adding folder items to

on run
do_it(skip)
do shell script "logger 'Sync backup folders: Complete'"
ejectit(trigger)
end run

on ejectit(trigger)
if the result of (display dialog "Backup sync complete. Eject drive '" & trigger & "'?") is not false then
tell application "Finder" to eject trigger
end if
end ejectit

on do_it(skip)
tell application "System Events" to set trigger_path to POSIX path of every disk whose name is trigger -- When a drive is mounted and its previous mount point was not removed the new mount point will be given a unique number to follow the name (which isn't shown in the Finder), e.g. "/Volumes/Backup 1". This asks the System for the path rather than us blindly using text.
if trigger_path is {} then
display dialog "The drive '" & trigger & "' does not appear to be mounted. :("
return
end if
set rsync_options to rsync_options & " "
repeat with i in theFolders
tell application "Finder"
if not (exists (i as POSIX file)) then
set end of skip to i as text
end if
end tell
log skip
if i is not in skip then
try
do shell script "/usr/local/bin/rsync " & rsync_options & quoted form of i & " '" & trigger_path & "/'"
set end of skip to i as text
on error the_err
if i is last item in theFolders then
display dialog "rsync encountered a problem: " & the_err
else
if button returned of (display dialog "rsync encountered a problem: " & the_err & return buttons {"Cancel", "Continue"}) is "Continue" then
set end of skip to i as text
do_it(skip)
end if
end if
end try
end if
end repeat
set skip to {}
end do_it

This script includes error handling that isn’t well tested. Getting it to work with it at all was a bit of a trick (particularly if not (exists (i as POSIX file)) since if not (exists POSIX file i) does not work as expected). So that whole “skip” idea may not work as intended if you are asked to continue. The last do_it(skip) call may need to be my do_it(skip) instead.

Auto-backup of Photos Library.photoslibrary when portable drive is attached

This script relies on Folder Actions to fire it once it is attached to /Volumes and the appropriately named hard drive is plugged in. I use it to update my off-site photo backup drive.

Settings in the script are commented with instructions, which are hopefully clear enough. It does mention iPhoto, because it was written when iPhoto was a thing. A good thing, a great thing even. Now… *meh*

The options for rsync are fairly standard (recursive, preserve times, extended attributes, and skip files already on the backup). The path to rsync is from OS X shipping with a version of rsync that was unbearably slow and me installing a newer version. I’m not sure when Apple ditched the older rsync version, but it sure wasn’t soon enough. I’m leaving it here for example purposes, but this script will fail if you don’t have an rsync binary there.

-- The name of the physical disk.
property top_drive : "Portable HDD"

-- The name of the disk image file.
-- If it is beyond root of top_drive, the (non-POSIX) path will need to be added. ie "backups:iPhoto backup.dmg"
property iPhoto_dmg : "Photos backup.sparseimage"

-- The name of the disk image drive.
property iPhoto_drive : "Photos backup"

-- POSIX path to iPhoto Library (Default is ~/Pictures/iPhoto Library).
property iPhoto_lib : quoted form of (POSIX path of ((path to pictures folder) as text) & "Photos Library.photoslibrary")

-- rsync options
property rsync_options : "-rtE --ignore-existing"

-- Path to rsync binary.
property rsync_path : "/usr/local/bin/" -- Leave blank ("") if not using a user installed version of rsync.

on logErr(the_msg, the_num)
do shell script "logger '" & "iPhoto backup: " & the_msg & " (" & the_num & ")'"
end logErr

on adding folder items to this_folder after receiving added_items
set new_item to item 1 of added_items as text
if new_item is (top_drive & ":") then
tell application "Finder"
try
open top_drive & ":" & iPhoto_dmg as alias
delay 10
on error errMsg number errNum
my logErr(errMsg, errNum)
end try
end tell
set n to 0
try
set rsync_options to rsync_options & " "
repeat until n = 10
tell application "Finder" to set all_drives to name of every disk
if iPhoto_drive is in all_drives then
set start_time to current date
set rsync_it to (do shell script rsync_path & "rsync " & rsync_options & iPhoto_lib & " " & quoted form of ("/Volumes/" & iPhoto_drive))
set total_time to (current date) - start_time
my logErr("Done", "" & total_time & " sec")
exit repeat
else
delay 6
set n to n + 1
end if
end repeat
tell application "Finder" to eject disk iPhoto_drive
display dialog "iPhoto backup complete." & return & return & "You may now eject the disk \"" & top_drive & "\"." with icon 2
on error errMsg number errNum
my logErr(errMsg, errNum)
display dialog "Oops! An error occurred. :(" & return & return & errNum & " : " & errMsg with icon 0
end try
end if
end adding folder items to

Parse e-mail for UPS tracking numbers

This AppleScript will parse the selected e-mail in Mail.app for UPS tracking numbers and will open the tracking page of the UPS website in your default browser.

This only looks at a single e-mail, but could be made to repeat through multiple selections.

tell application "Mail"
set _messages to selection as list
set input to content of item 1 of _messages
try
open location my parse_nums(input)
on error
display dialog "No UPS tracking info found."
end try
end tell

on parse_nums(input)
set input_new to ""
set output to ""
set input to every word of input
repeat with i in input
try
if first character of i is "1" and second character of i is "Z" then --Every UPS # I've seen starts with '1Z'
set input_new to input_new & i & "%0D%0A" as string --
URL encoded carriage return line feed needed for multiple entries.
end if
end try
end repeat
-- Now to remove the last set of new line characters.
repeat with c in characters 1 through ((count of characters of input_new) - 6) of input_new
set output to output & c
end repeat
set the_URL to "https://wwwapps.ups.com/WebTracking/track?track.x=Track&trackNums=" & output
return the_URL
end parse_nums

Set creation and modification date

This is to correct file times on photos that were imported from cameras with bad date settings or when they have been transferred to-and-fro and picked up the wrong data some where or after re-encoding high bit rate video to save a few GBs. [This does not alter the date atoms encoded in (most?) video files. ffmpeg can do that, however.]
Continue reading

Save list of folder contents

[UPDATE] Revisited this one and made it a little nicer. It is faster and gives a total count of directories and files, skips invisibles (but you can undo that), and doesn’t error on item names longer than 31 characters. Confirmed to run on 10.4, YMMV.

This AppleScript will create a file containing a tree style list of all files inside the chosen directory and save that list in a file created in that directory. The list file will include the path of the listed directory as well as the date and time it was saved and a tally of me bananas… er, folders and files under the starting directory.

The default directory in the choose dialog is the folder of the Finder’s frontmost window unless there are no Finder windows. In which case the default is the Desktop folder.

It does not follow aliases (not sure about symlinks) and will indicate an alias by placing ” [alias]” behind the file name.

This is actually a modified version of a script that’s been around the web for years.

-- http://strawhousepig.net/

set fileList to ""
set d_count to 0
set f_count to 0
global fileList, d_count, f_count

on listFolder(f, s)
list folder f as alias without invisibles
repeat with i in the result
set finfo to (info for alias (f & i))
if folder of finfo then
set d_count to d_count + 1
set fileList to fileList & s & i & "/" & return
listFolder(f & i & ":", s & " ")
else
if alias of finfo then
set f_count to f_count + 1
set fileList to fileList & s & i & " [alias]" & return
else
set f_count to f_count + 1
set fileList to fileList & s & i & return
end if
end if
end repeat
return {fileList, d_count, f_count}
end listFolder

try
tell application "Finder" to set cwd to (POSIX path of (folder of window 1 as string)) as POSIX file
on error
set cwd to path to desktop folder
end try
set theFolder to (choose folder "Select a folder to list:" default location cwd) as string

--This will get the name of the folder for use in the name of the generated file
set folderName to name of (info for (theFolder as alias))

--This will get the Unix style path of the folder for info purposes inside the file itself
set folderPath to POSIX path of theFolder

set infoList to listFolder(theFolder, "")
set fileList to "# File list of: " & folderPath & " on " & (current date) & return & item 2 of infoList & " folders and " & item 3 of infoList & " files total." & return & return & item 1 of infoList

set listFile to ((theFolder) as text) & "!File list of this folder (" & folderName & ")" & (do shell script "date \"+ %Y-%m-%d %H%M\"") & ".txt"

tell application "Finder"
set newFile to (open for access file listFile with write permission)
set eof newFile to 0
write fileList to newFile
close access newFile
open listFile as alias
end tell

iPhoto import from folders AppleScript

Preface: Having upgraded my phone (Nokia N8) firmware to “Belle” — whatever version that is, the Nokia Multimedia Transfer application no longer imports photos to iPhoto.* Tragic because the camera is the sole reason I bought this phone. Ordinarily this would not be a problem since the phone uses a “DCIM” folder at the drive root and can store images with 8 character names (5 of which you set yourself). But not every photo app does store them there or with a “proper” name. Even though there exists an “Images” folder in the drive root that I know gets used, Mass Storage.app does not access it. Could be another naming convention similar to DCF

This is an AppleScript to get around this limitation, and to allow (in my case at least) the importing of camera files named by date. In short you choose which folders you want this script to tell iPhoto to import from and can be used with any folder(s), no specific device required.

Notable: Uses ‘defaults’ to store and load folder paths in a property list (.plist) file.
Continue reading