#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import with_statement

__license__   = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'

import os, StringIO, urllib2, urlparse, base64, hashlib, httplib, socket
from ConfigParser import ConfigParser

from setup import Command, __appname__, __version__
from setup.install import Sdist

class Metadata(object):

    name = __appname__
    version = __version__
    author = 'Kovid Goyal'
    author_email = 'kovid@kovidgoyal.net'
    url = 'http://calibre-ebook.com'
    description = 'E-book management application.'
    long_description = open('README', 'rb').read()
    license = 'GPL'
    keywords = ['e-book', 'ebook', 'news', 'reading', 'catalog', 'books']
    platforms = ['Linux', 'Windows', 'OS X']
    classifiers    = [
        'Development Status :: 5 - Production/Stable',
        'Environment :: Console',
        'Environment :: X11 Applications :: Qt',
        'Intended Audience :: Developers',
        'Intended Audience :: End Users/Desktop',
        'License :: OSI Approved :: GNU General Public License (GPL)',
        'Natural Language :: English',
        'Operating System :: POSIX :: Linux',
        'Programming Language :: Python',
        'Topic :: Software Development :: Libraries :: Python Modules',
        'Topic :: System :: Hardware :: Hardware Drivers'
    ]


class PyPIRC(object):

    DEFAULT_REPOSITORY = 'http://pypi.python.org/pypi'
    DEFAULT_REALM = 'pypi'
    RC = os.path.expanduser('~/.pypirc')


    def read_pypirc(self):
        repository = self.DEFAULT_REPOSITORY
        realm = self.DEFAULT_REALM

        config = ConfigParser()
        config.read(self.RC)
        sections = config.sections()
        if 'distutils' in sections:
            # let's get the list of servers
            index_servers = config.get('distutils', 'index-servers')
            _servers = [server.strip() for server in
                        index_servers.split('\n')
                        if server.strip() != '']
            if _servers == []:
                # nothing set, let's try to get the default pypi
                if 'pypi' in sections:
                    _servers = ['pypi']
                else:
                    # the file is not properly defined, returning
                    # an empty dict
                    return {}
            for server in _servers:
                current = {'server': server}
                current['username'] = config.get(server, 'username')
                current['password'] = config.get(server, 'password')

                # optional params
                for key, default in (('repository',
                                        self.DEFAULT_REPOSITORY),
                                        ('realm', self.DEFAULT_REALM)):
                    if config.has_option(server, key):
                        current[key] = config.get(server, key)
                    else:
                        current[key] = default
                if (current['server'] == repository or
                    current['repository'] == repository):
                    return current
        elif 'server-login' in sections:
            # old format
            server = 'server-login'
            if config.has_option(server, 'repository'):
                repository = config.get(server, 'repository')
            else:
                repository = self.DEFAULT_REPOSITORY
            return {'username': config.get(server, 'username'),
                    'password': config.get(server, 'password'),
                    'repository': repository,
                    'server': server,
                    'realm': self.DEFAULT_REALM}

        return {}


class PyPIRegister(Command):

    description = 'Register distribution with PyPI'

    def add_options(self, parser):
        parser.add_option('--show-response', default=False, action='store_true',
            help='Show server responses')

    def run(self, opts):
        self.show_response = opts.show_response
        config = PyPIRC().read_pypirc()
        self.repository = config['repository']
        self.realm = config['realm']
        #self.verify_metadata()
        self.send_metadata(config['username'], config['password'])

    def send_metadata(self, username, password):
        auth = urllib2.HTTPPasswordMgr()
        host = urlparse.urlparse(self.repository)[1]
        auth.add_password(self.realm, host, username, password)
        # send the info to the server and report the result
        code, result = self.post_to_server(self.build_post_data('submit'),
            auth)
        self.info('Server response (%s): %s' % (code, result))

    def verify_metadata(self):
        ''' Send the metadata to the package index server to be checked.
        '''
        # send the info to the server and report the result
        (code, result) = self.post_to_server(self.build_post_data('verify'))
        print 'Server response (%s): %s'%(code, result)

    def build_post_data(self, action):
        # figure the data to send - the metadata plus some additional
        # information used by the package server
        meta = Metadata
        data = {
            ':action': action,
            'metadata_version' : '1.0',
            'name': Metadata.name,
            'version': Metadata.version,
            'summary': Metadata.description,
            'home_page': Metadata.url,
            'author': Metadata.author,
            'author_email': Metadata.author_email,
            'license': Metadata.license,
            'description': Metadata.long_description,
            'keywords': meta.keywords,
            'platform': meta.platforms,
            'classifiers': Metadata.classifiers,
            'download_url': 'UNKNOWN',
            # PEP 314
            'provides': [],
            'requires': [],
            'obsoletes': [],
        }
        if data['provides'] or data['requires'] or data['obsoletes']:
            data['metadata_version'] = '1.1'
        return data

    def post_to_server(self, data, auth=None):
        ''' Post a query to the server, and return a string response.
        '''
        self.info('Registering %s to %s' % (data['name'],
                                                self.repository))
        # Build up the MIME payload for the urllib2 POST data
        boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
        sep_boundary = '\n--' + boundary
        end_boundary = sep_boundary + '--'
        body = StringIO.StringIO()
        for key, value in data.items():
            # handle multiple entries for the same name
            if type(value) not in (type([]), type( () )):
                value = [value]
            for value in value:
                value = unicode(value).encode("utf-8")
                body.write(sep_boundary)
                body.write('\nContent-Disposition: form-data; name="%s"'%key)
                body.write("\n\n")
                body.write(value)
                if value and value[-1] == '\r':
                    body.write('\n')  # write an extra newline (lurve Macs)
        body.write(end_boundary)
        body.write("\n")
        body = body.getvalue()

        # build the Request
        headers = {
            'Content-type': 'multipart/form-data; boundary=%s; charset=utf-8'%boundary,
            'Content-length': str(len(body))
        }
        req = urllib2.Request(self.repository, body, headers)

        # handle HTTP and include the Basic Auth handler
        opener = urllib2.build_opener(
            urllib2.HTTPBasicAuthHandler(password_mgr=auth)
        )
        data = ''
        try:
            result = opener.open(req)
        except urllib2.HTTPError, e:
            if self.show_response:
                data = e.fp.read()
            result = e.code, e.msg
        except urllib2.URLError, e:
            result = 500, str(e)
        else:
            if self.show_response:
                data = result.read()
            result = 200, 'OK'
        if self.show_response:
            print '-'*75, data, '-'*75
        return result

