#!/usr/bin/python
# $Id: mirrortool,v 1.2 2002/05/07 10:12:03 mis Exp $
# (C) 2002 mis@pld.org.pl
# This is a free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2 as
# published by the Free Software Foundation (see file COPYING for details).

import os
import sys
import re
import gzip
import zlib
import string
import urllib2
import ConfigParser
import UserDict
import getopt
import imp
import tempfile
import types
import errno

VERSION = "0.1"
bug_email = "mis@pld.org.pl"

def print_err(msg):
    print "mirrortool: %s" % msg
    

class Mirror(UserDict.UserDict):
    mandatory_entries = [ 'organisation', 'continent', 'country', 'city',
                          'frequency', 'url', 'timezone', 'contact', 'src' ]
    
    other_entries = [ 'province' ]
    main_non_mandatory_entries = {
        'frequency':  1,
        'src' : 1
    }
        
    def __init__(self):
        UserDict.UserDict.__init__(self)

    def load_cfp(self, cfp, section):
        for ent in self.mandatory_entries:
            if not cfp.has_option(section, ent):
                if section == 'main' and self.main_non_mandatory_entries.has_key(ent):
                    cfp.set(section, ent, 'N/A')
                else:
                    raise NameError, "[%s]: missing '%s' entry" % (section, ent)
            self.data[ent] = cfp.get(section, ent)

        for ent in self.other_entries:
            if cfp.has_option(section, ent):
                self.data[ent] = cfp.get(section, ent)
            
        self.urls = re.split('\s+', self.url)
        del self.data['url']
        self.data['name'] = section
        if self.data.has_key('province'):
            self.data['city'] += " (%s)" % self.data['province']

    def __getattr__(self, name):
        if self.data.has_key(name):
            return self.data[name]
        return None



class MirrorDict(UserDict.UserDict):
    def __init__(self):
        UserDict.UserDict.__init__(self)

    def __getattr__(self, name):
        if self.data.has_key(name):
            return self.data[name]
        return None

    def sorted_keys(self):
        l = self.data.keys()
        l.sort()
        return l

    
class MirrorIndex:
    def __init__(self):
        self.loc_tree = MirrorDict()
        self.name_idx = {}
        pass
    
    def load(self, cfp):
        loc_tree = MirrorDict()
        name_idx = {}
        
        sections = cfp.sections()
        for s in sections:
            m = Mirror()
            m.load_cfp(cfp, s)
            
            name_idx[s] = m
            if not loc_tree.has_key(m.continent):
                loc_tree[m.continent] = MirrorDict()
            
            if not loc_tree[m.continent].has_key(m.country):
                loc_tree[m.continent][m.country] = []
                        
            loc_tree[m.continent][m.country].append(m)
            
        self.loc_tree = loc_tree
        self.name_idx = name_idx


    def dump(self):
        m = self.name_idx['main']
        print "\nMain site\n %s, %s, %s, %s\n   %s\n" % (m.organisation,
                                                         m.city, m.country, m.continent,
                                                         string.join(m.urls, '\n   '))
        
        for c in self.loc_tree.keys():
            print "%s" % c
            for cnt in self.loc_tree[c].sorted_keys():
                print " %s" % cnt
                l = self.loc_tree[c][cnt]
                for m in l:
                    if m.name != 'main':
                        print "  ",
                        print "%s, %s, %s\n     %s\n" % (m.name,
                                                      m.organisation, m.city,
                                                      string.join(m.urls,
                                                      '\n     '))


    def get_mirror(self, name):
        if self.name_idx.has_key(name):
            return self.name_idx[name]
        return None

    def continents(self):
        return self.loc_tree.sorted_keys()
    
    def countries(self, continent):
        return self.loc_tree[continent].sorted_keys()

    def mirrors(self, continent = None, country = None):
        if continent and country:
            return self.loc_tree[continent][country]
        
        elif continent:
            ml = []
            cl = self.countries(continent)
            for c in cl:
                cml = self.loc_tree[continent][c]
                for m in cml:
                    ml.append(m)
            return ml
        
        elif not continent and not country:
            ml = []
            cnl = self.continents() 
            for cn in cnl:
                cl = self.countries(cn)
                for c in cl:
                    cml = self.loc_tree[continent][c]
                    for m in cml:
                        ml.append(m)
            return ml
        
        else:
            raise "MirrorIndex.mirrors(): invalid arguments"
    
        
                
        
                    



