#!/bin/bash

##
# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##

##
# WARNING: This script is intended for use by developers working on
# the Calendar Server code base.  It is not intended for use in a
# deployment configuration.
#
# DO NOT use this script as a system startup tool (eg. in /etc/init.d,
# /Library/StartupItems, launchd plists, etc.)
#
# For those uses, install the server properly (eg. with "./run -i
# /tmp/foo && cd /tmp/foo && pax -pe -rvw . /") and use the caldavd
# executable to start the server.
##

set -e
set -u

wd="$(cd "$(dirname "$0")" && pwd)";

##
# Command line
##

      verbose="";
       do_get="true";
     do_setup="true";
       do_run="true";
  force_setup="false";
disable_setup="false";
   print_path="false";
      install="";
    daemonize="-X";
         kill="false";
      restart="false";
  plugin_name="caldav";
 service_type="Combined";
     read_key="";
      profile="";
      reactor="";

if [ -z "${CALENDARSERVER_CACHE_DEPS-}" ]; then
  cache_deps="${wd}/.dependencies";
else
  cache_deps="${CALENDARSERVER_CACHE_DEPS}";
fi;

usage ()
{
  program="$(basename "$0")";

  if [ "${1--}" != "-" ]; then echo "$@"; echo; fi;

  echo "Usage: ${program} [-hvgsfnpdkrR] [-K key] [-iI dst] [-t type] [-S statsdirectory] [-P plugin]";
  echo "Options:";
  echo "	-h  Print this help and exit";
  echo "	-v  Be verbose";
  echo "	-g  Get dependencies only; don't run setup or run the server.";
  echo "	-s  Run setup only; don't run server";
  echo "	-f  Force setup to run";
  echo "	-n  Do not run setup";
  echo "	-p  Print PYTHONPATH value for server and exit";
  echo "        -d  Run caldavd as a daemon";
  echo "        -k  Stop caldavd";
  echo "        -r  Restart caldavd";
  echo "        -K  Print value of configuration key and exit";
  echo "        -i  Perform a system install into dst; implies -s";
  echo "        -I  Perform a home install into dst; implies -s";
  echo "        -t  Select the server process type (Master, Slave or Combined) [${service_type}]";
  echo "        -S  Write a pstats object for each process to the given directory when the server is stopped.";
  echo "	-P  Select the twistd plugin name [${plugin_name}]";
  echo "        -R  Twisted Reactor plugin to execute [${reactor}]";

  if [ "${1-}" == "-" ]; then return 0; fi;
  exit 64;
}

while getopts 'hvgsfnpdkrK:i:I:t:S:P:R:' option; do
  case "$option" in
    '?') usage; ;;
    'h') usage -; exit 0; ;;
    'v')       verbose="-v"; ;;
    'f')   force_setup="true"; ;;
    'k')          kill="true"; ;;
    'r')       restart="true"; ;;
    'd')     daemonize=""; ;;
    'P')   plugin_name="${OPTARG}"; ;;
    'R')       reactor="-R ${OPTARG}"; ;;
    't')  service_type="${OPTARG}"; ;;
    'K')      read_key="${OPTARG}"; ;;
    'S')       profile="--profiler cprofile -p ${OPTARG}"; ;;
    'g') do_get="true" ; do_setup="false"; do_run="false"; ;;
    's') do_get="true" ; do_setup="true" ; do_run="false"; ;;
    'p') do_get="false"; do_setup="false"; do_run="false"; print_path="true"; ;;
    'i') do_get="true" ; do_setup="true" ; do_run="false"; install="${OPTARG}"; install_flag="--root="; ;;
    'I') do_get="true" ; do_setup="true" ; do_run="false"; install="${wd}/build/dst"; install_flag="--root="; install_home="${OPTARG}"; ;;
  esac;
done;
shift $((${OPTIND} - 1));

