#!/usr/bin/env python
# -*- Mode: python -*-
#
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewVC
# distribution or at http://viewvc.org/license-1.html.
#
# For more information, visit http://viewvc.org/
#
# -----------------------------------------------------------------------
#
# install script for viewvc -- temporary?
#
# ### this will eventually be replaced by autoconf plus tools. an
# ### interactive front-end to ./configure may be provided.
#
# -----------------------------------------------------------------------

import os
import sys
import string
import re
import traceback
import py_compile
import getopt
import StringIO

# get access to our library modules
sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), 'lib'))

import compat
import viewvc
import compat_ndiff
version = viewvc.__version__


## installer text
INFO_TEXT = """

This is the ViewVC %s installer.

It will allow you to choose the install path for ViewVC.  You will
now be asked some installation questions.

Defaults are given in square brackets.  Just hit [Enter] if a default
is okay.
""" % version

## installer defaults
DESTDIR = None
ROOT_DIR = None


## list of files for installation
## tuple (source path, destination path, install mode, true/false flag for
##        search-and-replace, flag or text for prompt before replace, 
##        compile_it)
##       

FILE_INFO_LIST = [
    ("bin/cgi/viewvc.cgi", "bin/cgi/viewvc.cgi", 0755, 1, 0, 0),
    ("bin/cgi/query.cgi", "bin/cgi/query.cgi", 0755, 1, 0, 0),
    ("bin/mod_python/viewvc.py", "bin/mod_python/viewvc.py", 0755, 1, 0, 0),
    ("bin/mod_python/query.py", "bin/mod_python/query.py", 0755, 1, 0, 0),
    ("bin/mod_python/handler.py", "bin/mod_python/handler.py", 0755, 1, 0, 0),
    ("bin/mod_python/.htaccess", "bin/mod_python/.htaccess", 0755, 0, 0, 0),
    ("bin/standalone.py", "bin/standalone.py", 0755, 1, 0, 0),
    ("viewvc.conf.dist", "viewvc.conf", 0644, 0,
	     """Note: If you are upgrading from viewcvs-0.7 or earlier: 
The section [text] has been removed from viewcvs.conf.  The functionality
went into the new files in subdirectory templates.""", 0),
    ("cvsgraph.conf.dist", "cvsgraph.conf", 0644, 0, 1, 0),

    ("bin/loginfo-handler", "bin/loginfo-handler", 0755, 1, 0, 0),
    ("bin/cvsdbadmin", "bin/cvsdbadmin", 0755, 1, 0, 0),
    ("bin/svndbadmin", "bin/svndbadmin", 0755, 1, 0, 0),
    ("bin/make-database", "bin/make-database", 0755, 1, 0, 0),
    ]

if sys.platform == "win32":
  FILE_INFO_LIST.extend([
    ("bin/asp/viewvc.asp", "bin/asp/viewvc.asp", 0755, 1, 0, 0),
    ("bin/asp/query.asp", "bin/asp/query.asp", 0755, 1, 0, 0),
  ])

TREE_LIST = [
  ("lib",       "lib",       0),
  ("templates", "templates", 1),
  ]

# used to escape substitution strings passed to re.sub(). re.escape() is no
# good because it blindly puts backslashes in front of anything that is not
# a number or letter regardless of whether the resulting sequence will be
# interpreted.
def ReEscape(str):
  return string.replace(str, "\\", "\\\\")

def Error(text, etype=None, evalue=None):
    print
    print "[ERROR] %s" % text
    if etype:
        print '[ERROR] ',
        traceback.print_exception(etype, evalue, None, file=sys.stdout)
    sys.exit(1)
    

def MkDir(path):
    try:
        compat.makedirs(path)
    except os.error, e:
        if e[0] == 17:
            # EEXIST: file exists
            return
        if e[0] == 13:
            # EACCES: permission denied
            Error("You do not have permission to create directory %s" % path)
        Error("Unknown error creating directory %s" % path, OSError, e)



def SetOnePath(contents, var, value):
    pattern = re.compile('^' + var + r'\s*=\s*.*$', re.MULTILINE)
    repl = '%s = r"%s"' % (var, os.path.join(ROOT_DIR, value))
    return re.sub(pattern, ReEscape(repl), contents)


def SetPythonPaths(contents):
    if contents[:2] == '#!':
        shbang = '#!' + sys.executable
        contents = re.sub('^#![^\n]*', ReEscape(shbang), contents)
    contents = SetOnePath(contents, 'LIBRARY_DIR', 'lib')
    contents = SetOnePath(contents, 'CONF_PATHNAME', 'viewvc.conf')
    return contents


def InstallFile(src_path, dest_path, mode, set_python_paths, prompt_replace,
                compile_it):
  dest_path = os.path.join(ROOT_DIR, dest_path)
    
  if prompt_replace and os.path.exists(DESTDIR + dest_path):
    # Collect ndiff output from ndiff
    sys.stdout = StringIO.StringIO()
    compat_ndiff.main([DESTDIR + dest_path, src_path])
    ndiff_output = sys.stdout.getvalue()

    # Return everything to normal
    sys.stdout = sys.__stdout__

    # Collect the '+ ' and '- ' lines
    # total collects the difference lines to be printed later
    total = ""
    # I use flag to throw out match lines.
    flag = 1
    for line in string.split(ndiff_output,'\n'):
      # Print line if it is a difference line
      if line[:2] == "+ " or line[:2] == "- " or line[:2] == "? ":
        total = total + line + "\n"
        flag = 1
      else:
        # Compress lines that are the same to print one blank line
        if flag:
          total = total + "\n"
          flag = 0

    if total == "\n":
      print "    File %s exists,\n    but there is no difference between target and source files.\n" % (DESTDIR + dest_path)
      return

    if type(prompt_replace) == type(""):
      print prompt_replace
    while 1:
      temp = raw_input("""
    File %s exists and is different from source file.
      DO YOU WANT TO,
        overwrite [o]
        do not overwrite [d]
        view differences [v]: """ % (DESTDIR + dest_path))
      print

      temp = string.lower(temp[0])

      if temp == "d":
        return

      if temp == "v":
        if string.lower(src_path[-4:]) in [ '.gif', '.png', '.jpg' ]:
          print 'Can not print differences between binary files'
        else:
          print total
          print """
LEGEND
 A leading '- ' indicates line to remove from installed file
 A leading '+ ' indicates line to add to installed file
 A leading '? ' shows intraline differences."""

      if temp == "o": 
        ReplaceFile(src_path, dest_path, mode, set_python_paths,
                    prompt_replace, compile_it)
        return
  else:
    ReplaceFile(src_path, dest_path, mode, set_python_paths,
                prompt_replace, compile_it)
  return

