#!/usr/bin/env python

# Copyright (c) 2000-2001 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# 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 the Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
__revision__ = '$Id: npm.py,v 1.15 2002/02/15 15:03:05 nico Exp $'

"""NARVAL Package Manager

This module provides functions to install and remove Narval packages.
It can be used as a command (see usage below) or from Horn.
"""

import os, os.path
import shutil, tempfile, zipfile
import string
from os.path import split,join,exists

USAGE = """
Usage: npm.py [options] command arguments

Commands:
   install PKGFILE    install package (as in 'install narvalpkg.tgz')
   uninstall MANIFEST uninstall package (as in 'uninstall narvalpkg.manifest')
   upgrade PKGFILE    upgrade package (as in 'upgrade narvalpkg.tgz')
   list               list installed packages
Options:
   --home DIR    use DIR as your NARVAL_HOME (default is '~/.narval')
   --help        print this message
"""
SUBDIR_LIST =  ['data','dtd','extensions','graphics','tests',
                'modules','recipes','transforms','doc']

def recursive_copy(file, dest) :
    list = []
    if os.path.isdir(file) :
        # file is a dir, recurse
        if not os.path.exists(dest) :
            os.makedirs(dest)
        for subfile in filter(lambda x: x!='CVS',os.listdir(file)) :
            list = list + recursive_copy(os.path.join(file,subfile),
                                         os.path.join(dest,subfile))
        # append new directory to the files list for a correct uninstall
        if os.path.basename(file) not in SUBDIR_LIST:
            list.append(dest)
    else :
        # file is a file, copy
        try:
            shutil.copy(file,dest)
            list.append(dest)
        except (IOError, os.error), why :
            print "Error: Can't copy %s to %s: %s" % (`file`, `dest`, str(why))
    return list

def unzip_file(filename,dest_dir):
    print filename
    assert zipfile.is_zipfile(filename)
    zip = zipfile.ZipFile(filename,'r')
    files = zip.namelist()
    for name in files:
        # build missing directories
        dest = join(dest_dir,name)
        head,tail = split(dest)
        dirs_to_make = []
        while not exists(head) and head:
            dirs_to_make.append(head)
            head,tail = split(head)
        dirs_to_make.reverse()
        for d in dirs_to_make:
            os.mkdir(d)

        # extract data to file
        if not os.path.isdir(dest):
            output = open(dest,'w')
            output.write(zip.read(name))
            output.close()
    zip.close()
    return files

# install ######################################################################

def install(pkg,options) :
    # useful variables
    PKG_NAME = os.path.splitext(os.path.basename(pkg))[0]
    PKG_TMPDIR = tempfile.mktemp()
    pkg = os.path.abspath(pkg)
    os.chdir(options['HOME'] )
    # copy files
    unzip_file(pkg,PKG_TMPDIR)
    file_list = []
    PKG_PATH = join(PKG_TMPDIR, PKG_NAME)
    for subdir in os.listdir(PKG_PATH) :
        if os.path.isdir(join(PKG_PATH,subdir)) and subdir in SUBDIR_LIST :
            print '> Installing '+subdir+'...'
            srcdir = os.path.join(PKG_PATH,subdir)
            destdir = subdir 
            if subdir == 'doc' :
                destdir = os.path.join(destdir,PKG_NAME)
            if not os.path.exists(destdir) :
                os.makedirs(destdir)
            file_list = file_list + recursive_copy(srcdir,destdir)

    # remove tmp tree
    shutil.rmtree(PKG_TMPDIR)
    # create manifest file
    MANIFEST_FILE = os.path.join(PKG_NAME+'.manifest')
    print "> Creating '%s' file for uninstall support..." % MANIFEST_FILE
    manifest = open(MANIFEST_FILE,'w+')
    for file in file_list :
        manifest.write(file+'\n')
    manifest.close()

# uninstall ####################################################################

def uninstall(pkg_manifest,options=None) :
    try:
        manifest = open(pkg_manifest)
        for file in manifest.readlines() :
            file = os.path.join(options['HOME'], file[:-1])
            try:
                if os.path.isdir(file):
                    os.rmdir(file)
                else:
                    os.remove(file)
                print 'Deleting %s...' % file
            except OSError:
                print "Error: couldn't delete %s !" % file
            if file[len(file)-3:] == '.py' :
                try:
                    os.remove(file+'o') # remove .pyo
                    print 'Deleting %so...' % file
                except OSError, e:
                    if e.errno != 2 :
                        print "Error: couldn't delete %sc !" % file
                try:
                    os.remove(file+'c') # remove .pyc
                    print 'Deleting %sc...' % file
                except OSError, e:
                    if e.errno != 2 :
                        print "Error: couldn't delete %sc !" % file
        manifest.close()
        print 'Deleting manifest %s...' % pkg_manifest
        os.remove(pkg_manifest)
    except IOError :
        print 'Error: no such manifest file as',pkg_manifest

# upgrade ######################################################################

def upgrade(pkg,options=None) :
    pkg_name = string.split(pkg,'-')[0]
    installed_packages = list_packages(options)
    for ipkg in installed_packages :
        if string.find(ipkg,pkg_name+'-') == 0 :
            manifest = os.path.join(options['HOME'],ipkg+'.manifest')
            uninstall(manifest,options)
            break
    install(pkg,options)

# list #########################################################################

def list_packages(options) :
    return map(lambda x: x[:-9], filter(lambda x: x[-9:] == '.manifest',
                                        os.listdir(options['HOME'])))

# main #########################################################################

def Run(SysArgs):
    import getopt, sys
    
    HOME = os.path.expanduser('~/.narval/')

    print 'NARVAL Package Manager'

    # handle command line arguments
    short_list = ''
    long_list = ['home=','help' ]
    optlist, args = [], []
    try:
        optlist, args = getopt.getopt(SysArgs[1:], short_list, long_list)
    except getopt.error,e :
        print 'Error:',e
        print USAGE
        sys.exit(1)
    for opt, val in optlist :
        if opt == '--home' :
            if not os.path.isdir(val):
                print 'Error, no such directory:', val
                sys.exit(1)
            print 'Using '+val+' for NARVAL homedir.'
            HOME=val
        if opt == '--help' :
            print USAGE
            sys.exit(0)
    # do something
    try:
        #command, target = args
        options = {'HOME':HOME}
        if args[0] == 'install' :
            install(args[1],options)
        elif args[0] == 'uninstall' :
            uninstall(args[1],options)
        elif args[0] == 'upgrade' :
            upgrade(args[1],options)
        elif args[0] == 'list' :
            print "Installed packages"
            for p in list_packages(options) :
                print '\t',p
        else:
            print USAGE
            sys.exit(1)
    except IndexError,e :
        print USAGE
        sys.exit(1)


if __name__ == '__main__' :
    import sys
    Run(sys.argv)
