Unison File Synchronizer

Unison project website.

Unison Manual.

Unison Mac OS X FAQs.

Set Up

Downloaded GUI Universal Binary 2.32.12 "(2009.05.06, stable, should work on Tiger and Leopard)."

1. Installed on both Principia (King office) and houptlab.org (magnet.neuro.fsu.edu) by screen sharing...

2. Logged into Magnet as User, and launched Unison on Magnet.

3. On Magnet, under System Preferences, set Unison to run automatically on start-up on houpt log-in. (Will it be running even if I don't log in to magnet as user?)

4. On Principia, In the finder, made a new folder in home directory "/Users/houpt/uniDisk"

5. Ran Unison on Principia; created a new profile:

Profile name: "uniDisk"
First Root:File: "/Users/houpt/uniDisk" 
Second Root:Remote
Second Root:User:"houpt"
Second Root:Host:"houptlab.org"
Second Root:File:"uniDisk"

Save the profile.

(Apparently don't need to setup a profile on the server.)

(Profiles are stored as .prf files in "/Library/Application Support/Unison" folder; apparently only way to delete a profile is to trash the .prf file manually.)

6. To sync, run Unison, choose uniDisk profile, hit "Open". First time of launch will be prompted for password.

7. Hit "Go" for default synchronization.

And it just worked! (apparently don't need to create a profile on the remote host. Folder will be created on the server side on the first sync.)

If you close the profile window, you can't reopen the window except by quiting and relaunching Unison? Hit "restart" button to get back to list of profiles. Apparently no way to edit the profiles after creation.

Set Up for Shared Hosting on DreamHost (Ubuntu)

DreamHost's shared hosts don't have Unison, but it is straight-forward to build it.

First step is to build OCaml and install it into a scratch directory (~/ocaml):

mkdir ~/ocaml
curl -OL http://caml.inria.fr/pub/distrib/ocaml-4.02/ocaml-4.02.3.tar.gz
tar xzf ocaml-4.02.3.tar.gz
cd ocaml-4.02.3
./configure --prefix ~/ocaml
make world.opt
make install

Next, add OCaml to the path, and build Unison:

PATH=~/ocaml/bin:$PATH
curl -OL http://www.seas.upenn.edu/~bcpierce/unison/download/releases/stable/unison-2.48.3.tar.gz
tar tzf unison-2.48.3.tar.gz
cd unison-2.48.3
make

Command Line

To synchronize the folder "/Users/houpt/uniDisk" from the command line (without invoking GUI app):

1. Make sure you are starting from directory "/Users/houpt" (maybe should use absolute paths in command line?))

2. type following command:

unison uniDisk ssh://houptlab.org/uniDisk -ui text -batch -times

3. prompted for password

4. If there are any conflicts etc., will be prompted for directions. These can be suppressed with various command line options, for example:

-auto 

if "auto" is set the user interface will not ask to confirm non-conflicts, but only ask questions about conflicts

-batch 

if "batch" is set the user interface will ask no questions at all; non-conlficts will be propogated, conflicts will be skipped

-silent  

if "silent", the textual interface will porint nothing at ell, except errors (automatically sets -batch TRUE)

-times

"times" synchronizes the modification times?

-terse

if "terse" is set, status messages are not reported, so only conflicts are reported.

-ignore 'Name .DS_Store"

Unison will ignore all files with the name .DS_Store (On Mac OS X, .DS_Store files encode only trivial appearance stuff like icon position, but .DS_Store files are changed everytime a folder is opened so frequently generate irrelevant conflicts.)

AppleScript

To avoid opening the terminal window, create the following AppleScript and save as an application.

       try
            -- add "-terse" if you want less verbose results
            set resultsText to do shell script "unison /Users/houpt/uniDisk ssh://houptlab.org/uniDisk -ui text -batch -times  -ignore 'Name .DS_Store'"
            
        on error errStr number errorNumber
            
            set unisonResultCodeString to (errStr & return & return & "Result: number " & errorNumber & return & return)
            
            if (errorNumber is equal to 0) then
            
                set resultsText to (unisonResultCodeString & "Successful synchronization; everything is up-to-date now")
            
            else if (errorNumber is equal to 1) then
            
                set resultsText to (unisonResultCodeString & "Some files were skipped, but all file transfers were successful.")
            
            else if (errorNumber is equal to 2) then
            
                set resultsText to (unisonResultCodeString & "Non-fatal failures occurred during file transfer.")
            
            else if (errorNumber is equal to 3) then
            
                set resultsText to (unisonResultCodeString & "A fatal error occurred, or the execution was interrupted.")
            
            else
            
                set resultsText to unisonResultCodeString
            
            end if
            
            
        end try
 
 
        display alert "Unison completed sync" giving up after 2
 
        -- display the results text in a new TextEdit window
        tell application "TextEdit"
            activate
            set NewDoc to make new document
            if (0 is length of resultsText) then
                set text of NewDoc to (date string of (current date)) & " " & (time string of (current date)) & return & return & "Unison reports no conflicts."
                else
                set text of NewDoc to (date string of (current date)) & " " &(time string of (current date)) & return & return & resultsText
            end if
        end tell
 
 tell me to quit

Results of the synch will be stored in the "unison.log" file, and the applescript will display results in a TextEdit window.

Unison With Scrivener

Scrivener stores its projects as bundles (i.e. folders). Because Unison does not sync modification times of folders, changes to Scrivener projects do not get properly propagated. To get around this, I use this more complicated AppleScript to zip up any Scrivener projects -- the zipped file gets propagated properly, with the Scrivener project modification times properly preserved and transmitted. Needless to say, this is a hack; but I really want to sync my Scrivener projects on multiple machines!

 --
 --  AppDelegate.applescript
 --  SyncUniDisk
 --
 --  Created by Tom Houpt on 12/12/28.
 --  Copyright (c) 2012 Tom Houpt. All rights reserved.
 --
 
  script AppDelegate
        property parent : class "NSObject"
 	
 	on applicationWillFinishLaunching_(aNotification)
 		-- Insert code here to initialize your application before any files are opened
        
        -- to handle incompatibilities of Unison file-synching with Scrivener's use of bundles for files
        -- T.A. Houpt, 2014-1-22
        --
        -- find all Scrivener "*.scriv" files (actually bundles) and zip them up
        -- then rename original file as "*.scriv YYYYMMDD hhmmss"
        -- then call unison in batch mode
        -- post an alert that we finished syncing
        -- display the unison results in a TextEdit window
        -- (we could unzip any *.scriv.zip files, but we do the unzipping manually for the moment)
  
  try
  
        set allScrivenerFileNames to do shell script "find '/Users/houpt/uniDisk' -name '*.scriv' -print"
        set AppleScript's text item delimiters to return
        set scrivenerFileRecords to text items of allScrivenerFileNames
        
        -- zip all the .scriv files
        -- for each .scriv file we found:
        --   1) delete previous zip file if it exists
        --   2) zip the .scriv file
        --   3) if we created the zip file, rename the "*.scriv" file as "*.scriv YYYYMMDD hhmmss"
        
        repeat with aRecord in scrivenerFileRecords
            if length of aRecord is greater than 0 then
                
                set scrivenerFile to aRecord
                set filePath to quoted form of scrivenerFile
                set zipFile to quoted form of (scrivenerFile & ".zip")
                
                -- if there is a previous zip file, then delete it
                set existsResults to do shell script "[ -e " & zipFile & " ] && echo 'Found' || echo 'Not found'"
                
                if (existsResults is "Found") then
                    do shell script "rm -f " & filePath
                end if
                
                do shell script "zip -r  " & zipFile & " " & filePath
                
                -- if the file was zipped, then rename the original file with date & time
                set existsResults to do shell script "[ -e " & zipFile & " ] && echo 'Found' || echo 'Not found'"
                
                if (existsResults is "Found") then
                    set {year:y, month:m, day:d, hours:h, minutes:mi, seconds:s} to (current date)
                    set dateNumber to y * 10000 + m * 100 + d
                    set timeNumber to h * 10000 + mi * 100 + s
                    
                    set newFilePath to quoted form of (scrivenerFile & " " & dateNumber & " " & timeNumber)
                    
                    do shell script "mv " & filePath & " " & newFilePath
                end if
            end if
        end repeat
        
        on error errStr number errorNumber
  
            set zipErrorString to ("Zip Error: " & errStr & " number " & errorNumber)
            
            display alert zipErrorString giving up after 10
  
        end try
  
  
        try
            -- -terse
            set resultsText to do shell script "unison /Users/houpt/uniDisk ssh://houptlab.org/uniDisk -ui text -batch -times  -ignore 'Name .DS_Store'"
            
        on error errStr number errorNumber
            
            set unisonResultCodeString to (errStr & return & return & "Result: number " & errorNumber & return & return)
            
            if (errorNumber is equal to 0) then
            
                set resultsText to (unisonResultCodeString & "Successful synchronization; everything is up-to-date now")
            
            else if (errorNumber is equal to 1) then
            
                set resultsText to (unisonResultCodeString & "Some files were skipped, but all file transfers were successful.")
            
            else if (errorNumber is equal to 2) then
            
                set resultsText to (unisonResultCodeString & "Non-fatal failures occurred during file transfer.")
            
            else if (errorNumber is equal to 3) then
            
                set resultsText to (unisonResultCodeString & "A fatal error occurred, or the execution was interrupted.")
            
            else
            
                set resultsText to unisonResultCodeString
            
            end if
            
            
        end try
   
   
        display alert "Unison completed sync" giving up after 2
        
        -- display the results text in a new TextEdit window
        tell application "TextEdit"
            activate
            set NewDoc to make new document
            if (0 is length of resultsText) then
                set text of NewDoc to (date string of (current date)) & " " & (time string of (current date)) & return & return & "Unison reports no conflicts."
                else
                set text of NewDoc to (date string of (current date)) & " " &(time string of (current date)) & return & return & resultsText
            end if
        end tell
        
        
        tell me to quit 
   
    end applicationWillFinishLaunching_
  	
    on applicationShouldTerminate_(sender)
          -- Insert code here to do any housekeeping before your application quits 
         return current application's NSTerminateNow
    end applicationShouldTerminate_
   
 end script

Conflicts

Conflicts arise when the server and the local directory have conflicting dates (i.e. the versions on office computer and laptop were independently edited, so are now out of sync.) If there are conflicts to be resolved, then the applescript will post a dialog window.

To resolve the conflicts, run the following command (make sure you are in Users/houpt directory)

unison uniDisk ssh://houptlab.org/uniDisk -ui text -times -ignore 'Name .DS_Store'

For each conflict, < moves from server to local, > moves from local to server, / skips the file, f means follow Unison recommendation.