#!/usr/bin/python2.4
#
# This file is part of the Falcon repository manager
# Copyright (C) 2005-2008 Dennis Kaarsemaker
# See the file named COPYING in the root of the source tree for license details
#
# falcon - main repository maintenance application

import gettext
from gettext import gettext as _
gettext.bindtextdomain('falcon','/usr/share/locale')
gettext.textdomain('falcon')

import falcon
import sys, os, shutil, optparse, time

(output, warning, error, debug) = falcon.util.outfunc

usage = _("""%prog [options] action [args]
  Actions:
    scan
    clean
    export
    install <filename.dsc>
    sync [mirror]
    iso
    configure
    build <filename.dsc>
    shell
    dbshell
    """)

parser = optparse.OptionParser(usage=usage)
parser.error = lambda string: error(string)
parser.add_option('-r','--root', dest="rootdir",
                  help=_("Specify the rootdir"), metavar=_("DIR"))
parser.add_option('-v','--verbose', action="store_true", dest="verbose", default=False,
                  help=_("Give verbose output"))
parser.add_option('-t','--timimgs', action="store_true", dest="timings", default=False,
                  help=_("Measure duration of various parts of the program"))
parser.add_option('-y','--yes', action="store_true", dest="force_yes", default=False,
                  help=_("Answer yes to all questions"))
parser.add_option('-n','--no', action="store_true", dest="force_no", default=False,
                  help=_("Answer no to all questions"))
parser.add_option('-P', '--pocket', dest="pocket", default=None,
                  help=_("Scan only this pocket/install into this pocket"))
parser.add_option('-C', '--component', dest="component", default=None,
                  help=_("Update only this component/install into this component"))
parser.add_option('-s', '--size', dest="isosize", default="650",
                  help=_("Size of the iso image in megabytes, default %default"))
parser.add_option('-c', '--complete-only', action="store_true", dest="complete_only", default=False,
                  help=_("Only install completely built packages into the archive"))
parser.add_option('-e', '--editor', dest='editor', default=None,
                  help=_("Specify a text editor for editing the configuration"))
parser.add_option('-p','--no-sync', action="store_true", dest="no_sync", default=False,
                  help=_("Do not sync to mirrors, but pretend to"))
parser.add_option('-a', '--architecture', dest="iso_architecture", default=None,
                  help=_("Build an ISO for this architecture"))
parser.add_option('-V', '--version', action="store_true", dest="dump_version", default=False,
                  help=_("Show version and exit"))
parser.add_option('-i', '--install', dest="install_built", default=False, action="store_true",
                  help=_("Install fully built packages into the archive after building"))

oldwd = os.getcwd()
options, args = parser.parse_args()
options.application = 'falcon'

if options.dump_version:
    print falcon.conf.falcon_version
    sys.exit(0)

print _("Falcon repository manager %s (C)2005-2008 %s") % (falcon.conf.falcon_version, falcon.conf.falcon_author)
if os.getuid() == 0:
    error(_("Falcon should not be run as root"))

if not len(args):
    parser.print_help()
    sys.exit(1)
action = args[0]; args=args[1:]
if action not in ('scan','iso','install','sync','configure','panic','export','shell','clean','build','dbshell'):
    parser.print_help()
    sys.exit(1)
if args and action not in ('sync', 'install', 'build'):
    parser.print_help()
    sys.exit(1)

falcon.conf.set_options(options, action!='iso', action)
if not falcon.conf.configured and action != 'iso':
    falcon.conf.configure()

# Now what do we do?
res = falcon.plugin.run_plugins('pre_action', action, args)
if not res:
    error(_("Unable to proceed"))

# ISO Building
if action == 'iso':
    output(_("Scanning %s for .deb files") % falcon.conf.rootdir)
    (required, available) = falcon.iso.check_size(falcon.conf.rootdir)
    if required > available:
        error(_("Required disk space during ISO creation: %dMB, available: %dMB") % (required, available))
    if required > falcon.conf.isosize:
        warning(_("ISO image will be larger than 650 MB (%dMB to be precise)"))
        if not falcon.questions.yesno(_("Are you sure you want to continue and create multiple .iso files?"),False):
            error(_("Aborted by user"))
        else:
            error(_("Sorry, can't creat multiple ISO's yet"))
    
    # Initialize config to ISO specific values
    output(_("Creating iso directory tree for %s") % falcon.conf.rootdir)
    rootdir = falcon.iso.create_tree(falcon.conf.rootdir)
    
    # Reconfigure now that we have a rootdir
    options.rootdir = rootdir
    falcon.conf.set_options(options)

