mirror of
				https://github.com/kovidgoyal/calibre.git
				synced 2025-11-04 03:27:00 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			358 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			358 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#!/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.kovidgoyal.net'
 | 
						|
    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
 |