class PyPIUpload(PyPIRegister):

    description = 'Upload source distribution to PyPI'

    sub_commands = ['sdist', 'pypi_register']

    def add_options(self, parser):
        pass

    def run(self, opts):
        self.show_response = opts.show_response
        config = PyPIRC().read_pypirc()
        self.repository = config['repository']
        self.realm = config['realm']
        self.username = config['username']
        self.password = config['password']
        self.upload_file('sdist', '', Sdist.DEST)


    def upload_file(self, command, pyversion, filename):
        # Sign if requested
        #if self.sign:
        #    gpg_args = ["gpg", "--detach-sign", "-a", filename]
        #    if self.identity:
        #        gpg_args[2:2] = ["--local-user", self.identity]
        #    spawn(gpg_args,
        #          dry_run=self.dry_run)

        # Fill in the data - send all the meta-data in case we need to
        # register a new release
        content = open(filename,'rb').read()
        meta = Metadata
        md5 = hashlib.md5()
        md5.update(content)
        data = {
            # action
            ':action': 'file_upload',
            'protcol_version': '1',

            # identify release
            'name': meta.name,
            'version': meta.version,

            # file content
            'content': (os.path.basename(filename),content),
            'filetype': command,
            'pyversion': pyversion,
            'md5_digest': md5.hexdigest(),

            # additional meta-data
            'metadata_version' : '1.0',
            'summary': meta.description,
            'home_page': meta.url,
            'author': meta.author,
            'author_email': meta.author_email,
            'license': meta.license,
            'description': meta.long_description,
            'keywords': meta.keywords,
            'platform': meta.platforms,
            'classifiers': meta.classifiers,
            'download_url': 'UNKNOWN',
            # PEP 314
            'provides': [],
            'requires': [],
            'obsoletes': [],
            }
        comment = ''
        data['comment'] = comment

        #if self.sign:
        #    data['gpg_signature'] = (os.path.basename(filename) + ".asc",
        #                             open(filename+".asc").read())

        # set up the authentication
        auth = "Basic " + base64.encodestring(self.username + ":" + self.password).strip()

        # Build up the MIME payload for the POST data
        boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
        sep_boundary = '\n--' + boundary
        end_boundary = sep_boundary + '--'
        body = StringIO.StringIO()
        for key, value in data.items():
            # handle multiple entries for the same name
            if type(value) != type([]):
                value = [value]
            for value in value:
                if type(value) is tuple:
                    fn = ';filename="%s"' % value[0]
                    value = value[1]
                else:
                    fn = ""
                value = str(value)
                body.write(sep_boundary)
                body.write('\nContent-Disposition: form-data; name="%s"'%key)
                body.write(fn)
                body.write("\n\n")
                body.write(value)
                if value and value[-1] == '\r':
                    body.write('\n')  # write an extra newline (lurve Macs)
        body.write(end_boundary)
        body.write("\n")
        body = body.getvalue()

        self.info("Submitting %s to %s" % (filename, self.repository))

        # build the Request
        # We can't use urllib2 since we need to send the Basic
        # auth right with the first request
        schema, netloc, url, params, query, fragments = \
            urlparse.urlparse(self.repository)
        assert not params and not query and not fragments
        if schema == 'http':
            http = httplib.HTTPConnection(netloc)
        elif schema == 'https':
            http = httplib.HTTPSConnection(netloc)
        else:
            raise AssertionError("unsupported schema "+schema)

        data = ''
        try:
            http.connect()
            http.putrequest("POST", url)
            http.putheader('Content-type',
                           'multipart/form-data; boundary=%s'%boundary)
            http.putheader('Content-length', str(len(body)))
            http.putheader('Authorization', auth)
            http.endheaders()
            http.send(body)
        except socket.error, e:
            self.warn(str(e))
            raise SystemExit(1)

        r = http.getresponse()
        if r.status == 200:
            self.info('Server response (%s): %s' % (r.status, r.reason))
        else:
            self.info('Upload failed (%s): %s' % (r.status, r.reason))
            raise SystemExit(1)
        if self.show_response:
            print '-'*75, r.read(), '-'*75