def ReplaceFile(src_path, dest_path, mode, set_python_paths,
                prompt_replace, compile_it):
  try:
    contents = open(src_path, "rb").read()
  except IOError, e:
    Error(str(e))

  if set_python_paths:
    contents = SetPythonPaths(contents)

  ## write the file to the destination location
  path, basename = os.path.split(DESTDIR + dest_path)
  MkDir(path)
  
  try:
    open(DESTDIR + dest_path, "wb").write(contents)
  except IOError, e:
    if e[0] == 13:
      # EACCES: permission denied
      Error("You do not have permission to write file %s" % dest_path)
    Error("Unknown error writing file %s" % dest_path, IOError, e)
    
  os.chmod(DESTDIR + dest_path, mode)
  
  if compile_it:
    py_compile.compile(DESTDIR + dest_path,
                       DESTDIR + dest_path + "c" , dest_path)
  
  return


def install_tree(src_path, dst_path, prompt_replace):
  files = os.listdir(src_path)
  files.sort()
  for fname in files:
    # eliminate some items which appear in a development area
    if fname == 'CVS' or fname == '.svn' or fname == '_svn' \
       or fname[-4:] == '.pyc' or fname[-5:] == '.orig' \
       or fname[-4:] == '.rej' or fname[0] == '.' \
       or fname[-1] ==  '~':
      continue

    src = os.path.join(src_path, fname)
    dst = os.path.join(dst_path, fname)
    if os.path.isdir(src):
      install_tree(src, dst, prompt_replace)
    else:
      print " ", src
      set_paths = 0
      compile_it = fname[-3:] == '.py'
      InstallFile(src, dst, 0644, set_paths, prompt_replace, compile_it)

  # prompt to delete all .py and .pyc files that don't belong in installation
  full_dst_path = os.path.join(DESTDIR + ROOT_DIR, dst_path)
  for fname in os.listdir(full_dst_path):
    if not os.path.isfile(os.path.join(full_dst_path, fname)) or \
       not ((fname[-3:] == '.py' and fname not in files) or
            (fname[-4:] == '.pyc' and fname[:-1] not in files)):
      continue
    
    while 1:
      temp = raw_input("""
    File %s does not belong in ViewVC %s.
    DO YOU WANT TO,
        delete [d]
        leave as is [l]: """ % (os.path.join(dst_path, fname), version))
      print
      
      temp = string.lower(temp[0])
      
      if temp == "l":
        break
      
      if temp == "d": 
        os.unlink(os.path.join(full_dst_path, fname))
        break

## MAIN
if __name__ == "__main__":
    # option parsing
    try:
      optlist, args = getopt.getopt(sys.argv[1:], "", ['prefix=', 'destdir='])
    except getopt.GetoptError, e:
      Error("Invalid option", getopt.GetoptError, e)
    for opt, arg in optlist:
      if opt == '--prefix':
        ROOT_DIR = arg
      if opt == '--destdir':
        DESTDIR = arg

    ## print greeting
    print INFO_TEXT

    ## prompt for ROOT_DIR if none provided
    if ROOT_DIR is None:
      if sys.platform == "win32":
        pf = os.getenv("ProgramFiles", "C:\\Program Files")
        default = os.path.join(pf, "viewvc-" + version)  
      else:
        default = "/usr/local/viewvc-" + version
      temp = string.strip(raw_input("Installation path [%s]: " % default))
      print
      if len(temp):
        ROOT_DIR = temp
      else:
        ROOT_DIR = default
        
    ## prompt for DESTDIR if none provided
    if DESTDIR is None:
      default = ''
      temp = string.strip(raw_input(
        "DESTDIR path (generally, only package maintainers will need "
        "to change\nthis) [%s]: " % default))
      print
      if len(temp):
        DESTDIR = temp
      else:
        DESTDIR = default
        
    ## install the files
    print "Installing ViewVC to:", ROOT_DIR,
    if DESTDIR:
      print "(DESTDIR = %s)" % (DESTDIR)
    else:
      print

    for args in FILE_INFO_LIST:
        print " ", args[0]
        apply(InstallFile, args)

    for args in TREE_LIST:
      apply(install_tree, args)

    print """

ViewVC File Installation Complete

Consult INSTALL for detailed information to finish the installation
and configure ViewVC for your system.

Overview of remaining steps:

1) Edit the %s file.

2) Configure an existing web server to run (or copy to cgi-bin) 
   %s.
      OR 
   Run the web server that comes with ViewVC at 
   %s.
""" % (
    os.path.join(ROOT_DIR, 'viewvc.conf'),
    os.path.join(ROOT_DIR, 'bin', 'cgi', 'viewvc.cgi'),
    os.path.join(ROOT_DIR, 'standalone.py'))