if [ $# != 0 ]; then usage "Unrecognized arguments:" "$@"; fi;

##
# Set up paths
##

py_version ()
{
  local python="$1"; shift;
  echo "$("${python}" -c "from distutils.sysconfig import get_python_version; print get_python_version()")";
}

try_python ()
{
  local python="$1"; shift;

  if [ -z "${python}" ]; then return 1; fi;

  if ! type "${python}" > /dev/null 2>&1; then return 1; fi;
  local py_version="$(py_version "${python}")";
  if [ "${py_version/./}" -lt "25" ]; then return 1; fi;

  return 0;
}

for v in "" "2.6" "2.5"; do
  for p in								\
    "${PYTHON:=}"							\
    "python${v}"							\
    "/usr/local/bin/python${v}"						\
    "/usr/local/python/bin/python${v}"					\
    "/usr/local/python${v}/bin/python${v}"				\
    "/opt/bin/python${v}"						\
    "/opt/python/bin/python${v}"					\
    "/opt/python${v}/bin/python${v}"					\
    "/Library/Frameworks/Python.framework/Versions/${v}/bin/python"	\
    "/opt/local/bin/python${v}"						\
    "/sw/bin/python${v}"						\
    ;
  do
    if try_python "${p}"; then python="${p}"; break; fi;
  done;
  if [ -n "${python:-}" ]; then break; fi;
done;

if [ -z "${python:-}" ]; then
  echo "No suitable python found. Python 2.5 is required.";
  exit 1;
fi;

if [ -z "${caldav:-}" ]; then
  caldav="${wd}";
fi;

if [ -z "${caldavd_wrapper_command:-}" ]; then
  if [ "$(uname -s)" == "Darwin" ] && [ "$(uname -r | cut -d . -f 1)" -ge 9 ]; then
      caldavd_wrapper_command="launchctl bsexec /";
  else
      caldavd_wrapper_command="";
  fi;
fi;

    top="$(cd "${caldav}/.." && pwd -L)"
patches="${caldav}/lib-patches";
twisted="${top}/Twisted";
    dav="${twisted}/twisted/web2/dav"

if [ -z "${config:-}" ]; then
  config="${wd}/conf/caldavd-dev.plist";
fi;

conf_read_key ()
{
  local key="$1"; shift;

  # FIXME: This only works for simple values (no arrays, dicts)
  tr '\n' ' ' < "${config}"                                                 \
    | xpath "/plist/dict/*[preceding-sibling::key[1]='${key}'" 2> /dev/null \
    | sed -n 's|^<[^<][^<]*>\([^<]*\)</[^<][^<]*>.*$|\1|p';
}

if [ -n "${read_key}" ]; then
    conf_read_key "${read_key}";
    exit $?;
fi;

if "${kill}" || "${restart}"; then
    pidfile="$(conf_read_key "PIDFile")";
    if [ ! -r "${pidfile}" ]; then
        echo "Unreadable PID file: ${pidfile}";
        exit 1;
    fi;
    pid="$(cat "${pidfile}" | head -1)";
    if [ -z "${pid}" ]; then
        echo "No PID in PID file: ${pidfile}";
        exit 1;
    fi;
    echo "Killing process ${pid}";
    kill -TERM "${pid}";
    if ! "${restart}"; then
        exit 0;
    fi;
fi;

if [ -z "${PYTHONPATH:-}" ]; then
  user_python_path="";
else
  user_python_path=":${PYTHONPATH}";
fi;

       py_platform="$("${python}" -c "from distutils.util import get_platform; print get_platform()")";
        py_version="$(py_version "${python}")";
py_platform_libdir="lib.${py_platform}-${py_version}";

if [ -n "${install}" ] && ! echo "${install}" | grep '^/' > /dev/null; then
  install="$(pwd)/${install}";
fi;

svn_uri_base="$(svn info "${caldav}" --xml 2> /dev/null | sed -n 's|^.*<root>\(.*\)</root>.*$|\1|p')";

if [ -z "${svn_uri_base}" ]; then
  svn_uri_base="http://svn.calendarserver.org/repository/calendarserver";
fi;

export PYTHONPATH="${caldav}";

# Find a command that can hash up a string for us
if type -t openssl > /dev/null; then
  hash="hash"; hash () { openssl dgst -md5; }
elif type -t md5 > /dev/null; then
  hash="md5";
elif type -t md5sum > /dev/null; then
  hash="md5sum";
elif type -t cksum > /dev/null; then
  hash="hash"; hash () { cksum | cut -f 1 -d " "; }
elif type -t sum > /dev/null; then
  hash="hash"; hash () { sum | cut -f 1 -d " "; }
else
  hash="";
fi;

##
# Routines
##

run () {
  if "${print_path}"; then
    echo "${PYTHONPATH}";
    exit 0;
  fi;

  echo "";
  echo "Using ${python} as Python";

  if "${do_run}"; then
    if [ ! -f "${config}" ]; then
      echo "";
      echo "Missing config file: ${config}";
      echo "You might want to start by copying the test configuration:";
      echo "";
      echo "  cp conf/caldavd-test.plist conf/caldavd-dev.plist";
      echo "";
      exit 1;
    fi;

    cd "${wd}";
    if [ ! -d "${wd}/logs" ]; then
      mkdir "${wd}/logs";
    fi;

    echo "";
    echo "Starting server...";
    exec ${caldavd_wrapper_command}                   \
        "${caldav}/bin/caldavd" ${daemonize}          \
        -f "${config}"                                \
        -T "${twisted}/bin/twistd"                    \
        -P "${plugin_name}"                           \
        -t "${service_type}"                          \
        ${reactor}                                    \
        ${profile};
    cd /;
  fi;
}

apply_patches () {
  local name="$1"; shift;
  local path="$1"; shift;

  if [ -d "${patches}/${name}" ]; then
    echo "";
    echo "Applying patches to ${name} in ${path}...";

    cd "${path}";
    find "${patches}/${name}"                  \
        -type f                                \
        -name '*.patch'                        \
        -print                                 \
        -exec patch -p0 --forward -i '{}' ';';
    cd /;

  fi;

  echo ""
  echo "Removing build directory ${path}/build..."
  rm -rf "${path}/build";
  echo "Removing pyc files from ${path}..."
  find "${path}" -type f -name '*.pyc' -print0 | xargs -0 rm -f;
}

www_get () {
  if ! "${do_get}"; then return 0; fi;

  local name="$1"; shift;
  local path="$1"; shift;
  local  url="$1"; shift;

  if "${force_setup}" || [ ! -d "${path}" ]; then
    local ext="$(echo "${url}" | sed 's|^.*\.\([^.]*\)$|\1|')";

    case "${ext}" in
      gz|tgz) decompress="gzip -d -c"; ;;
      bz2)    decompress="bzip2 -d -c"; ;;
      tar)    decompress="cat"; ;;
      *)
        echo "Unknown extension: ${ext}";
        exit 1;
        ;;
    esac;

    echo "";

    if [ -n "${cache_deps}" ] && [ -n "${hash}" ]; then
      mkdir -p "${cache_deps}";

      cache_file="${cache_deps}/${name}-$(echo "${url}" | "${hash}")-$(basename "${url}")";

      if [ ! -f "${cache_file}" ]; then
	echo "Downloading ${name}...";
	curl -L "${url}" -o "${cache_file}";
      fi;

      echo "Unpacking ${name} from cache...";
      get () { cat "${cache_file}"; }
    else
      echo "Downloading ${name}..."; 
      get () { curl -L "${url}"; }
    fi;

    rm -rf "${path}";
    cd "$(dirname "${path}")";
    get | ${decompress} | tar -xvf -;
    apply_patches "${name}" "${path}";
    cd /;
  fi;
}

