AppleScript ‘Backup System’

Can we see the script, now?

Well, OK.  Since we’re up to that part, I want to point out a few things.

  • If Dropbox isn’t running, a warning will appear.  This is simply because I truly believe you should be backing up to an offsite location.
  • Old backups are never.  Did you read that?  Make sure you do as the potential for filling up your free 2GB Dropbox is quite real if you choose to backup a LOT of data each time.
  • Destination folders are created, if they don’t exist.
  • Growl notifications are shown for everything important.

The Script

One last thing – you can download the script, if you want to read it offline.

-- Backup.scpt
--
-- April 2011, Chris Rasmussen
-- http://www.digitalformula.net | http://twitter.com/digitalformula
--
-- Used to archive specified files to relevant backup folders to a specified folder, preferably Dropbox or some other cloud-based sync service
--
-- The backup is done by archiving source folders so that indexing doesn't take too long (a known issue with Dropbox)

-- set some variables for machine-specific information
-- get the username of the current logged in user
global computerName
set computerName to (computer name of (system info))
global userHome
set userHome to (home directory of (system info))

-- ***
-- *** Makes changes in this section only
-- ***

-- options
global PromptForOverwrite
-- set this to true if you want to prompted before overwriting any existing archived backups in the destination folder
-- since we're using destination folder names based on date *and* time, existing backups shouldn't ever really exist
set PromptForOverwrite to false

-- set this to the parent path of where backups will go
-- unless you change this below, the default is your Dropbox
-- IMPORTANT : if you set this to anything but 'userHome' you MUST make sure a colon (:) is added to the end of the path
global BasePath
set BasePath to userHome & "Dropbox" & ":"

-- create the list of items to backup
-- for readability the list definition has been split onto multiple lines using the 'continuation character' (Option + lower-case L)
-- the first parameter is the name of the application, the second parameter is the full Apple path its files are in
-- please don't put spaces in the application name for now ... sorry
-- this is the same as:
-- set TheBackupList to {{"SnippetsApp", "Snippets"}, {"HazelApp", "Hazel"}}
-- for the downloadable version, the data to be backed up is specified one item per line
--
-- to add a new item to the list, copy the last item onto a new line - make sure the curly braces, commas and carriage returns match!

set TheBackupList to {¬
	{"SnippetsApp", " HD:Users:chris:Library:Application Support:Snippets"}, ¬
	{"HazelApp", " HD:Users:chris:Library:Application Support:Hazel"}, ¬
	{"KeePass", " HD:Users:chris:Data:KeePass"}, ¬
	{"AppleScripts", " HD:Users:chris:Library:Scripts"}, ¬
	{"PrefsFolder", " HD:Users:chris:Library:Preferences"} ¬
		}

-- ***
-- *** That's it - stop editing!
-- ***

-- see if Growl is available on this system
-- this variable is global so that the newBackup class has access to it
global GrowlAvailable
tell application "System Events" to set GrowlAvailable to exists application process "GrowlHelperApp"

if GrowlAvailable then
	-- enable the script to send Growl notifications
	tell application "GrowlHelperApp"
		set the allNotificationsList to {"removed", "success", "aborted", "no_existing", "created"}
		set the enabledNotificationsList to {"removed", "success", "aborted", "no_existing", "created"}
		register as application "Backup Database" all notifications allNotificationsList default notifications enabledNotificationsList icon of application "Script Editor"
	end tell
end if

-- see if Dropbox is available
global DropboxAvailable
tell application "System Events" to set DropboxAvailable to exists application process "Dropbox"

-- see if Dropbox is available
if not DropboxAvailable then
	display dialog "Oops!  Dropbox is either not installed or not running on this computer.  Dropbox is highly recommended as the backup destination so that backup archives are created 'offsite'.nnPlease download and install Dropbox from http://www.dropbox.com or, if you've already got it, look into why Dropbox can't be accessed ...k?  :)" buttons {"Ok"} with icon stop with title "Oh no!"
end if