class MirrorChooser:
    def __init__(self, mirrorl):
        self.mirrorl = mirrorl

    def choose(self, obj, **options):
        ml = self.choose_location()
        url_l = []
        url_h = {}
        i = 0
        for m in ml:
            for url in m.urls:
                s = "%s\t%s;  %s" % (url, m.organisation, m.city)
                url_l.append(s)
                url_h[s] = {'m' : m, 'url': url }
                i += 1
                

        label = self.select(url_l, "site", 0);
        m = url_h[label]['m']
        url = url_h[label]['url']
        site_name = "%s, %s, %s, %s" % (m.city, m.name, m.organisation, url)
        obj.mirrortool_configure(site_name, url, **options);
                
    def choose_location(self):
        organisation = self.mirrorl.get_mirror('main').organisation
        print """
Now we need to know where your favorite %s site is located.
First, pick a nearby continent and country, then, you
will be presented with a list of URLs of mirrors.
""" % organisation,

        continents = self.mirrorl.continents()
        continent = self.select(continents, "continent")
            
        if continent:
            countries = self.mirrorl.countries(continent)
            country = self.select(countries, "country")
            
            if country:
                return self.mirrorl.mirrors(continent, country)
        return None
                
    def select_i(self, values, label, lonely = 1):
        if len(values) < 1:
            return None
        
        if len(values) == 1 and lonely:
            return 0

        while 1:
            print "\nChoose a %s:" % label
            
            i = 1
            for val in values:
                print "%d) %s" % (i, val)
                i += 1
                
            print "Your choice [1]: ",
            s = string.strip(sys.stdin.readline())
            if len(s) == 0:
                s = '1';
                
            try:
                v = int(s)
            except:
                continue
            v -= 1;
            if v >= 0 and v < len(values):
                return v
        return None

    def select(self, values, label, lonely = 1):
        i = self.select_i(values, label, lonely)
        if i != None:
            return values[i]
        return None


def load_module(name):
    mod_name = "mod_%s" % name
    try:
        mod = imp.find_module(mod_name, ['', '/usr/share/lib/mirrortool',
                                         '/usr/lib/mirrortool' ])
    except ImportError, e:
        print_err("%s: %s" % (mod_name, e.args[0]))
        sys.exit(1)

    except Exception, e:
        print_err("%s: %s" % (mod_name, e.args[0]))
        sys.exit(1)
    
    try:
        mod = imp.load_module(mod_name, mod[0], mod[1], mod[2])
    except Exception, e:
        print_err("%s: %s" % (mod_name, e.args[0]))
        sys.exit(1)
        
    return mod

def list_modules():
    import dircache

    modules = {}
    for dir in ['.', '/usr/share/lib/mirrortool', '/usr/lib/mirrortool' ]:
        l = dircache.listdir(dir)
        for f in l:
            m = re.match('^mod_([\w-]+)\.pyc?', f)
            if m:
                modules[m.group(1)] = 1

    ml = modules.keys()
    ml.sort()
    for modname in ml:
        mod = load_module(modname)
        if mod and mod.__dict__.has_key('mirrortool_summary'):
            print "%s -- %s" % (modname, mod.mirrortool_summary())
            opts = mod.mirrortool_options()
            if len(opts):
                print "  Module options:\n    %s" % opts
            print ""
            
def get_conf_mirrors_path():
    fp = None
    fn = os.environ['HOME'] + '/.mirrortoolrc'
    try:
        fp = open(fn)
    except:
        fn = '/etc/mirrortool.conf'
        try:
            fp = open(fn)
        except IOError, e:
            print_err("%s: %s" % (e.filename, e.strerror))
            return None
        
    cfp = ConfigParser.ConfigParser()
    cfp.readfp(fp)
    if not cfp.has_option('mirrortool', 'url'):
        print_err("%s: %s" % (fp.name, "no url entry"))
        sys.exit(1)
        
    return cfp.get('mirrortool', 'url')


