# -*- coding: iso-8859-1 -*-

# ==============================================================================
# COPYRIGHT (C) 1991 - 2003  EDF R&D                  WWW.CODE-ASTER.ORG
# THIS PROGRAM IS FREE SOFTWARE; YOU CAN REDISTRIBUTE IT AND/OR MODIFY
# IT UNDER THE TERMS OF THE GNU GENERAL PUBLIC LICENSE AS PUBLISHED BY
# THE FREE SOFTWARE FOUNDATION; EITHER VERSION 2 OF THE LICENSE, OR
# (AT YOUR OPTION) ANY LATER VERSION.
#
# THIS PROGRAM IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT
# WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF
# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU
# GENERAL PUBLIC LICENSE FOR MORE DETAILS.
#
# YOU SHOULD HAVE RECEIVED A COPY OF THE GNU GENERAL PUBLIC LICENSE
# ALONG WITH THIS PROGRAM; IF NOT, WRITE TO EDF R&D CODE_ASTER,
#    1 AVENUE DU GENERAL DE GAULLE, 92141 CLAMART CEDEX, FRANCE.
# ==============================================================================

"""
Tools to maintain an astk installation.
Note that 'as_run' is a part of astk, so it tries to update itself.

Methods are called by an ASTER_RUN object.
"""

__revision__ = "$Id: astk_maintenance.py 2618 2006-12-15 10:32:40Z courtois $"

# ----- differ messages translation
def _(mesg):
   return mesg

import sys
import os
import re
import tarfile
from glob      import glob

from as_run    import RunAsterError
from utils     import search_cmd, get_absolute_dirname, \
                      IterVersion, change_text_file

#-------------------------------------------------------------------------------
def get_devel_param(run):
   """Returns user/server on the main server.
   """
   user = run['devel_server_user']
   mach = run['devel_server_ip']
   if user == '':
      run.Mess(_("remote connection to '%s' may fail :%s" \
                 "'devel_server_user' not defined in '%s'") \
            % (mach, os.linesep, run.user_rc), _('<A>_ALARM'))
   return user, mach

#-------------------------------------------------------------------------------
def AstkUpdate(run, *args):
   """Update current astk installation.
   """
   if len(args) > 0:
      run.parser.error(_("'--%s' requires no argument") % run.current_action)

   run.print_timer = True
   if not run['local']:
      if run['update_protocol'] == 'HTTP':
         REPSRC = run['http_server_user']+'@'+run['http_server_ip']+ \
               ':'+run['http_rep_maj']
      else:
         user, mach = get_devel_param(run)
         REPSRC = '%s@%s:%s' % (user, mach, run['devel_rep_maj'])
   else:
      REPSRC = run['local_rep_maj']

   # version courante
   if run['version'] == 'VDEV':
      run.Mess(_('It is too dangerous to update a development version !'))
      return
   run.timer.Start('Download update archives')
   run.Mess(_('Searching astk updates following release %s...') % run['version'])

   reptrav = os.path.join(run['tmp_user'], 'astk_update.'+run['num_job'])
   if not run['debug']:
      run.to_delete.append(reptrav)
   run.MkDir(reptrav)
   os.chdir(reptrav)
   
   # format des packages
   fmt_pkg = 'astk-%s-1.noarch.tar.gz'
   fmt_cnt = 'astk-%s'
   
   # ----- search for sub-releases of current version
   itervers = IterVersion(run['version'], 1, 3, 5)
   vlast = run['version']
   fmaj = None
   for vch in itervers:
      # nom du fichier archive
      src  = os.path.join(REPSRC, fmt_pkg % vch)
      dest = os.path.join(reptrav, os.path.basename(src))
      # use niverr='INFO' to ignore failure
      jret = run.Copy(dest, src, niverr='SILENT', \
            protocol=run['update_protocol'])
      if jret == 0:
         vlast = vch
         fmaj = dest
      else:
         itervers.jump()
   
   if vlast != run['version']:
      run.Mess(_('Last release found : %s') % vlast + os.linesep)
   run.timer.Stop('Download update archives')
   
   # ----- start update
   if fmaj:
      mail = []
      run.ExitOnFatalError = False
      try:
         update_astk(run, fmaj, fmt_cnt % vlast, reptrav)
      except RunAsterError, msg:
         res = 'FAILED'
         run.ExitOnFatalError = True
         import traceback
         mail.extend([_('Exception raised by AstkUpdate:'),
            '-'*60, 'Traceback:'])
         mail.append(''.join(traceback.format_tb(sys.exc_traceback)))
         mail.append('-'*60)
         mail.append(_('Exit code : %s') % msg)
         run.Mess(os.linesep.join(mail), _('<F>_UPDATE_FAILED'))
      else:
         res = 'COMPLETED'
         run.ExitOnFatalError = True
         mail.append(_('astk update ended successfully from %s to %s') % \
               (run['version'], vlast))
         run.Mess(os.linesep.join(mail), 'OK')
      if run['report_to'] != '':
         run.SendMail(dest=run['report_to'],
            text = os.linesep.join(mail),
            subject = 'AstkUpdate %s on %s from %s to %s' % \
                  (res, run.GetHostName(), run['version'], vlast))
   else:
      run.Mess(_('no update found'), 'OK')