# Install single sources
if action == 'install':
    if len(args) != 1 or not falcon.conf.component or not falcon.conf.pocket or not args[0].endswith('.dsc')\
        or not os.path.exists(os.path.join(oldwd, args[0])):
        parser.print_help()
        sys.exit(1)
    try:
        c = falcon.pocket.Component.objects.get(name=falcon.conf.component, pocket__name=falcon.conf.pocket)
    except falcon.pocket.Component.DoesNotExist:
        error(_("Can't find component %s/%s") % (falcon.conf.pocket, falcon.conf.component))

    srcpath = os.path.realpath(os.path.join(oldwd, os.path.dirname(args[0])))
    dsc = os.path.join(srcpath, os.path.basename(args[0]))

    control, controlfields, files = falcon.package.parse_dsc(dsc)
    to_move = []
    name = controlfields['Source']
    for f in files:
        if not os.path.exists(os.path.join(srcpath,f.name)):
            error(_("Can't install incomplete sourcepackage %s") % name)
        to_move.append(f.name)

    binaries = [x.strip() for x in controlfields['Binary'].split(",")]
    version = controlfields['Version']
    if ':' in version:
        version = version[version.find(':')+1:]
    for b in binaries:
        f = '%s_%s_all.deb' % (b, version)
        if os.path.exists(os.path.join(srcpath,f)):
            to_move.append(f)
            continue
        f = '%s_%s_all.udeb' % (b, version)
        if os.path.exists(os.path.join(srcpath,f)):
            to_move.append(f)
            continue
        for a in falcon.conf.architectures:
            f = '%s-dbgsym_%s_%s.ddeb' % (b, version, a)
            if os.path.exists(os.path.join(srcpath,f)):
                to_move.append(f)
            f = '%s_%s_%s.deb' % (b, version, a)
            if os.path.exists(os.path.join(srcpath,f)):
                to_move.append(f)
                continue
            f = '%s_%s_%s.udeb' % (b, version, a)
            if os.path.exists(os.path.join(srcpath,f)):
                to_move.append(f)
                continue
            # Tried'em all!
            if falcon.conf.complete_only:
                error(_("Can't install package %s because it was not yet completely built") % name)

    # Find a changelog
    cl = falcon.package.find_changelog(srcpath, to_move)
    if cl:
        to_move.append(os.path.basename(cl))

    # Now install the package
    if srcpath != os.path.realpath(c.poolpath):
        for f in to_move:
            if os.path.exists(os.path.join(c.poolpath, f)):
                if f.endswith('orig.tar.gz'):
                    to_move.remove(f)
                else:
                    error(_("File %s already exists in component %s") % (f, c.poolpath))
        for f in to_move:
            shutil.move(os.path.join(srcpath, f), os.path.join(c.poolpath, f))
        
    s = falcon.package.SourcePackage.create_from_dscfile(c, os.path.basename(args[0]))
    output(_("Installing package %s from %s") % (s.packagename, os.path.basename(args[0])))
    c.install(s)
    c.save()

# Build single sources
if action == 'build':
    if len(args) != 1 or not args[0].endswith('.dsc') or not os.path.exists(os.path.join(oldwd,args[0])) or not falcon.conf.pocket:
        parser.print_help()
        sys.exit(1)
    dsc = args[0]
    srcpath = os.path.realpath(os.path.join(oldwd, os.path.dirname(dsc)))
    falcon.build.build(os.path.join(srcpath, os.path.basename(dsc)))


if action == 'export':
    falcon.conf.lastexport = time.time()

# Main update functionality
if action in ("scan", "iso", "export", "clean"):
    falcon.util.timer_start(_("Main update loop"))

    if not os.path.exists('dists'):
        os.mkdir('dists')

    # Remove obsolete pockets, only if scanning everything
    pockets = sorted(os.listdir('pool'))
    if not falcon.conf.pocket:
       for oldpock in os.listdir('dists'):
           if os.path.isdir(os.path.join('dists',oldpock)):
               if oldpock not in pockets:
                   output(_("Removing obsolete pocket %s") % oldpock)
                   shutil.rmtree(os.path.join('dists',oldpock))

    for pocket in falcon.pockets:
        if falcon.conf.pocket and pocket.name != falcon.conf.pocket:
            continue
        falcon.util.timer_start(_("Scanning pocket %s") % pocket.name)

        # Remove components that no longer exist, but only if scanning the whole pocket
        if not falcon.conf.component:
            if os.path.exists(pocket.distpath):
                for oldsect in os.listdir(pocket.distpath):
                    if os.path.isdir(os.path.join(pocket.distpath,oldsect)):
                        if oldsect not in [x.name for x in pocket.components]:
                            output(_("Removing obsolete component %s") % oldsect)
                            shutil.rmtree(os.path.join(pocket.distpath,oldsect))

        # Update components
        for component in pocket.components:
            if falcon.conf.component and not (component == falcon.conf.component):
                continue
            if action in ("scan","iso","clean") and type(component) == falcon.pocket.Component:
                output(_("Scanning component '%s'") % str(component))
                component.scan(clean = action=='clean')
            if action in ("export","iso"):
                output(_("Exporting component '%s'") % str(component))
                component.export()

        # Generate release file and contentlistings
        if action in ("export","iso"):
            output(_("Exporting pocket '%s'") % pocket)
            pocket.export()

        falcon.util.timer_stop()
    falcon.util.timer_stop()
    