def read_mirrors_file(url):
    isgz = 0
    if re.match('^.+?\.gz$', url):
        isgz = 1

    fp = None
    if not re.match('^\w+://', url):
        if isgz:
            fp = gzip.GzipFile(url, mode = "rb")
        else:
            fp = open(url)
            
    else:
        print "Retrieving %s..." % url
        urlfp = urllib2.urlopen(url)
        tmpfp = tempfile.TemporaryFile()
        
        tmpfp.write(urlfp.read())
        urlfp.close()
        tmpfp.flush()
        tmpfp.seek(0)
        
        if isgz:
            fp = gzip.GzipFile(fileobj = tmpfp, mode = 'rb')
        else:
            fp = tmpfp

    cfp = ConfigParser.ConfigParser()
    cfp.readfp(fp)
    return cfp


### TODO improve options management
def usage():
    print "mirrortool %s" % VERSION
    print "This program may be freely redistributed under the terms of the GNU GPL v2\n";
    print """Usage: mirrortool --source URL OPTIONS
-s, --source=URL\t MIRRORS file URL
-l,--list\t\t display mirror list
-L,--modlist\t\t display available modules
-m,--mod=MODULE\t\t pass mirror list trough module MODULE
-P,--modopts=OPTIONS\t pass OPTIONS as module options (see -L for particular
                         module options)
--debug\t\t display debug messages                         
-h,--help\t\t display this help

Report bugs to <%s>.
""" % bug_email
    
def getopts():
    module = None
    opth = {}

    opts, args = getopt.getopt(sys.argv[1:], "s:hlLo:m:P:",
                               ["source=", "help", "list", "modlist",
                                "mod=", "modopts="])
    for o, a in opts:
        if o in ("-s", "--source"):
            opth['source'] = a
        
        if o in ("-h", "--help"):
            opth['help'] = 1

        if o in ("--debug",):
            opth['debug'] = 1

        elif o in ("-L", "--modlist"):
            opth['modlist'] = 1

        elif o in ("-P", "--modopts"):
            pl = re.split('\s+', a)
            if not opth.has_key('options'):
                opth['options'] = {}
                
            for param in pl:
                va = re.split('=', param)
                if len(va) == 2:
                    opth['options'][va[0]] = va[1]
                elif len(va) == 1:
                    opth['options'][va[0]] = 1
                else:
                    print "%s: invalid parameter"
                    sys.exit(1)
            
        elif o in ("-m", "--mod"):
            opth['module'] = a
            
        elif o in ("-l", "--list"):
            opth['list'] = 1
            
    if len(opth.keys()) == 0:
        opth['help'] = 1

    if opth.has_key('help'):
        usage()
        sys.exit(0)
        
    if opth.has_key('modlist'):
        list_modules()
        sys.exit(0)

    if not opth.has_key('list') and not opth.has_key('module'):
        usage()
        sys.exit(0)


    if not opth.has_key('options'):
        opth['options'] = {}

    return opth

#
# main
# 
try: 
    opth = getopts()
    
    if opth.has_key('source'):
        url = opth['source']
    else:
        url = get_conf_mirrors_path()

    if not url:
        sys.exit(1)
        
    cfp = read_mirrors_file(url)
    ml = MirrorIndex()
    ml.load(cfp);

    if opth.has_key('list'):
        ml.dump()

    if not opth.has_key('module'):
        sys.exit(0)

    mod = load_module(opth['module'])
    
    if mod.__dict__.has_key('mirrortool_out'):
        mod.mirrortool_out(ml, **opth['options'])
        
    elif mod.__dict__.has_key('mirrortool_configure'):
        opts = opth['options']
        print "options %s " % `opts`
        chooser = MirrorChooser(ml) 
        chooser.choose(mod, **opts)
        
    else:
        print "mirrortool: %s: invalid module" % mod_name
        sys.exit(1)
        
except KeyboardInterrupt:
    pass

except SystemExit:
    pass

except  urllib2.HTTPError, e:
    print_err("%s: retr failed" % url)
    
except  urllib2.URLError, e:
    print_err(e.reason.args[0])

except IOError, e:
    if opth.has_key('debug'):
        print "%s, %s" % (e, `e.__dict__`)

    fn = e.filename
    if not fn:
        fn = ""
    else:
        fn = "%s: " %fn
    err = e.strerror
    if err == None and e.__dict__.has_key('args'):
        err = e.args[0]
        
    print_err("%s%s" % (fn, err))

except KeyError:
    pass
    
except Exception, e:
    #print "ex %s" %  `e`
    printed = 0
    for k in e.__dict__.keys():
        if type(e.__dict__[k]) == types.StringType:
            print "mirrortool: %s" % e.__dict__[k]
            printed = 1
    if not printed:
        raise