svn_get () {
  if ! "${do_get}"; then return 0; fi;

  local     name="$1"; shift;
  local     path="$1"; shift;
  local      uri="$1"; shift;
  local revision="$1"; shift;

  if [ -d "${path}" ]; then
    local wc_uri="$(svn info --xml "${path}" 2> /dev/null | sed -n 's|^.*<url>\(.*\)</url>.*$|\1|p')";

    if "${force_setup}"; then
      # Verify that we have a working copy checked out from the correct URI
      if [ "${wc_uri}" != "${uri}" ]; then
        echo "";
        echo "Current working copy (${path}) is from the wrong URI: ${wc_uri} != ${uri}";
        rm -rf "${path}";
        svn_get "${name}" "${path}" "${uri}" "${revision}";
        return $?;
      fi;

      echo "";

      echo "Reverting ${name}...";
      svn revert -R "${path}";

      echo "Updating ${name}...";
      svn update -r "${revision}" "${path}";

      apply_patches "${name}" "${path}";
    else
      if ! "${print_path}"; then
        # Verify that we have a working copy checked out from the correct URI
        if [ "${wc_uri}" != "${uri}" ]; then
          echo "";
          echo "Current working copy (${path}) is from the wrong URI: ${wc_uri} != ${uri}";
          echo "Performing repository switch for ${name}...";
          svn switch -r "${revision}" "${uri}" "${path}";

          apply_patches "${name}" "${path}";
        else
          local svnversion="$(svnversion "${path}")";
          if [ "${svnversion%%[M:]*}" != "${revision}" ]; then
            echo "";
            echo "Updating ${name}...";
            svn update -r "${revision}" "${path}";

            apply_patches "${name}" "${path}";
          fi;
        fi;
      fi;
    fi;
  else
    echo "";

    checkout () {
      echo "Checking out ${name}...";
      svn checkout -r "${revision}" "${uri}@${revision}" "${path}";
    }

    if [ "${revision}" != "HEAD" ] && [ -n "${cache_deps}" ] && [ -n "${hash}" ]; then
      local cache_file="${cache_deps}/${name}-$(echo "${uri}" | "${hash}")@r${revision}.tgz";

      mkdir -p "${cache_deps}";

      if [ -f "${cache_file}" ]; then
	echo "Unpacking ${name} from cache...";
	mkdir -p "${path}";
	tar -C "${path}" -xvzf "${cache_file}";
      else
	checkout;
	echo "Caching ${name}...";
	tar -C "${path}" -cvzf "${cache_file}" .;
      fi;
    else
      checkout;
    fi;

    apply_patches "${name}" "${path}";
  fi;
}