if action == 'export' and falcon.conf.webbase:
    output(_("Writing main index"))
    # Do file cleanup
    whitelist = ['index.html', 'dists', 'pool', 'ai', '.falcon', '.htaccess', 'robots.txt', 'favicon.ico']
    if falcon.conf.gpgkey:
        whitelist.append(falcon.conf.origin + '.gpg')
        try:
            if not os.path.exists('%s.gpg' % falcon.conf.origin):
                falcon.util.run(['gpg', '--export', '--armor', falcon.conf.gpgkey], outfile='%s.gpg' % falcon.conf.origin)
        except:
            warning(_("Couldn't export GPG key"))
    from django.conf import settings
    try:
        template = settings.TEMPLATE_DIRS[0]
        templatefiles = [x for x in os.listdir(template) if not x.endswith('.html')]
        whitelist += templatefiles
        for file in templatefiles:
            if not os.path.exists(file) or falcon.util.newer(os.path.join(template,file),file):
                shutil.copy(os.path.join(template, file), file)
    except:
        pass

    for file in os.listdir('.'):
        if file not in whitelist:
            if os.path.isdir(file):
                if falcon.questions.yesno(_("Delete non-repository directory %s?") % file, False):
                    shutil.rmtree(file)
            else:
                if falcon.questions.yesno(_("Delete non-repository file %s?") % file, False):
                    os.unlink(file)

    from django.template import Context
    template = falcon.util.get_template('base.html')
    mirrors = falcon.mirror.Mirror.objects.all().order_by('name')
    do_appinstall = False
    try:
        do_appinstall = falcon.plugins.app_install.AppInstallDataPlugin.conf.enabled
    except:
        pass
    context = Context({'pockets': falcon.pockets, 'conf': falcon.conf, 'mirrors': mirrors, 'do_appinstall': do_appinstall})
    falcon.util.writefile('index.html',template.render(context))

# ISO generation, phase 2
if action == 'iso':
    output(_("Creating ISO file"))
    iso = falcon.iso.create_iso(falcon.conf.rootdir)
    output(_("Cleaning temporary files"))
    falcon.iso.cleanup(falcon.conf.rootdir)
    output(_("ISO file created in %s") % iso)

# Rsync handling
if action == 'sync':
    if falcon.conf.lastexport < falcon.conf.lastinstall:
        warning(_("You installed packages after the last export run. This will not be visible on the mirrors"))
    if args:
        mirrors = []
        for a in args:
            try:
                mirrors.append(falcon.mirror.Mirror.objects.get(name=a))
            except falcon.mirror.Mirror.DoesNotExist:
                error(_("Mirror %s does not exist") % a)
    else:
        mirrors = falcon.mirror.Mirror.objects.all().order_by('name')
        paths = [os.path.basename(x.rootdir) for x in mirrors]
        old_mirrors = [x for x in os.listdir('.falcon') if x.startswith('base-') and x not in paths]
        for m in old_mirrors:
            shutil.rmtree(os.path.join('.falcon', m))
    for m in mirrors:
        m.update()
        falcon.plugin.run_plugins('pre_rsync', m)
        if m.rsync:
            output(_("Synchronizing with %s") % m.name)
            m.sync()

# Configuration
if action == "configure":
    falcon.conf.configure()

# Useful debugging tools
if action == 'shell':
    import code, readline, rlcompleter
    readline.parse_and_bind("tab: complete")
    ic = code.InteractiveConsole()
    ic.push('from falcon.shell import *')
    if os.path.exists(os.path.join(os.getenv('HOME'), '.falcon', '.history')):
        readline.read_history_file(os.path.join(os.getenv('HOME'), '.falcon', '.history'))
    ic.interact()
    if os.path.exists(os.path.join(os.getenv('HOME'), '.falcon')):
        readline.write_history_file(os.path.join(os.getenv('HOME'), '.falcon', '.history'))

if action == 'dbshell':
    from django.db import runshell
    runshell()

if action == 'panic':
    import datetime
    print """
                  nnnmmm
   \||\       ;;;;%%%@@@@@@       \ //,
    V|/     %;;%%%%%@@@@@@@@@@  ===Y//
    68=== ;;;;%%%%%%@@@@@@@@@@@@    @Y
    ;Y   ;;%;%%%%%%@@@@@@@@@@@@@@    Y
    ;Y  ;;;+;%%%%%%@@@@@@@@@@@@@@@    Y
    ;Y__;;;+;%%%%%%@@@@@@@@@@@@@@i;;__Y
   iiY"";;   "uu%@@@@@@@@@@uu"   @"";;;>
          Y     "UUUUUUUUU"     @@
          `;       ___ _       @
            `;.  ,====\\=.  .;'
              ``""\""`==\\=='
                     `;=====   Don't
                        ===    panic!
"""
    if datetime.date.today().timetuple()[1:3] == (5,25):
        print _("Today is Towel Day. Do you know where your towel is?")

falcon.plugin.run_plugins('post_action', action, args)
# Cleanup
falcon.unlock()
if falcon.screen:
    falcon.screen.finish()
