Adrian Nier Code

Back to overview

Sync directories with CCC

Last modification: Wednesday, December 02, 2009 03:31 pm

This script will let you easily compose and then run an rsync script in the Terminal. Make sure that you have the latest version of Carbon Copy Cloner installed, since the application’s rsync executable is needed for this script to work.

If you didn’t do so already, please visit Mike Bombich at http://www.bombich.com and consider making a donation for his work on Carbon Copy Cloner and the Mac-compatible version of rsync.

Implementation

on run
   -- Compose the command
   set _rsyncCommand to composeRsyncCommand()
   if _rsyncCommand is false then return false
   
   -- Run it in the Terminal
   tell application "Terminal" to do script _rsyncCommand
end run


on composeRsyncCommand()
   
   -- Get the posix path to the rsync executable
   set _rsyncPosixPath to rsyncPosixPath()
   if _rsyncPosixPath is false then return false
   
   -- Let the user select source and destination directories
   set _posixPaths to chooseSourceAndDestinationPosixPaths()
   if _posixPaths is false then return false
   set _sourcePosixPath to item 1 of _posixPaths
   set _destinationPosixPath to item 2 of _posixPaths
   
   -- Let the user choose from the list of selected rsync parameters
   set _parameters to composeRsyncParameters()
   if _parameters is false then return false
   
   -- Let the user decide wether to run rsync as root user
   set _adminPrivileges to askAboutAdministrativePrivileges()
   
   -- Compose the rsync command
   set _rsyncCommand to (quoted form of _rsyncPosixPath) & " " & _parameters & " " & (quoted form of _sourcePosixPath) & " " & (quoted form of _destinationPosixPath)
   if _adminPrivileges then set _rsyncCommand to "sudo " & _rsyncCommand
   
   -- Let the user make modifications to the command
   set _rsyncCommand to displaySummary(_sourcePosixPath, _destinationPosixPath, _rsyncCommand)
   return _rsyncCommand
   
end composeRsyncCommand


on displaySummary(_sourcePosixPath, _destinationPosixPath, _rsyncCommand)
   
   -- Display a dialog to the user informing them what is going to happen.
   activate
   set _prompt to "Ready to transfer data from" & return & return & _sourcePosixPath & return & return & "to" & return & return & _destinationPosixPath & return & return & "by running the following command. Changes you make to this command now will alter rsync’s behavior."
   
   set _result to display dialog _prompt default answer _rsyncCommand buttons {"Quit", "Run"} default button "Run"
   if button returned of _result is "Quit" then
      return false
   else if text returned of _result is "" then
      return false
   else
      return text returned of _result
   end if
end displaySummary



on askAboutAdministrativePrivileges()
   
   -- Ask the user about the privileges rsync should run with
   activate
   set _prompt to "Would you like to run rsync with adminitrative privileges?"
   set _buttons to {"Standard privileges", "Administrative privileges"}
   set _button to button returned of (display dialog _prompt buttons _buttons default button 2 with icon 1)
   if _button is "Standard privileges" then
      return false
   else
      return true
   end if
end askAboutAdministrativePrivileges

on chooseSourceAndDestinationPosixPaths()
   
   -- Ask the user to select the source and destination directories
   repeat
      set _sourcePosixPath to chooseDirectoryAsPosixPathWithPrompt("Please select the source directory:")
      if _sourcePosixPath is false then return false
      
      set _destinationPosixPath to chooseDirectoryAsPosixPathWithPrompt("Please select the destination directory:")
      if _destinationPosixPath is false then return false
      
      -- Return the user’s choices if the paths are not one and the same
      if _sourcePosixPath is not _destinationPosixPath then
         return {_sourcePosixPath, _destinationPosixPath}
      end if
      
      -- Display an error dialog to the user, because the paths are the same.
      activate
      set _prompt0 to "The source and destination reference the same directory."
      set _prompt1 to "If you’re trying to select two volumes that use the same name, you’ll have to rename one of them."
      set _prompt to _prompt0 & return & return & _prompt1
      set _buttons to {"Quit", "Select again"}
      set _button to button returned of (display dialog _prompt buttons _buttons default button 2 with icon 2)
      if _button is "Quit" then return false
      
   end repeat
   