#-------------------------------------------------------------------------------
def update_astk_dict_trans(run):
   """Retourne le dictionnaire pour les substitution.
   """
   # HOME_ASTK
   home_astk = get_absolute_dirname(run.astk_serv_root)
   
   # récupère le shell utilisé actuellement par as_serv
   fas_serv = os.path.join(run.astk_serv_root, 'bin', 'as_serv')
   shell = open(fas_serv, 'r').readline().strip().replace('#!', '')
   
   # récupère la commande wish utilisée par astk
   fastk = os.path.join(home_astk, 'ASTK_CLIENT', 'bin', 'astk')
   wish_cmd = search_cmd(re.escape('/bin/wish'), fastk)
   
   d_trans = {
      'ASTER_ROOT'      : run.aster_root,
      'SHELL_EXECUTION' : shell,
      'HOME_ASTK'       : home_astk,
      'WISH_EXE'        : wish_cmd or 'wish',
      'TOOLS_DIR'       : os.path.join(run.aster_root, 'outils'),
      'PYTHON_EXE'      : sys.executable,
      'MAXCMDLEN'       : run.system.MaxCmdLen,
   }
   run.DBG(d_trans)
   return d_trans
   
#-------------------------------------------------------------------------------
def update_astk(run, arch, content, reptrav):
   """Réalise la mise à jour d'astk client et serveur à partir du fichier `arch`.
   """
   # 1. ----- extract archive
   os.chdir(reptrav)
   run.timer.Start('Extraction of archives')
   run.Mess(_('Extraction of archives'), 'TITLE')
   if tarfile.is_tarfile(arch):
      # 1.1. ----- extraction
      run.VerbStart(_('extract archive %s') % arch, verbose=True)
      jret = 0
      try:
         tar = tarfile.open(arch, 'r')
         tar.errorlevel = 2
         for ti in tar:
            tar.extract(ti)
      except tarfile.ExtractError:
         jret = 4
      run.VerbEnd(jret, verbose=True)
      tar.close()
      if jret != 0:
         run.Mess(_('error during extracting archive %s') % arch,
               _('<F>_TAR_ERROR'))
   else:
      run.Mess(_('invalid tar (compressed) archive or ' \
            'file not found : %s') % arch, _('<F>_FILE_NOT_FOUND'))
   run.timer.Stop('Extraction of archives')
   if not os.path.isdir(content):
      run.Mess(_("expected a directory '%s' in archive") % content,
            _('<F>_FILE_NOT_FOUND'))

   # 2. ----- configure GUI
   run.timer.Start('Configuration of astk files')
   run.Mess(_('Configuration of astk files'), 'TITLE')
   # --- remplacement des variables
   d_trans = update_astk_dict_trans(run)
   
   repcnt = os.path.join(reptrav, content)
   os.chdir(repcnt)
   l_bin = [os.path.join('ASTK_CLIENT', 'bin', 'astk'),
            os.path.join('ASTK_CLIENT', 'bin', 'bsf'),]
   
   l_bin.extend(glob(os.path.join('ASTK_SERV', 'bin', '*')))
   l_bin.append(os.path.join('ASTK_SERV', 'lib', 'sub_script'))
   
   l_files = l_bin[:]
   l_files.extend(glob(os.path.join('ASTK_CLIENT', 'lib', 'ASTK', 'astkrc', '*')))
   l_files.extend(glob(os.path.join('ASTK_SERV', 'conf', '*')))
   
   run.DBG(l_files)
   extsav = '.astk_update.%s' % run['num_job']
   for ff in l_files:
      if not os.path.isfile(ff):
         run.DBG('not found', ff)
      else:
         run.VerbStart(_('configure %s...') % ff, verbose=True)
         iret = change_text_file(ff, d_trans, delimiter='?', keep=run['debug'], ext=extsav)
         run.VerbEnd(iret, verbose=True)
   
   # --- permissions
   run.DBG(l_bin)
   extsav = '.astk_update.chmod.%s' % run['num_job']
   for ff in l_bin:
      if not os.path.isfile(ff):
         run.DBG('not found', ff)
      else:
         run.VerbStart(_('change permission of %s...') % ff, verbose=True)
         iret = 0
         try:
            os.chmod(ff, 0755)
         except OSError:
            iret = 4
         run.VerbEnd(iret, verbose=True)
   run.timer.Stop('Configuration of astk files')

   # 3. ----- installation
   # --- backup previous configuration
   run.timer.Start('Installation')
   run.Mess(_('Installation'), 'TITLE')
   home_astk = d_trans['HOME_ASTK']
   copy_and_backup(run, home_astk, repcnt,
                   os.path.join('ASTK_CLIENT', 'lib', 'ASTK', 'astkrc'), ext='.new')
   run.Mess(_("""Your current configuration : ASTK_CLIENT/lib/ASTK/astkrc
            new 'astkrc' saved as : ASTK_CLIENT/lib/ASTK/astkrc.new
       See release notes.
"""))
   
   copy_and_backup(run, home_astk, repcnt, os.path.join('ASTK_SERV', 'conf'), ext='.new')
   run.Mess(_("""Your current configuration : ASTK_SERV/conf
            new 'astkrc' saved as : ASTK_SERV/conf.new
       See release notes.
"""))

   # --- real installation
   copy_and_backup(run, repcnt, home_astk, 'ASTK_CLIENT', ext='.old')
   copy_and_backup(run, repcnt, home_astk, 'ASTK_SERV', ext='.old')
   run.timer.Stop('Installation')

#-------------------------------------------------------------------------------
def copy_and_backup(run, src, dest, content, ext=None):
   """Copy content from `src` to `dest`.
   """
   dsrc = os.path.join(src, content)
   ddes = os.path.join(dest, content)
   if os.path.isdir(dsrc):
      if ext:
         dsav = ddes + ext
         if os.path.exists(dsav):
            run.Delete(dsav)
         os.rename(ddes, dsav)
      run.Copy(ddes, dsrc)
   else:
      run.Mess(_("not found : '%s'") % dsrc)