py_build () {
    local     name="$1"; shift;
    local     path="$1"; shift;
    local optional="$1"; shift;

    if "${do_setup}"; then
      echo "";
      echo "Building ${name}...";
      cd "${path}";
      if ! "${python}" ./setup.py -q build --build-lib "build/${py_platform_libdir}" "$@"; then
          if "${optional}"; then
              echo "WARNING: ${name} failed to build.";
              echo "WARNING: ${name} is not required to run the server; continuing without it.";
          else
              return $?;
          fi;
      fi;
      cd /;
    fi;
}

py_install () {
  local name="$1"; shift;
  local path="$1"; shift;

  if [ -n "${install}" ]; then
    echo "";
    echo "Installing ${name}...";
    cd "${path}";
    "${python}" ./setup.py install "${install_flag}${install}";
    cd /;
  fi;
}

py_have_module () {
    local module="$1"; shift;

    PYTHONPATH="" "${python}" -c "import ${module}" > /dev/null 2>&1;
}

##
# Download and set up dependancies
##

#
# Zope Interface
#

if ! py_have_module zope.interface; then
  zope="${top}/zope.interface-3.3.0";

  www_get "Zope Interface" "${zope}" http://www.zope.org/Products/ZopeInterface/3.3.0/zope.interface-3.3.0.tar.gz;
  py_build "Zope Interface" "${zope}" false;
  py_install "Zope Interface" "${zope}";

  export PYTHONPATH="${PYTHONPATH}:${zope}/build/${py_platform_libdir}";
fi;

#
# PyXML
#