end chooseSourceAndDestinationPosixPaths

on chooseDirectoryAsPosixPathWithPrompt(_prompt)
   -- Ask the user for a directory
   try
      activate
      set _location to POSIX file "/Volumes/"
      set _chosenFolder to choose folder with prompt _prompt default location _location
      
      -- Convert the alias to a posix path
      set _path to aliasToPosixPath(_chosenFolder)
      
      return _path
   on error
      return false
   end try
   
end chooseDirectoryAsPosixPathWithPrompt

on aliasToPosixPath(_alias)
   
   
   -- Check wether there's a volume with the same name as the one containing the alias
   set _hfsPath to (_alias as string)
   
   set _prvDlmt to text item delimiters
   set text item delimiters to ":"
   set _foldersDiskName to text item 1 of _hfsPath
   set text item delimiters to _prvDlmt
   
   tell application "System Events" to set _diskNames to name of disks
   
   set _sameDiskCount to 0
   repeat with _i from 1 to count of _diskNames
      if (item _i of _diskNames) is _foldersDiskName then set _sameDiskCount to _sameDiskCount + 1
   end repeat
   
   
   -- Let the user resolve the name conflict 
   if _sameDiskCount > 1 then
      tell application "System Events" to set _diskMountPoints to POSIX path of disks
      set _listPrompt to "Please select the mount point for the disk containing the chosen directory:"
      activate
      set _chosenMountPoint to choose from list _diskMountPoints with prompt _listPrompt default items {item 1 of _diskMountPoints} without multiple selections allowed
      if _chosenMountPoint is false then return false
      
      set _chosenMountPoint to item 1 of _chosenMountPoint
      if _chosenMountPoint is "/" then set _chosenMountPoint to ""
      
      tell application "System Events" to set _wrongMountPoint to POSIX path of disk _foldersDiskName
      set _wrongPosixPath to POSIX path of (_alias as string)
      
      set _prvDlmt to text item delimiters
      set text item delimiters to _wrongMountPoint
      set _remainingPath to text items 2 thru -1 of _wrongPosixPath as string
      set text item delimiters to _prvDlmt
      
      return _chosenMountPoint & "/" & _remainingPath
   else
      return POSIX path of (_alias as string)
   end if
   
end aliasToPosixPath

on composeRsyncParameters()
   -- Specify a list of parameters that rsync supports
   set _parameters to {}
   set end of _parameters to {"-q", "suppress non-error messages"}
   set end of _parameters to {"-h", "output numbers in a human-readable format"}
   set end of _parameters to {"-r", "recurse into directories"}
   set end of _parameters to {"-l", "copy symlinks as symlinks"}
   set end of _parameters to {"-p", "preserve permissions"}
   set end of _parameters to {"-o", "preserve owner (super-user only)"}
   set end of _parameters to {"-g", "preserve group"}
   set end of _parameters to {"-N", "preserve create times"}
   set end of _parameters to {"-t", "preserve modification times"}
   set end of _parameters to {"-H", "preserve hard links"}
   set end of _parameters to {"-A", "preserve ACLs"}
   set end of _parameters to {"-X", "preserve extended attributes"}
   set end of _parameters to {"-x", "don't cross filesystem boundaries"}
   set end of _parameters to {"--devices", "preserve device files (super-user only)"}
   set end of _parameters to {"--specials", "preserve special files"}
   set end of _parameters to {"--delete", "delete extraneous files from destination dirs"}
   set end of _parameters to {"--fileflags", "preserve file-flags (aka chflags)"}
   set end of _parameters to {"--force-change", "affect user-/system-immutable files/dirs"}
   
   
   -- Convert the list above to something more suitable for the choose form list command
   set _listTitles to {}
   set _defaultTitles to {}
   repeat with _i from 1 to count of _parameters
      if item 2 of item _i of _parameters does not contain "delete" then
         set end of _defaultTitles to item 2 of item _i of _parameters
      end if
      set end of _listTitles to item 2 of item _i of _parameters
   end repeat
   
   
   -- Ask the user which parameters should be included in the commmand
   activate
   set _chosenParameters to choose from list _listTitles default items _defaultTitles with multiple selections allowed
   if _chosenParameters is false then return false
   
   -- Produce a string of all the chosen parameters
   set _combinedParameters to ""
   repeat with _i from 1 to count of _parameters
      if (item 2 of item _i of _parameters) is in _chosenParameters then
         set _combinedParameters to _combinedParameters & (item 1 of item _i of _parameters) & " "
      end if
   end repeat
   
   return _combinedParameters
   