-- class to store settings relating to the objects to be backed up
-- by using a class we can do multiple backups by simple accessing the class and 'running' it
on BackupObject(appName, folderName)

	script backupObjectDetails

		-- get the current date and format it nicely
		property CurrentDate : (year of (current date)) & "-" & (month of (current date) as integer) & "-" & (day of (current date)) as string
		property CurrentTime : (hours of (current date)) & (minutes of (current date)) & (seconds of (current date)) as string

		-- although it's not an explicitly specified configuration option, you can uncomment the following line if you don't want to use destination folders that are named based on date & time
		-- property ApplicationName : appName
		-- if you uncommented the line above, make sure you comment the one below or your changes won't take effect
		property ApplicationName : CurrentDate & "-" & CurrentTime & "-" & appName
		property ApplicationNameShort : appName

		property SourceParent : userHome & "Library:Application Support:" as string
		property DestParent : BasePath & "Backups:" & computerName & ":" as string

		property SourcePath : folderName
		property DestPath : DestParent & ApplicationName

		property AppFolder : folderName

		property DestDB : DestPath & ":" & ApplicationName & ".zip"
		property FileCount : 0

		-- flag variable to tell the script if it should continue once all conditions are evaluated
		property GoodToGo : false

		-- method to copy the master database to the backup folder
		to copyDatabase to destinationFolder

			-- try and create the master Backups directory
			try
				tell application "Finder" to set destResults to make new folder at (BasePath as string) with properties {name:"Backups"}
			end try

			-- try and create the machine-specific backup directory
			try
				tell application "Finder" to set destResults to make new folder at (BasePath & "Backups" as string) with properties {name:computerName}
			end try

			-- first check if the folder containing the source files exists
			-- if it doesn't, we've got nowhere to copy files from
			tell application "Finder" to if exists SourcePath then

				-- then check if the destination folder exists
				-- if it doesn't, the script will create it
				tell application "Finder" to if exists DestPath then

					-- check if a backup copy of the database file already exists
					-- if it doesn't, the database will be copied immediately
					tell application "Finder" to if exists DestDB then

						if PromptForOverwrite then

							-- an existing backup does exist
							-- ask the user if they want to remove it first
							set continueResult to display dialog "Archived backup of " & ApplicationName & " database already exists in " & DestPath & ".  Delete existing backup and continue?" buttons {"Yes", "No"} with icon caution with title "Backup already exists"
							if button returned of continueResult is "No" then
								if GrowlAvailable then tell application "GrowlHelperApp" to notify with name "aborted" title "Backup " & ApplicationName & " Database" description ApplicationName & " database backup aborted." application name "Backup Database"

								-- don't continue
								set GoodToGo to false

							else

								-- user specified to remove the existing backup first
								delete file DestDB
								if GrowlAvailable then tell application "GrowlHelperApp" to notify with name "removed" title "Backup " & ApplicationName & " Database" description "Old " & ApplicationName & " database backups removed." application name "Backup Database"

								-- ok to continue
								set GoodToGo to true

							end if
						else
							-- ok to continue
							set GoodToGo to true

						end if
					else
						-- an existing backup doesn't exist so just do the backup now
						if GrowlAvailable then tell application "GrowlHelperApp" to notify with name "no_existing" title "Backup " & ApplicationName & " Database" description "No existing " & ApplicationName & " database backups found." application name "Backup Database"

						-- ok to continue
						set GoodToGo to true

					end if
				else
					-- destination directory doesn't exist
					-- try and create it then do the copy
					try
						tell application "Finder" to set destResults to make new folder at (BasePath & "Backups:" & computerName as string) with properties {name:ApplicationName}
					on error
						display dialog "Oops!  The destination folder, " & DestPath & ", could not be created.  Please try again later." buttons {"Ok"} with icon stop with title "Oh no!"
						return
					end try

					-- ok to continue
					set GoodToGo to true

				end if

			else
				display dialog "Oops!  The source folder you selected, " & SourcePath & ", ain't there!  Please check it and try again." buttons {"Ok"} with icon stop with title "Oh no!"

			end if

			if GoodToGo then

				-- see if there is more than 1 file in the selected folder
				-- we need to do this so the notifications show the correct file counts when archiving is finished
				tell application "Finder"
					-- create the ZIP file
					do shell script "zip -r " & POSIX path of DestDB & " -9 "" & POSIX path of SourcePath & """
					try
						set SourceData to every item in entire contents of folder SourcePath
						if class of SourceData is list then
							-- the resulting object will be a list if there is more than 1 file
							set FileCount to (count of items in SourceData)
						else
							-- only 1 file exists
							set FileCount to 1
						end if
					on error
						display dialog "No files found in selected folder!"
					end try
				end tell

				-- backup successful - time to display some stats
				if GrowlAvailable then tell application "GrowlHelperApp" to notify with name "success" title ApplicationNameShort & " backup successful" description (FileCount as string) & " " & ApplicationNameShort & " files archived successfully" application name "Backup Database"

			else
				-- GoodToGo is false
				display dialog "Hmmm you either aborted the backup or something broke while the backup was running.  If you didn't abort, please make sure file permissions are correct and that there aren't any locked files in the source or destination folders then try running the script again ... k?  :)" buttons {"Ok"} with icon stop with title "Oh no!"

			end if

		end copyDatabase

	end script

end BackupObject

-- go through the list and backup everything up
-- excessive comments for readability

-- get the number of objects we're backing up
set NumberOfObjects to count of TheBackupList

-- set the list pointer to the first object MINUS 1
-- AppleScripts are 1-based, not 0-based!
set CurrentObject to 0
-- go through the list and do the actual backup part
repeat until CurrentObject is equal to (NumberOfObjects)

	-- move the list pointer to the first object
	-- this is done here because of AppleScript's 1-based lists
	set CurrentObject to CurrentObject + 1

	-- get the current object containing the name of the app and the path to be backed up
	set TempObject to item CurrentObject of TheBackupList

	-- create the backup object that contains all information about what to backup ...
	set TheBackupObject to BackupObject(item 1 of TempObject, item 2 of TempObject)
	-- ... then back it up!
	tell TheBackupObject to copyDatabase to (DestPath of TheBackupObject)

	-- rinse and repeat until there's nothing else to wash ... err ... backup

end repeat

It’s a decent-sized script, huh?  There are a boat load of comments so in reality I’d say the script itself isn’t too bad.  It really does work, though, I promise.  I use it every day to backup the data that’s in this particular script already i.e. my Snippets database, my Hazel configuration, KeePass database, all my AppleScripts and my account preferences.

If you do download the script or use it yourself, I’d like to hear about it.  Even if you grab it, tear it to shreds, change everything except the idea etc, still let me know.  Thanks!