if ! py_have_module xml.dom.ext; then
  xml="${top}/PyXML-0.8.4";

  www_get "PyXML" "${xml}" http://internap.dl.sourceforge.net/sourceforge/pyxml/PyXML-0.8.4.tar.gz;
  py_build "PyXML" "${xml}" false;
  py_install "PyXML" "${xml}";

  export PYTHONPATH="${PYTHONPATH}:${xml}/build/${py_platform_libdir}";
fi;

#
# PyOpenSSL
#

if ! py_have_module OpenSSL; then
  ssl="${top}/pyOpenSSL-0.7";

  www_get "PyOpenSSL" "${ssl}" http://pypi.python.org/packages/source/p/pyOpenSSL/pyOpenSSL-0.7.tar.gz;
  py_build "PyOpenSSL" "${ssl}" false;
  py_install "PyOpenSSL" "${ssl}";

  export PYTHONPATH="${PYTHONPATH}:${ssl}/build/${py_platform_libdir}";
fi;

#
# PyKerberos
#

if type krb5-config > /dev/null; then
  if ! py_have_module kerberos; then
    kerberos="${top}/PyKerberos";

    svn_get "PyKerberos" "${kerberos}" "${svn_uri_base}/PyKerberos/trunk" 4241;
    py_build "PyKerberos" "${kerberos}" false; # FIXME: make optional
    py_install "PyKerberos" "${kerberos}";

    export PYTHONPATH="${PYTHONPATH}:${kerberos}/build/${py_platform_libdir}";
  fi;
fi;

#
# PyOpenDirectory
#

if [ "$(uname -s)" == "Darwin" ]; then
  if ! py_have_module opendirectory; then
    opendirectory="${top}/PyOpenDirectory";

    svn_get "PyOpenDirectory" "${opendirectory}" "${svn_uri_base}/PyOpenDirectory/trunk" 4827;
    py_build "PyOpenDirectory" "${opendirectory}" false;
    py_install "PyOpenDirectory" "${opendirectory}";

    export PYTHONPATH="${PYTHONPATH}:${opendirectory}/build/${py_platform_libdir}";
  fi;
fi;

#
# xattr
#

if ! py_have_module xattr; then
  xattr="${top}/xattr";

  svn_get "xattr" "${xattr}" http://svn.red-bean.com/bob/xattr/releases/xattr-0.5 1013;
  py_build "xattr" "${xattr}" false;
  py_install "xattr" "${xattr}";

  export PYTHONPATH="${PYTHONPATH}:${xattr}/build/${py_platform_libdir}";
fi;

#
# select26
#

if [ "${py_version}" != "${py_version##2.5}" ] && ! py_have_module select26; then
  select26="${top}/select26-0.1a3"

  www_get "select26" "${select26}" http://pypi.python.org/packages/source/s/select26/select26-0.1a3.tar.gz;
  py_build "select26" "${select26}" true;
  py_install "select26" "${select26}";

  export PYTHONPATH="${PYTHONPATH}:${select26}/build/${py_platform_libdir}";
fi;

if ! type memcached >& /dev/null; then
  #
  # libevent
  #
  libevent="${top}/libevent-1.4.8-stable"

  www_get "libevent" "${libevent}" http://monkey.org/~provos/libevent-1.4.8-stable.tar.gz

  if "${do_setup}" && (
    "${force_setup}" || [ ! -d "${libevent}/_root" ]
  ); then
    echo "";
    echo "Building libevent...";
    cd "${libevent}";
    ./configure --prefix="${libevent}/_root";
    make;
    make install;
  fi;

  export LD_LIBRARY_PATH="${LD_LIBRARY_PATH:-}:${libevent}/_root/lib";
  export DYLD_LIBRARY_PATH="${DYLD_LIBRARY_PATH:-}:${libevent}/_root/lib";

  #
  # memcached
  #
  memcached="${top}/memcached-1.2.6"

  www_get "memcached" "${memcached}" http://www.danga.com/memcached/dist/memcached-1.2.6.tar.gz;

  if "${do_setup}" && (
    "${force_setup}" || [ ! -d "${memcached}/_root" ]
  ); then
    echo "";
    echo "Building memcached...";
    cd "${memcached}";
    ./configure --prefix="${memcached}/_root" \
                --with-libevent="${libevent}/_root" \
                --enable-threads;
    make;
    make install;
  fi;

  export PATH="${PATH}:${top}/memcached-1.2.6/_root/bin";