end composeRsyncParameters

on rsyncPosixPath()
   -- Get the path to the Carbon Copy Cloner application
   set _cccPosixPath to carbonCopyClonerPosixPath()
   if _cccPosixPath is false then return false
   
   -- Get the path to the rsync executable inside CCC’s application bundle
   set _rsyncPosixPath to findRsyncExecutableAtPosixPath(_cccPosixPath)
   if _rsyncPosixPath is false then
      activate
      display dialog "This script does not work with the currently installed version of Carbon Copy Cloner." buttons {"Quit"} default button 1 with icon 0
      return false
   end if
   
   return _rsyncPosixPath
end rsyncPosixPath

on findRsyncExecutableAtPosixPath(_posixPath)
   -- Given a starting directory, find a file named ‘rsync’
   try
      set _result to do shell script "find " & quoted form of _posixPath & " -name rsync"
      
      if _result is "" then
         return false
      else
         return paragraph 1 of _result
      end if
   on error
      return false
   end try
end findRsyncExecutableAtPosixPath

on carbonCopyClonerPosixPath()
   
   try
      -- Check default locations for rsync executable
      set _rsyncPath to findRsyncExecutableAtPosixPath("/Applications/Carbon Copy Cloner.app/")
      if _rsyncPath is not false then return _rsyncPath
      
      set _rsyncPath to findRsyncExecutableAtPosixPath("/Applications/Utilities/Carbon Copy Cloner.app/")
      if _rsyncPath is not false then return _rsyncPath
      
      set _homeFolderPath to path to home folder as string
      set _rsyncPath to findRsyncExecutableAtPosixPath(POSIX path of (_homeFolderPath & "Applications:Carbon Copy Cloner.app"))
      if _rsyncPath is not false then return _rsyncPath
      
      set _rsyncPath to findRsyncExecutableAtPosixPath(POSIX path of (_homeFolderPath & "Downloads:Carbon Copy Cloner:Carbon Copy Cloner.app"))
      if _rsyncPath is not false then return _rsyncPath
      
      set _rsyncPath to findRsyncExecutableAtPosixPath(POSIX path of (_homeFolderPath & "Downloads:Carbon Copy Cloner.app"))
      if _rsyncPath is not false then return _rsyncPath
      
      set _rsyncPath to findRsyncExecutableAtPosixPath(POSIX path of (_homeFolderPath & "Desktop:Carbon Copy Cloner.app"))
      if _rsyncPath is not false then return _rsyncPath
      
      (*
      -- Perform a Spotlight search to find Carbon Copy Cloner
      -- ### This feature needs a timeout. Runs too long on drives with lots of data.
      set _cccPosixPath to item 1 of performSpotlightSearchWithQuery("\"kMDItemFSName == 'Carbon Copy Cloner.app'\"")
      *)

      
      error 1
   on error
      try
         -- Ask the user where Carbon Copy Cloner is
         activate
         set _prompt to "Where is Carbon Copy Cloner?"
         set _type to {"com.apple.application-bundle"}
         set _location to POSIX file "/Volumes/"
         set _ccc to choose file with prompt _prompt of type _type default location _location
         return POSIX path of (_ccc as string)
      on error
         return false
      end try
   end try
end carbonCopyClonerPosixPath

on performSpotlightSearchWithQuery(_query)
   -- Perform a Spotlight search using the specified query and return a list of posix paths
   try
      set _result to do shell script "mdfind -0 " & _query
   on error
      return {}
   end try
   
   set _prvDlmt to text item delimiters
   set text item delimiters to (ASCII character 0)
   set _foundPaths to text items of _result
   set text item delimiters to _prvDlmt
   
   if item -1 of _foundPaths is "" then
      return items 1 thru -2 of _foundPaths
   else
      return _foundPaths
   end if
end performSpotlightSearchWithQuery