# 
# lock-revision.sh - lock a new revision
################################################################
# Copyright (C) 2001, 2002 Tom Lord
# 
# See the file "COPYING" for further information about
# the copyright and warranty status of this work.
# 

set -e 
errname=lock-revision

################################################################
# special options
# 
# Some options are special:
# 
#       --version | -V
#       --help | -h
# 
if test $# -ne 0 ; then

  for opt in "$@" ; do
    case $opt in

      --version|-V) exec larch --version
                    ;;


      --help|-h)
                printf "acquire the lock for the next revision\\n"
                printf "usage: lock-revision [options] revision\\n"
                printf "\\n"
                printf " -V --version                  print version info\\n"
                printf " -h --help                     display help\\n"
                printf "\\n"
                printf " -R --root root                specify the local archive root\\n"
                printf " -A --archive archive          specify the archive name\\n"
                printf " -e -e errname                 use ERRNAME in error messages\\n"
                printf "\\n"
                printf " -u --unlock                   release the indicated lock\\n"
                printf " --break                       break the indicated lock\\n"
                printf "\\n"
                printf "Acquire the lock needed to create REVISION.\\n"
                printf "\\n"
                printf "Revision must be the next patch-level in sequence, however,\\n"
                printf "if no patch level is specified, the lock is taken unconditionally.\\n"
                printf "If no version is specified, the latest version is locked.\\n"
                printf "\\n"
                printf "The output format is:\\n"
                printf "\\n"
                printf "        \"%%s\\\\n\" \"\$lockdir\" \\n"
                printf "\\n"
                exit 0
                ;;

      *)
                ;;
    esac
  done
fi

################################################################
# Ordinary Options
# 
# 

archroot=
archive=
break=
action=lock
unlock=
break=

while test $# -ne 0 ; do

  case "$1" in 

    --silent)           shift   # ignored
                        ;;
    --quiet)            shift   # ignored
                        ;;
    --report)           shift   # ignored
                        ;;
    --verbose)          shift   # ignored
                        ;;


    -u|--unlock)        shift
                        unlock=--unlock
                        break=
                        action=unlock
                        ;;
                        

    --break)            shift
                        unlock=
                        break=--break
                        action=break
                        ;;
                        

    -R|--root)          shift
                        if test $# -eq 0 ; then
                          printf "lock-revision: -R and --root require an argument\\n" 1>&2
                          printf "try --help\\n" 1>&2
                          exit 1
                        fi
                        archroot="$1"
                        shift
                        ;;

    -A|--archive)       shift
                        if test $# -eq 0 ; then
                          printf "lock-revision: -A and --archive require an argument\\n" 1>&2
                          printf "try --help\\n" 1>&2
                          exit 1
                        fi
                        archive="$1"
                        shift
                        ;;

    -e|--errname)       shift
                        if test $# -eq 0 ; then
                          printf "lock-revision: -e and --errname require an argument\\n" 1>&2
                          printf "try --help\\n" 1>&2
                          exit 1
                        fi
                        errname="$1"
                        shift
                        ;;


    -*)                 printf "lock-revision: unrecognized option (%s)\\n" "$1" 1>&2
                        printf "try --help\\n" 1>&2
                        exit 1
                        ;;

    *)                  break
                        ;;

  esac

done



################################################################
# Ordinary Arguments
# 

if test $# -ne 1 ; then
  printf "usage: lock-revision [options] version-or-revision\\n" 1>&2
  printf "try --help\\n" 1>&2
  exit 1
fi

rvn_spec="$1"

################################################################
# Sanity Check and Process Defaults
# 

larch valid-package-name -e "$errname" -t -- "$rvn_spec"

archive=`larch parse-package-name -R "$archroot" -A "$archive" --arch "$rvn_spec"`
category=`larch parse-package-name --basename "$rvn_spec"`
branch=`larch parse-package-name --package "$rvn_spec"`
myid=`larch my-id -u`


################################################################
# Establish an Archive Connection
# 
if test "$WITHARCHIVE" != "$archive" ; then

  exec larch with-archive -A "$archive" \
      larch lock-revision -R "$archroot" -A "$archive" --errname "$errname" $unlock $break "$rvn_spec"
fi


################################################################
# Which Lock?
# 
# The held lock for the base revision is:
# 
#       +revision-lock-held--absolute-0--id
# 
# and for the revision following patch level LVL:
# 
#       +revision-lock-held--LVL--id
# 

if larch valid-package-name -t --vsn "$rvn_spec" ; then
  version="`larch parse-package-name --package-version \"$rvn_spec\"`"