fi;

#
# Twisted
#

case "${USER}" in
  wsanchez)
    proto="svn+ssh";
    ;;
  *)
    proto="svn";
    ;;
esac;
svn_uri="${proto}://svn.twistedmatrix.com/svn/Twisted/branches/dav-take-two-3081-4";
svn_get "Twisted" "${twisted}" "${svn_uri}" 26969;

# No py_build step, since we tend to do edit Twisted, we want the sources in
# PYTHONPATH, not a build directory.

py_install "Twisted" "${twisted}";

export PYTHONPATH="${PYTHONPATH}:${twisted}";

# twisted.web2 doesn't get installed by default
if [ -n "${install}" ]; then
  echo "";
  echo "Installing Twisted.web2...";
  cd "${twisted}";
  "${python}" ./twisted/web2/topfiles/setup.py install "${install_flag}${install}";
  cd /;
fi;

#
# dateutil
#

if ! py_have_module dateutil; then
    dateutil="${top}/python-dateutil-1.4.1";

    www_get "dateutil" "${dateutil}" http://www.labix.org/download/python-dateutil/python-dateutil-1.4.1.tar.gz;
    py_install "dateutil" "${dateutil}";

    export PYTHONPATH="${PYTHONPATH}:${dateutil}";
fi;

#
# vobject
#

vobject="${top}/vobject";

case "${USER}" in
  cyrusdaboo)
    base="svn+ssh://cdaboo@svn.osafoundation.org/svn";
    ;;
  *)
    base="http://svn.osafoundation.org";
    ;;
esac;
svn_uri="${base}/vobject/trunk";
svn_get "vObject" "${vobject}" "${svn_uri}" 212;

py_install "vObject" "${vobject}";

export PYTHONPATH="${PYTHONPATH}:${vobject}";

#
# PyDirector
#

if ! py_have_module pydirector; then
  pydirector="${top}/pydirector-1.0.0";
  www_get "PyDirector" "${pydirector}" http://internap.dl.sourceforge.net/sourceforge/pythondirector/pydirector-1.0.0.tar.gz;
  py_build "PyDirector" "${pydirector}" false;
  py_install "PyDirector" "${pydirector}";

  export PYTHONPATH="${PYTHONPATH}:${pydirector}/build/${py_platform_libdir}";
fi;

#
# CalDAVTester
#

caldavtester="${top}/CalDAVTester";

svn_get "CalDAVTester" "${caldavtester}" "${svn_uri_base}/CalDAVTester/trunk" 4822;

#
# PyFlakes
#

pyflakes="${top}/Pyflakes";

svn_get "Pyflakes" "${pyflakes}" http://divmod.org/svn/Divmod/trunk/Pyflakes 17198;

#
# Calendar Server
#

py_install "Calendar Server" "${caldav}";

##
# Do home install
# This is a hack, but it's needed because installing with --home doesn't work for python-dateutil.
##

if [ -n "${install_home:-}" ]; then
  py_prefix="$("${python}" -c "import sys; print sys.prefix;")";
  py_libdir="$("${python}" -c "from distutils.sysconfig import get_python_lib; print get_python_lib(1);")";

  install -d "${install_home}";
  install -d "${install_home}/bin";
  install -d "${install_home}/conf";
  install -d "${install_home}/lib/python";

  rsync -av "${install}${py_prefix}/bin/" "${install_home}/bin/";
  rsync -av "${install}${py_libdir}/" "${install_home}/lib/python/";
  rsync -av "${install}${py_prefix}/caldavd/" "${install_home}/caldavd/";

  rm -rf "${install}";
fi;

##
# Run the server
##

export PYTHONPATH="${PYTHONPATH}${user_python_path}";

run;