else
  version="`larch versions -r \"$archive/$branch\" | head -n 1`"
  if test -z "$version" ; then
    printf "%s: no versions on branch\\n" "$errname" 1>&2
    printf "  archive: %s\\n" "$archive" 1>&2
    printf "  branch: %s\\n" "$branch" 1>&2
    printf "\\n" 1>&2
    exit 1
  fi
fi


if larch valid-package-name -t --patch-level "$rvn_spec" ; then
  patchlvl=`larch parse-package-name --lvl "$rvn_spec"`
else
  patchlvl=`larch revisions -r "$archive/$version" | head -n 1`
  if test -z "$patchlvl" ; then
    patchlvl=base-0
  elif test "$patchlvl" = "version-0" ; then
    patchlvl=versionfix-1
  else
    type=${patchlvl%-*}
    seq=${patchlvl##*-}
    patchlvl="$type-$(($seq + 1))"
  fi
fi

################################################################
# Is the Revision In Sequence?
# 

wftp-home
if ! wftp-cd "$category/$branch/$version" ; then
  printf "\\n" 1>&2
  printf "%s: version not found in archive\\n" "$errname" 1>&2
  printf "  archive: %s\\n" "$archive" 1>&2
  printf "  version: %s\\n" "$version" 1>&2
  printf "\\n" 1>&2
  printf "See \"larch make-version --help\"\\n" 1>&2
  printf "\\n" 1>&2
  exit 1
fi


set +e
patchlvlre="base-0|patch-[0-9]+|version-0|versionfix-[1-9][0-9]*"

last=`( wftp-ls | grep -E "^($patchlvlre)$" )  \
        | sort -t- -k 1,1r -k 2,2rn \
        | head -n 1 `

latest=`( echo $patchlvl && ( wftp-ls | grep -E "^($patchlvlre)$" ) )  \
         | sort -t- -k 1,1r -k 2,2rn \
         | head -n 1 `
set -e

if test "(" "x$last" = "x$patchlvl" ")" -o "(" "$latest" != "$patchlvl" ")" ; then
  printf "%s: trying to create out of sequence revision.\\n" "$errname" 1>&2
  printf "  proposed patch level is %s\\n" "$patchlvl" 1>&2
  printf "  latest patch level is %s\\n" "$latest" 1>&2
  exit 1
fi

if test "x$last" = x ; then
  lock_lvl="absolute-0"
  lock="+revision-lock"
  locked_prefix="+revision-lock-held--absolute-0--"
  broken_dir="+revision-lock-broken--absolute-0"
else
  lock_lvl="$last"
  lock="$last/+revision-lock"
  locked_prefix="+revision-lock-held--$lock_lvl--"
  broken_dir="+revision-lock-broken--$lock_lvl"
fi

locked="$locked_prefix$myid"
broken="$broken_dir/,,remade-lock--$lock_lvl"

################################################################
# Do It
# 

# 
# Possible Branch States:
# 
# [A] No versions exist yet, lock is unlocked in the usual way:
#
# version/
#   version/+revision-lock                              == $lock
#     version/+revision-lock/+contents
# 
# 
# [B] Patch level LVL is the most recent, lock is unlocked in the usual way:
# 
# version/
#   version/LVL
#     version/LVL/+revision-lock                        == $lock
#       version/LVL/+revision-lock/+contents
# 
# 
# [C] Lock was recently broken, or a lock break operation was interrupted
# after breaking the lock, but before cleaning up:
# 
# version/
#   version/+revision-lock-broken--LVL                  == $broken_dir
#     version/+revision-lock-broken--LVL/,,remade-lock--LVL == $broken
#       version/+revision-lock-broken--LVL/,,remade-lock--LVL/+contents
#     version/+revision-lock-broken--LVL/...            junk from failed transaction
# 
# 
# [D] Lock is currently held by user UID
# 
# version/
#   version/+revision-lock-held--LVL--UID                       == $locked
#     version/+revision-lock-held--LVL--UID/+contents
#       version/+revision-lock-held--LVL--UID/+contents/... (stuff being installed)
# 
# 
# [-] Junk that can be left around when cleanups don't finish:
# 
# version/
#  version/+revision-lock-held--LVL--UID
#    (no +contents subdir)
# 
# version/
#   version/+revision-lock-broken--LVL
#     (no ,,remade-lock--LVL subdir)
#     version/+revision-lock-broken--LVL/...            junk from failed transaction
# 
# version/
#   version/version--(LVL + 1)                          i.e., completed next version
#     version/version--(LVL + 1)/,,remade-lock--LVL     failed attempt to break lock
#       version/version--(LVL + 1)/,,remade-lock--LVL/...  junk
#     version/version--(LVL + 1)/...                    revision dirs etc.
#
# in a lock dir:
# 
# ,,junk--UID--DATE--SEQ
# 

unlock_failed()
{
  printf "%s: lock break unsuccessful\\n" "$errname" 1>&2
  printf "  possible causes include:\\n" 1>&2
  printf "    transaction already committed\\n" 1>&2
  printf "    another user already broke the lock\\n" 1>&2
  printf "    communication with archive lost\\n" 1>&2
  printf "    a bug (hopefully not)\\n" 1>&2
  printf "\\n"
  exit 1
}

unlock_procedure()
{
  locked="$1"

  wftp-home
  wftp-cd "$category/$branch/$version"

  if ! wftp-cd "$locked/+contents" > /dev/null 2>&1 ; then
    # [A] -> [A]
    # [B] -> [B]
    # [C] -> [C]
    # 
    printf "%s: lock not held\\n" "$errname" 1>&2
    printf "\\n" 1>&2
    exit 1

  else
    # there is $version/$locked/+contents
    # 
    wftp-mkdir ",,remade-lock--$lock_lvl" > /dev/null 2>&1 \
    || true

    wftp-cd ",,remade-lock--$lock_lvl" > /dev/null 2>&1 \
    || unlock_failed

    # there is $version/$locked/+contents/,,remade-lock--LVL
    # 
    wftp-mkdir "+contents" > /dev/null 2>&1 \
    || true

    wftp-cd "+contents" > /dev/null 2>&1 \
    || unlock_failed

    # there is $version/$locked/+contents/,,remade-lock--LVL/+contents
    # 
    junk_name=,,junk--$myid--`date +%Y-%m-%d-%H:%M:%S--$$`

    (   wftp-rename "../../../../$broken_dir" "$junk_name" > /dev/null 2>&1 \
     && larch arch-rmrf "$junk_name" > /dev/null 2>&1 ) \
    || true

    wftp-cd ../../.. > /dev/null 2>&1 \
    || true
    
    # there is $version/$locked
    # [D] -> [C]
    # 
    # 
    wftp-rename +contents "../$broken_dir" > /dev/null 2>&1 \
    || unlock_failed

    # unlock succeeded -- clean up
    #
    wftp-cd .. > /dev/null 2>&1 \
    || true

    # there is $branch
    #
    wftp-rmdir "$locked" > /dev/null 2>&1 \
    || true


    wftp-cd "$broken_dir"
    for f in `wftp-ls` ; do
      if test "$f" != ",,remade-lock--$lock_lvl" ; then
        larch arch-rmrf "$f"
      fi
    done
    wftp-cd ".."

    # [C] -> [A]
    #  
    wftp-rename "$broken" "$lock" > /dev/null 2>&1 \
    || true

    wftp-rmdir "$broken_dir" > /dev/null 2>&1 \
    || true
    
  fi
}


case $action in

  lock)                 if wftp-cd "$locked/+contents" > /dev/null 2>&1 ; then
                          # [D] -> [D]
                          #
                          # Lock already held by this user.  Clean out any old
                          # contents.
                          # 
                          for file in `wftp-ls` ; do
                            if test "$file" != ",,remade-lock--$lock_lvl" ; then
                              larch arch-rmrf "$file"
                            fi
                          done
                          printf "%s\\n" "$category/$branch/$version/$locked"
                          exit 0

                        elif wftp-rename "$lock" "$locked" ; then
                          # [A], [B] -> [D]
                          # 
                          # newly acquired lock
                          # 
                          printf "%s\\n" "$category/$branch/$version/$locked"

                        elif wftp-rename "$broken" "$locked" ; then
                          # [C] -> [D]
                          #
                          # newly acquired, recently broken lock
                          # 
                          printf "%s\\n" "$category/$branch/$version/$locked"

                        else

                          printf "%s: unable to obtain lock for revision\\n" "$errname" 1>&2
                          printf "  lock may be busy\\n" 1>&2
                          printf "  see \"larch lock-revision --help\" for information\\n" 1>&2
                          printf "  about the --break option\\n" 1>&2
                          exit 1

                        fi
                        ;;


  unlock)               unlock_procedure "$locked"
                        ;;

  break)                broke_it=
                        for f in `wftp-ls` ; do
                          suffix=${f#$locked_prefix}

                          if test "$suffix" = "$f" ; then
                            continue
                          fi

                          wftp-home
                          wftp-cd "$category/$branch/$version/$f/+contents" > /dev/null 2>&1 \
                          || continue

                          wftp-cd ../..
                          unlock_procedure "$f"
                          broke_it=yes
                          break
                        done
                        if test -z "$broke_it" ; then
                          printf "%s: revision not locked\\n" "$errname"
                        else
                          printf "%s: lock broken\\n" "$errname"
                        fi
                        exit 0
                        ;;


esac
