calibre now depends on dnspython for email delivery. Also remove python 2.5 builds as it is no longer officially supported, though calibre will probably continue to run on it for a little while longer.

This commit is contained in:
Kovid Goyal 2009-03-25 18:47:23 -07:00
parent 52d968838c
commit 9ca023e9a7
11 changed files with 480 additions and 460 deletions

View File

@ -9,7 +9,7 @@ Create linux binary.
''' '''
def freeze(): def freeze():
import glob, sys, subprocess, tarfile, os, re, textwrap, shutil, cStringIO, bz2, codecs import glob, sys, tarfile, os, textwrap, shutil
from contextlib import closing from contextlib import closing
from cx_Freeze import Executable, setup from cx_Freeze import Executable, setup
from calibre.constants import __version__, __appname__ from calibre.constants import __version__, __appname__
@ -18,13 +18,13 @@ def freeze():
from calibre.web.feeds.recipes import recipe_modules from calibre.web.feeds.recipes import recipe_modules
from calibre.ebooks.lrf.fonts import FONT_MAP from calibre.ebooks.lrf.fonts import FONT_MAP
import calibre import calibre
QTDIR = '/usr/lib/qt4' QTDIR = '/usr/lib/qt4'
QTDLLS = ('QtCore', 'QtGui', 'QtNetwork', 'QtSvg', 'QtXml', 'QtWebKit') QTDLLS = ('QtCore', 'QtGui', 'QtNetwork', 'QtSvg', 'QtXml', 'QtWebKit')
binary_excludes = ['libGLcore*', 'libGL*', 'libnvidia*'] binary_excludes = ['libGLcore*', 'libGL*', 'libnvidia*']
binary_includes = [ binary_includes = [
'/usr/bin/pdftohtml', '/usr/bin/pdftohtml',
'/usr/lib/libunrar.so', '/usr/lib/libunrar.so',
@ -48,58 +48,60 @@ def freeze():
'/usr/lib/libMagickWand.so', '/usr/lib/libMagickWand.so',
'/usr/lib/libMagickCore.so', '/usr/lib/libMagickCore.so',
] ]
binary_includes += [os.path.join(QTDIR, 'lib%s.so.4'%x) for x in QTDLLS] binary_includes += [os.path.join(QTDIR, 'lib%s.so.4'%x) for x in QTDLLS]
d = os.path.dirname d = os.path.dirname
CALIBRESRC = d(d(d(os.path.abspath(calibre.__file__)))) CALIBRESRC = d(d(d(os.path.abspath(calibre.__file__))))
CALIBREPLUGINS = os.path.join(CALIBRESRC, 'src', 'calibre', 'plugins') CALIBREPLUGINS = os.path.join(CALIBRESRC, 'src', 'calibre', 'plugins')
FREEZE_DIR = os.path.join(CALIBRESRC, 'build', 'cx_freeze') FREEZE_DIR = os.path.join(CALIBRESRC, 'build', 'cx_freeze')
DIST_DIR = os.path.join(CALIBRESRC, 'dist') DIST_DIR = os.path.join(CALIBRESRC, 'dist')
os.chdir(CALIBRESRC) os.chdir(CALIBRESRC)
print 'Freezing calibre located at', CALIBRESRC print 'Freezing calibre located at', CALIBRESRC
sys.path.insert(0, os.path.join(CALIBRESRC, 'src')) sys.path.insert(0, os.path.join(CALIBRESRC, 'src'))
entry_points = entry_points['console_scripts'] + entry_points['gui_scripts'] entry_points = entry_points['console_scripts'] + entry_points['gui_scripts']
entry_points = ['calibre_postinstall=calibre.linux:binary_install', entry_points = ['calibre_postinstall=calibre.linux:binary_install',
'calibre-parallel=calibre.parallel:main'] + entry_points 'calibre-parallel=calibre.parallel:main'] + entry_points
executables = {} executables = {}
for ep in entry_points: for ep in entry_points:
executables[ep.split('=')[0].strip()] = (ep.split('=')[1].split(':')[0].strip(), executables[ep.split('=')[0].strip()] = (ep.split('=')[1].split(':')[0].strip(),
ep.split(':')[-1].strip()) ep.split(':')[-1].strip())
if os.path.exists(FREEZE_DIR): if os.path.exists(FREEZE_DIR):
shutil.rmtree(FREEZE_DIR) shutil.rmtree(FREEZE_DIR)
os.makedirs(FREEZE_DIR) os.makedirs(FREEZE_DIR)
if not os.path.exists(DIST_DIR): if not os.path.exists(DIST_DIR):
os.makedirs(DIST_DIR) os.makedirs(DIST_DIR)
includes = [x[0] for x in executables.values()] includes = [x[0] for x in executables.values()]
includes += ['calibre.ebooks.lrf.fonts.prs500.'+x for x in FONT_MAP.values()] includes += ['calibre.ebooks.lrf.fonts.prs500.'+x for x in FONT_MAP.values()]
includes += ['email.iterators', 'email.generator']
excludes = ['matplotlib', "Tkconstants", "Tkinter", "tcl", "_imagingtk",
"ImageTk", "FixTk", 'wx', 'PyQt4.QtAssistant', 'PyQt4.QtOpenGL.so',
excludes = ['matplotlib', "Tkconstants", "Tkinter", "tcl", "_imagingtk",
"ImageTk", "FixTk", 'wx', 'PyQt4.QtAssistant', 'PyQt4.QtOpenGL.so',
'PyQt4.QtScript.so', 'PyQt4.QtSql.so', 'PyQt4.QtTest.so', 'qt', 'PyQt4.QtScript.so', 'PyQt4.QtSql.so', 'PyQt4.QtTest.so', 'qt',
'glib', 'gobject'] 'glib', 'gobject']
packages = ['calibre', 'encodings', 'cherrypy', 'cssutils', 'xdg', packages = ['calibre', 'encodings', 'cherrypy', 'cssutils', 'xdg',
'dateutil'] 'dateutil', 'dns', 'email']
includes += ['calibre.web.feeds.recipes.'+r for r in recipe_modules] includes += ['calibre.web.feeds.recipes.'+r for r in recipe_modules]
LOADER = '/tmp/loader.py' LOADER = '/tmp/loader.py'
open(LOADER, 'wb').write('# This script is never actually used.\nimport sys') open(LOADER, 'wb').write('# This script is never actually used.\nimport sys')
INIT_SCRIPT = '/tmp/init.py' INIT_SCRIPT = '/tmp/init.py'
open(INIT_SCRIPT, 'wb').write(textwrap.dedent(''' open(INIT_SCRIPT, 'wb').write(textwrap.dedent('''
## Load calibre module specified in the environment variable CALIBRE_CX_EXE ## Load calibre module specified in the environment variable CALIBRE_CX_EXE
## Also restrict sys.path to the executables' directory and add the ## Also restrict sys.path to the executables' directory and add the
## executables directory to LD_LIBRARY_PATH ## executables directory to LD_LIBRARY_PATH
import encodings import encodings
import os import os
import sys import sys
@ -107,26 +109,26 @@ def freeze():
import zipimport import zipimport
import locale import locale
import codecs import codecs
enc = locale.getdefaultlocale()[1] enc = locale.getdefaultlocale()[1]
if not enc: if not enc:
enc = locale.nl_langinfo(locale.CODESET) enc = locale.nl_langinfo(locale.CODESET)
enc = codecs.lookup(enc if enc else 'UTF-8').name enc = codecs.lookup(enc if enc else 'UTF-8').name
sys.setdefaultencoding(enc) sys.setdefaultencoding(enc)
paths = os.environ.get('LD_LIBRARY_PATH', '').split(os.pathsep) paths = os.environ.get('LD_LIBRARY_PATH', '').split(os.pathsep)
if DIR_NAME not in paths or not sys.getfilesystemencoding(): if DIR_NAME not in paths or not sys.getfilesystemencoding():
paths.insert(0, DIR_NAME) paths.insert(0, DIR_NAME)
os.environ['LD_LIBRARY_PATH'] = os.pathsep.join(paths) os.environ['LD_LIBRARY_PATH'] = os.pathsep.join(paths)
os.environ['PYTHONIOENCODING'] = enc os.environ['PYTHONIOENCODING'] = enc
os.execv(sys.executable, sys.argv) os.execv(sys.executable, sys.argv)
sys.path = sys.path[:3] sys.path = sys.path[:3]
sys.frozen = True sys.frozen = True
sys.frozen_path = DIR_NAME sys.frozen_path = DIR_NAME
executables = %(executables)s executables = %(executables)s
exe = os.environ.get('CALIBRE_CX_EXE', False) exe = os.environ.get('CALIBRE_CX_EXE', False)
ret = 1 ret = 1
if not exe: if not exe:
@ -141,7 +143,7 @@ def freeze():
module = __import__(module, fromlist=[1]) module = __import__(module, fromlist=[1])
func = getattr(module, func) func = getattr(module, func)
ret = func() ret = func()
module = sys.modules.get("threading") module = sys.modules.get("threading")
if module is not None: if module is not None:
module._shutdown() module._shutdown()
@ -162,35 +164,35 @@ def freeze():
'init_script' : INIT_SCRIPT, 'init_script' : INIT_SCRIPT,
'copy_dependent_files' : True, 'copy_dependent_files' : True,
'create_shared_zip' : False, 'create_shared_zip' : False,
} }
} }
) )
def copy_binary(src, dest_dir): def copy_binary(src, dest_dir):
dest = os.path.join(dest_dir, os.path.basename(src)) dest = os.path.join(dest_dir, os.path.basename(src))
if not os.path.exists(dest_dir): if not os.path.exists(dest_dir):
os.makedirs(dest_dir) os.makedirs(dest_dir)
shutil.copyfile(os.path.realpath(src), dest) shutil.copyfile(os.path.realpath(src), dest)
shutil.copymode(os.path.realpath(src), dest) shutil.copymode(os.path.realpath(src), dest)
for f in binary_includes: for f in binary_includes:
copy_binary(f, FREEZE_DIR) copy_binary(f, FREEZE_DIR)
for pat in binary_excludes: for pat in binary_excludes:
matches = glob.glob(os.path.join(FREEZE_DIR, pat)) matches = glob.glob(os.path.join(FREEZE_DIR, pat))
for f in matches: for f in matches:
os.remove(f) os.remove(f)
print 'Adding calibre plugins...' print 'Adding calibre plugins...'
os.makedirs(os.path.join(FREEZE_DIR, 'plugins')) os.makedirs(os.path.join(FREEZE_DIR, 'plugins'))
for f in glob.glob(os.path.join(CALIBREPLUGINS, '*.so')): for f in glob.glob(os.path.join(CALIBREPLUGINS, '*.so')):
copy_binary(f, os.path.join(FREEZE_DIR, 'plugins')) copy_binary(f, os.path.join(FREEZE_DIR, 'plugins'))
print 'Adding Qt plugins...' print 'Adding Qt plugins...'
plugdir = os.path.join(QTDIR, 'plugins') plugdir = os.path.join(QTDIR, 'plugins')
for dirpath, dirnames, filenames in os.walk(plugdir): for dirpath, dirnames, filenames in os.walk(plugdir):
for f in filenames: for f in filenames:
if not f.endswith('.so') or 'designer' in dirpath or 'codecs' in dirpath or 'sqldrivers' in dirpath: if not f.endswith('.so') or 'designer' in dirpath or 'codecs' in dirpath or 'sqldrivers' in dirpath:
continue continue
f = os.path.join(dirpath, f) f = os.path.join(dirpath, f)
dest_dir = dirpath.replace(plugdir, os.path.join(FREEZE_DIR, 'qtplugins')) dest_dir = dirpath.replace(plugdir, os.path.join(FREEZE_DIR, 'qtplugins'))
@ -198,7 +200,7 @@ def freeze():
print 'Creating launchers' print 'Creating launchers'
for exe in executables: for exe in executables:
path = os.path.join(FREEZE_DIR, exe) path = os.path.join(FREEZE_DIR, exe)
open(path, 'wb').write(textwrap.dedent('''\ open(path, 'wb').write(textwrap.dedent('''\
#!/bin/sh #!/bin/sh
export CALIBRE_CX_EXE=%s export CALIBRE_CX_EXE=%s
@ -209,15 +211,15 @@ def freeze():
$loader "$@" $loader "$@"
''')%exe) ''')%exe)
os.chmod(path, 0755) os.chmod(path, 0755)
exes = list(executables.keys()) exes = list(executables.keys())
exes.remove('calibre_postinstall') exes.remove('calibre_postinstall')
exes.remove('calibre-parallel') exes.remove('calibre-parallel')
open(os.path.join(FREEZE_DIR, 'manifest'), 'wb').write('\n'.join(exes)) open(os.path.join(FREEZE_DIR, 'manifest'), 'wb').write('\n'.join(exes))
print 'Creating archive...' print 'Creating archive...'
dist = open(os.path.join(DIST_DIR, 'calibre-%s-i686.tar.bz2'%__version__), 'wb') dist = open(os.path.join(DIST_DIR, 'calibre-%s-i686.tar.bz2'%__version__), 'wb')
with closing(tarfile.open(fileobj=dist, mode='w:bz2', with closing(tarfile.open(fileobj=dist, mode='w:bz2',
format=tarfile.PAX_FORMAT)) as tf: format=tarfile.PAX_FORMAT)) as tf:
for f in walk(FREEZE_DIR): for f in walk(FREEZE_DIR):
name = f.replace(FREEZE_DIR, '')[1:] name = f.replace(FREEZE_DIR, '')[1:]

View File

@ -14,12 +14,11 @@ IMAGEMAGICK_DIR = 'C:\\ImageMagick'
FONTCONFIG_DIR = 'C:\\fontconfig' FONTCONFIG_DIR = 'C:\\fontconfig'
VC90 = r'C:\VC90.CRT' VC90 = r'C:\VC90.CRT'
import sys, os, py2exe, shutil, zipfile, glob, subprocess, re import sys, os, py2exe, shutil, zipfile, glob, re
from distutils.core import setup from distutils.core import setup
from distutils.filelist import FileList
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
sys.path.insert(0, BASE_DIR) sys.path.insert(0, BASE_DIR)
from setup import VERSION, APPNAME, entry_points, scripts, basenames from setup import VERSION, APPNAME, scripts, basenames
sys.path.remove(BASE_DIR) sys.path.remove(BASE_DIR)
ICONS = [os.path.abspath(os.path.join(BASE_DIR, 'icons', i)) for i in ('library.ico', 'viewer.ico')] ICONS = [os.path.abspath(os.path.join(BASE_DIR, 'icons', i)) for i in ('library.ico', 'viewer.ico')]
@ -33,7 +32,7 @@ WINVER = VERSION+'.0'
PY2EXE_DIR = os.path.join(BASE_DIR, 'build','py2exe') PY2EXE_DIR = os.path.join(BASE_DIR, 'build','py2exe')
class BuildEXE(py2exe.build_exe.py2exe): class BuildEXE(py2exe.build_exe.py2exe):
def run(self): def run(self):
py2exe.build_exe.py2exe.run(self) py2exe.build_exe.py2exe.run(self)
print 'Adding plugins...' print 'Adding plugins...'
@ -61,19 +60,19 @@ class BuildEXE(py2exe.build_exe.py2exe):
if os.path.exists(tg): if os.path.exists(tg):
shutil.rmtree(tg) shutil.rmtree(tg)
shutil.copytree(imfd, tg) shutil.copytree(imfd, tg)
print print
print 'Adding main scripts' print 'Adding main scripts'
f = zipfile.ZipFile(os.path.join(PY2EXE_DIR, 'library.zip'), 'a', zipfile.ZIP_DEFLATED) f = zipfile.ZipFile(os.path.join(PY2EXE_DIR, 'library.zip'), 'a', zipfile.ZIP_DEFLATED)
for i in scripts['console'] + scripts['gui']: for i in scripts['console'] + scripts['gui']:
f.write(i, i.partition('\\')[-1]) f.write(i, i.partition('\\')[-1])
f.close() f.close()
print print
print 'Copying icons' print 'Copying icons'
for icon in ICONS: for icon in ICONS:
shutil.copyfile(icon, os.path.join(PY2EXE_DIR, os.path.basename(icon))) shutil.copyfile(icon, os.path.join(PY2EXE_DIR, os.path.basename(icon)))
print print
print 'Adding third party dependencies' print 'Adding third party dependencies'
print '\tAdding devcon' print '\tAdding devcon'
@ -96,18 +95,18 @@ class BuildEXE(py2exe.build_exe.py2exe):
shutil.copytree(f, tgt) shutil.copytree(f, tgt)
else: else:
shutil.copyfile(f, tgt) shutil.copyfile(f, tgt)
print print
print 'Doing DLL redirection' # See http://msdn.microsoft.com/en-us/library/ms682600(VS.85).aspx print 'Doing DLL redirection' # See http://msdn.microsoft.com/en-us/library/ms682600(VS.85).aspx
for f in glob.glob(os.path.join(PY2EXE_DIR, '*.exe')): for f in glob.glob(os.path.join(PY2EXE_DIR, '*.exe')):
open(f + '.local', 'w').write('\n') open(f + '.local', 'w').write('\n')
print print
print 'Adding Windows runtime dependencies...' print 'Adding Windows runtime dependencies...'
for f in glob.glob(os.path.join(VC90, '*')): for f in glob.glob(os.path.join(VC90, '*')):
shutil.copyfile(f, os.path.join(PY2EXE_DIR, os.path.basename(f))) shutil.copyfile(f, os.path.join(PY2EXE_DIR, os.path.basename(f)))
def exe_factory(dest_base, script, icon_resources=None): def exe_factory(dest_base, script, icon_resources=None):
exe = { exe = {
'dest_base' : dest_base, 'dest_base' : dest_base,
@ -144,7 +143,9 @@ def main(args=sys.argv):
'includes' : [ 'includes' : [
'sip', 'pkg_resources', 'PyQt4.QtSvg', 'sip', 'pkg_resources', 'PyQt4.QtSvg',
'mechanize', 'ClientForm', 'wmi', 'mechanize', 'ClientForm', 'wmi',
'win32file', 'pythoncom', 'win32file', 'pythoncom',
'email.iterators',
'email.generator',
'win32process', 'win32api', 'msvcrt', 'win32process', 'win32api', 'msvcrt',
'win32event', 'calibre.ebooks.lrf.any.*', 'win32event', 'calibre.ebooks.lrf.any.*',
'calibre.ebooks.lrf.feeds.*', 'calibre.ebooks.lrf.feeds.*',
@ -155,14 +156,14 @@ def main(args=sys.argv):
'PyQt4.QtWebKit', 'PyQt4.QtNetwork', 'PyQt4.QtWebKit', 'PyQt4.QtNetwork',
], ],
'packages' : ['PIL', 'lxml', 'cherrypy', 'packages' : ['PIL', 'lxml', 'cherrypy',
'dateutil'], 'dateutil', 'dns'],
'excludes' : ["Tkconstants", "Tkinter", "tcl", 'excludes' : ["Tkconstants", "Tkinter", "tcl",
"_imagingtk", "ImageTk", "FixTk" "_imagingtk", "ImageTk", "FixTk"
], ],
'dll_excludes' : ['mswsock.dll'], 'dll_excludes' : ['mswsock.dll'],
}, },
}, },
) )
return 0 return 0

View File

@ -59,8 +59,8 @@ class FetchISBNDB(Thread):
args.extend(['--author', self.author]) args.extend(['--author', self.author])
if self.publisher: if self.publisher:
args.extend(['--publisher', self.publisher]) args.extend(['--publisher', self.publisher])
if self.verbose: if self.verbose:
args.extend(['--verbose']) args.extend(['--verbose'])
args.append(self.key) args.append(self.key)
try: try:
opts, args = option_parser().parse_args(args) opts, args = option_parser().parse_args(args)

View File

@ -28,7 +28,8 @@ def fetch_metadata(url, max=100, timeout=5.):
raw = urlopen(url).read() raw = urlopen(url).read()
except Exception, err: except Exception, err:
raise ISBNDBError('Could not fetch ISBNDB metadata. Error: '+str(err)) raise ISBNDBError('Could not fetch ISBNDB metadata. Error: '+str(err))
soup = BeautifulStoneSoup(raw) soup = BeautifulStoneSoup(raw,
convertEntities=BeautifulStoneSoup.XML_ENTITIES)
book_list = soup.find('booklist') book_list = soup.find('booklist')
if book_list is None: if book_list is None:
errmsg = soup.find('errormessage').string errmsg = soup.find('errormessage').string
@ -41,13 +42,13 @@ def fetch_metadata(url, max=100, timeout=5.):
return books return books
finally: finally:
socket.setdefaulttimeout(timeout) socket.setdefaulttimeout(timeout)
class ISBNDBMetadata(MetaInformation): class ISBNDBMetadata(MetaInformation):
def __init__(self, book): def __init__(self, book):
MetaInformation.__init__(self, None, []) MetaInformation.__init__(self, None, [])
self.isbn = book['isbn'] self.isbn = book['isbn']
self.title = book.find('titlelong').string self.title = book.find('titlelong').string
if not self.title: if not self.title:
@ -59,7 +60,7 @@ class ISBNDBMetadata(MetaInformation):
for au in temp: for au in temp:
if not au: continue if not au: continue
self.authors.extend([a.strip() for a in au.split('&')]) self.authors.extend([a.strip() for a in au.split('&')])
try: try:
self.author_sort = book.find('authors').find('person').string self.author_sort = book.find('authors').find('person').string
if self.authors and self.author_sort == self.authors[0]: if self.authors and self.author_sort == self.authors[0]:
@ -67,12 +68,12 @@ class ISBNDBMetadata(MetaInformation):
except: except:
pass pass
self.publisher = book.find('publishertext').string self.publisher = book.find('publishertext').string
summ = book.find('summary') summ = book.find('summary')
if summ and hasattr(summ, 'string') and summ.string: if summ and hasattr(summ, 'string') and summ.string:
self.comments = 'SUMMARY:\n'+summ.string self.comments = 'SUMMARY:\n'+summ.string
def build_isbn(base_url, opts): def build_isbn(base_url, opts):
return base_url + 'index1=isbn&value1='+opts.isbn return base_url + 'index1=isbn&value1='+opts.isbn
@ -85,11 +86,11 @@ def build_combined(base_url, opts):
query = query.strip() query = query.strip()
if len(query) == 0: if len(query) == 0:
raise ISBNDBError('You must specify at least one of --author, --title or --publisher') raise ISBNDBError('You must specify at least one of --author, --title or --publisher')
query = re.sub(r'\s+', '+', query) query = re.sub(r'\s+', '+', query)
if isinstance(query, unicode): if isinstance(query, unicode):
query = query.encode('utf-8') query = query.encode('utf-8')
return base_url+'index1=combined&value1='+quote(query, '+') return base_url+'index1=combined&value1='+quote(query, '+')
def option_parser(): def option_parser():
@ -97,7 +98,7 @@ def option_parser():
_(''' _('''
%prog [options] key %prog [options] key
Fetch metadata for books from isndb.com. You can specify either the Fetch metadata for books from isndb.com. You can specify either the
books ISBN ID or its title and author. If you specify the title and author, books ISBN ID or its title and author. If you specify the title and author,
then more than one book may be returned. then more than one book may be returned.
@ -112,11 +113,11 @@ key is the account key you generate after signing up for a free account from isb
default=None, help=_('The title of the book to search for.')) default=None, help=_('The title of the book to search for.'))
parser.add_option('-p', '--publisher', default=None, dest='publisher', parser.add_option('-p', '--publisher', default=None, dest='publisher',
help=_('The publisher of the book to search for.')) help=_('The publisher of the book to search for.'))
parser.add_option('-v', '--verbose', default=False, parser.add_option('-v', '--verbose', default=False,
action='store_true', help=_('Verbose processing')) action='store_true', help=_('Verbose processing'))
return parser return parser
def create_books(opts, args, timeout=5.): def create_books(opts, args, timeout=5.):
base_url = BASE_URL%dict(key=args[1]) base_url = BASE_URL%dict(key=args[1])
@ -124,10 +125,10 @@ def create_books(opts, args, timeout=5.):
url = build_isbn(base_url, opts) url = build_isbn(base_url, opts)
else: else:
url = build_combined(base_url, opts) url = build_combined(base_url, opts)
if opts.verbose: if opts.verbose:
print ('ISBNDB query: '+url) print ('ISBNDB query: '+url)
return [ISBNDBMetadata(book) for book in fetch_metadata(url, timeout=timeout)] return [ISBNDBMetadata(book) for book in fetch_metadata(url, timeout=timeout)]
def main(args=sys.argv): def main(args=sys.argv):
@ -137,10 +138,10 @@ def main(args=sys.argv):
parser.print_help() parser.print_help()
print ('You must supply the isbndb.com key') print ('You must supply the isbndb.com key')
return 1 return 1
for book in create_books(opts, args): for book in create_books(opts, args):
print unicode(book).encode('utf-8') print unicode(book).encode('utf-8')
return 0 return 0
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -337,7 +337,7 @@ class DeviceMenu(QMenu):
class Emailer(Thread): class Emailer(Thread):
def __init__(self, timeout=10): def __init__(self, timeout=60):
Thread.__init__(self) Thread.__init__(self)
self.setDaemon(True) self.setDaemon(True)
self.job_lock = RLock() self.job_lock = RLock()
@ -369,6 +369,7 @@ class Emailer(Thread):
def _send_mails(self, jobnames, callback, attachments, def _send_mails(self, jobnames, callback, attachments,
to_s, subjects, texts, attachment_names): to_s, subjects, texts, attachment_names):
opts = email_config().parse() opts = email_config().parse()
opts.verbose = 3 if os.environ.get('CALIBRE_DEBUG_EMAIL', False) else 0
from_ = opts.from_ from_ = opts.from_
if not from_: if not from_:
from_ = 'calibre <calibre@'+socket.getfqdn()+'>' from_ = 'calibre <calibre@'+socket.getfqdn()+'>'
@ -380,7 +381,8 @@ class Emailer(Thread):
attachment_name = attachment_names[i]) attachment_name = attachment_names[i])
efrom, eto = map(extract_email_address, (from_, to_s[i])) efrom, eto = map(extract_email_address, (from_, to_s[i]))
eto = [eto] eto = [eto]
sendmail(msg, efrom, eto, localhost=None, verbose=0, sendmail(msg, efrom, eto, localhost=None,
verbose=opts.verbose,
timeout=self.timeout, relay=opts.relay_host, timeout=self.timeout, relay=opts.relay_host,
username=opts.relay_username, username=opts.relay_username,
password=opts.relay_password, port=opts.relay_port, password=opts.relay_password, port=opts.relay_port,

View File

@ -11,12 +11,12 @@ from PyQt4.QtCore import Qt, QObject, SIGNAL, QVariant, QThread, \
from PyQt4.QtGui import QDialog, QItemSelectionModel from PyQt4.QtGui import QDialog, QItemSelectionModel
from calibre.gui2.dialogs.fetch_metadata_ui import Ui_FetchMetadata from calibre.gui2.dialogs.fetch_metadata_ui import Ui_FetchMetadata
from calibre.gui2 import error_dialog, NONE, info_dialog, warning_dialog from calibre.gui2 import error_dialog, NONE, info_dialog
from calibre.gui2.widgets import ProgressIndicator from calibre.gui2.widgets import ProgressIndicator
from calibre.utils.config import prefs from calibre.utils.config import prefs
class Fetcher(QThread): class Fetcher(QThread):
def __init__(self, title, author, publisher, isbn, key): def __init__(self, title, author, publisher, isbn, key):
QThread.__init__(self) QThread.__init__(self)
self.title = title self.title = title
@ -24,47 +24,47 @@ class Fetcher(QThread):
self.publisher = publisher self.publisher = publisher
self.isbn = isbn self.isbn = isbn
self.key = key self.key = key
def run(self): def run(self):
from calibre.ebooks.metadata.fetch import search from calibre.ebooks.metadata.fetch import search
self.results, self.exceptions = search(self.title, self.author, self.results, self.exceptions = search(self.title, self.author,
self.publisher, self.isbn, self.publisher, self.isbn,
self.key if self.key else None) self.key if self.key else None)
class Matches(QAbstractTableModel): class Matches(QAbstractTableModel):
def __init__(self, matches): def __init__(self, matches):
self.matches = matches self.matches = matches
self.matches.sort(cmp=lambda b, a: \ self.matches.sort(cmp=lambda b, a: \
cmp(len(a.comments if a.comments else ''), cmp(len(a.comments if a.comments else ''),
len(b.comments if b.comments else ''))) len(b.comments if b.comments else '')))
QAbstractTableModel.__init__(self) QAbstractTableModel.__init__(self)
def rowCount(self, *args): def rowCount(self, *args):
return len(self.matches) return len(self.matches)
def columnCount(self, *args): def columnCount(self, *args):
return 5 return 5
def headerData(self, section, orientation, role): def headerData(self, section, orientation, role):
if role != Qt.DisplayRole: if role != Qt.DisplayRole:
return NONE return NONE
text = "" text = ""
if orientation == Qt.Horizontal: if orientation == Qt.Horizontal:
if section == 0: text = _("Title") if section == 0: text = _("Title")
elif section == 1: text = _("Author(s)") elif section == 1: text = _("Author(s)")
elif section == 2: text = _("Author Sort") elif section == 2: text = _("Author Sort")
elif section == 3: text = _("Publisher") elif section == 3: text = _("Publisher")
elif section == 4: text = _("ISBN") elif section == 4: text = _("ISBN")
return QVariant(text) return QVariant(text)
else: else:
return QVariant(section+1) return QVariant(section+1)
def summary(self, row): def summary(self, row):
return self.matches[row].comments return self.matches[row].comments
def data(self, index, role): def data(self, index, role):
row, col = index.row(), index.column() row, col = index.row(), index.column()
if role == Qt.DisplayRole: if role == Qt.DisplayRole:
@ -86,18 +86,18 @@ class Matches(QAbstractTableModel):
return NONE return NONE
class FetchMetadata(QDialog, Ui_FetchMetadata): class FetchMetadata(QDialog, Ui_FetchMetadata):
def __init__(self, parent, isbn, title, author, publisher, timeout): def __init__(self, parent, isbn, title, author, publisher, timeout):
QDialog.__init__(self, parent) QDialog.__init__(self, parent)
Ui_FetchMetadata.__init__(self) Ui_FetchMetadata.__init__(self)
self.setupUi(self) self.setupUi(self)
self.pi = ProgressIndicator(self) self.pi = ProgressIndicator(self)
self.timeout = timeout self.timeout = timeout
QObject.connect(self.fetch, SIGNAL('clicked()'), self.fetch_metadata) QObject.connect(self.fetch, SIGNAL('clicked()'), self.fetch_metadata)
self.key.setText(prefs['isbndb_com_key']) self.key.setText(prefs['isbndb_com_key'])
self.setWindowTitle(title if title else _('Unknown')) self.setWindowTitle(title if title else _('Unknown'))
self.isbn = isbn self.isbn = isbn
self.title = title self.title = title
@ -106,19 +106,19 @@ class FetchMetadata(QDialog, Ui_FetchMetadata):
self.previous_row = None self.previous_row = None
self.warning.setVisible(False) self.warning.setVisible(False)
self.connect(self.matches, SIGNAL('activated(QModelIndex)'), self.chosen) self.connect(self.matches, SIGNAL('activated(QModelIndex)'), self.chosen)
self.connect(self.matches, SIGNAL('entered(QModelIndex)'), self.connect(self.matches, SIGNAL('entered(QModelIndex)'),
self.show_summary) self.show_summary)
self.matches.setMouseTracking(True) self.matches.setMouseTracking(True)
self.fetch_metadata() self.fetch_metadata()
def show_summary(self, current, *args): def show_summary(self, current, *args):
row = current.row() row = current.row()
if row != self.previous_row: if row != self.previous_row:
summ = self.model.summary(row) summ = self.model.summary(row)
self.summary.setText(summ if summ else '') self.summary.setText(summ if summ else '')
self.previous_row = row self.previous_row = row
def fetch_metadata(self): def fetch_metadata(self):
self.warning.setVisible(False) self.warning.setVisible(False)
key = str(self.key.text()) key = str(self.key.text())
@ -143,7 +143,7 @@ class FetchMetadata(QDialog, Ui_FetchMetadata):
self.connect(self._hangcheck, SIGNAL('timeout()'), self.hangcheck) self.connect(self._hangcheck, SIGNAL('timeout()'), self.hangcheck)
self.start_time = time.time() self.start_time = time.time()
self._hangcheck.start(100) self._hangcheck.start(100)
def hangcheck(self): def hangcheck(self):
if not (self.fetcher.isFinished() or time.time() - self.start_time > 75): if not (self.fetcher.isFinished() or time.time() - self.start_time > 75):
return return
@ -169,13 +169,13 @@ class FetchMetadata(QDialog, Ui_FetchMetadata):
_('No metadata found, try adjusting the title and author ' _('No metadata found, try adjusting the title and author '
'or the ISBN key.')).exec_() 'or the ISBN key.')).exec_()
return return
self.matches.setModel(self.model) self.matches.setModel(self.model)
QObject.connect(self.matches.selectionModel(), QObject.connect(self.matches.selectionModel(),
SIGNAL('currentRowChanged(QModelIndex, QModelIndex)'), SIGNAL('currentRowChanged(QModelIndex, QModelIndex)'),
self.show_summary) self.show_summary)
self.model.reset() self.model.reset()
self.matches.selectionModel().select(self.model.index(0, 0), self.matches.selectionModel().select(self.model.index(0, 0),
QItemSelectionModel.Select | QItemSelectionModel.Rows) QItemSelectionModel.Select | QItemSelectionModel.Rows)
self.matches.setCurrentIndex(self.model.index(0, 0)) self.matches.setCurrentIndex(self.model.index(0, 0))
finally: finally:
@ -183,14 +183,24 @@ class FetchMetadata(QDialog, Ui_FetchMetadata):
self.unsetCursor() self.unsetCursor()
self.matches.resizeColumnsToContents() self.matches.resizeColumnsToContents()
self.pi.stop() self.pi.stop()
def terminate(self):
if hasattr(self, 'fetcher') and self.fetcher.isRunning():
self.fetcher.terminate()
def __enter__(self, *args):
return self
def __exit__(self, *args):
self.terminate()
def selected_book(self): def selected_book(self):
try: try:
return self.matches.model().matches[self.matches.currentIndex().row()] return self.matches.model().matches[self.matches.currentIndex().row()]
except: except:
return None return None
def chosen(self, index): def chosen(self, index):
self.matches.setCurrentIndex(index) self.matches.setCurrentIndex(index)
self.accept() self.accept()

View File

@ -1,7 +1,8 @@
from __future__ import with_statement
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
''' '''
The dialog used to edit meta information for a book as well as The dialog used to edit meta information for a book as well as
add/remove formats add/remove formats
''' '''
import os, time, traceback import os, time, traceback
@ -26,7 +27,7 @@ from calibre.utils.config import prefs
from calibre.customize.ui import run_plugins_on_import from calibre.customize.ui import run_plugins_on_import
class CoverFetcher(QThread): class CoverFetcher(QThread):
def __init__(self, username, password, isbn, timeout): def __init__(self, username, password, isbn, timeout):
self.username = username self.username = username
self.password = password self.password = password
@ -34,7 +35,7 @@ class CoverFetcher(QThread):
self.isbn = isbn self.isbn = isbn
QThread.__init__(self) QThread.__init__(self)
self.exception = self.traceback = self.cover_data = None self.exception = self.traceback = self.cover_data = None
def run(self): def run(self):
try: try:
login(self.username, self.password, force=False) login(self.username, self.password, force=False)
@ -42,8 +43,8 @@ class CoverFetcher(QThread):
except Exception, e: except Exception, e:
self.exception = e self.exception = e
self.traceback = traceback.format_exc() self.traceback = traceback.format_exc()
class Format(QListWidgetItem): class Format(QListWidgetItem):
def __init__(self, parent, ext, size, path=None): def __init__(self, parent, ext, size, path=None):
@ -51,25 +52,25 @@ class Format(QListWidgetItem):
self.ext = ext self.ext = ext
self.size = float(size)/(1024*1024) self.size = float(size)/(1024*1024)
text = '%s (%.2f MB)'%(self.ext.upper(), self.size) text = '%s (%.2f MB)'%(self.ext.upper(), self.size)
QListWidgetItem.__init__(self, file_icon_provider().icon_from_ext(ext), QListWidgetItem.__init__(self, file_icon_provider().icon_from_ext(ext),
text, parent, QListWidgetItem.UserType) text, parent, QListWidgetItem.UserType)
class AuthorCompleter(QCompleter): class AuthorCompleter(QCompleter):
def __init__(self, db): def __init__(self, db):
all_authors = db.all_authors() all_authors = db.all_authors()
all_authors.sort(cmp=lambda x, y : cmp(x[1], y[1])) all_authors.sort(cmp=lambda x, y : cmp(x[1], y[1]))
QCompleter.__init__(self, [x[1] for x in all_authors]) QCompleter.__init__(self, [x[1] for x in all_authors])
class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
def do_reset_cover(self, *args): def do_reset_cover(self, *args):
pix = QPixmap(':/images/book.svg') pix = QPixmap(':/images/book.svg')
self.cover.setPixmap(pix) self.cover.setPixmap(pix)
self.cover_changed = True self.cover_changed = True
def select_cover(self, checked): def select_cover(self, checked):
files = choose_images(self, 'change cover dialog', files = choose_images(self, 'change cover dialog',
u'Choose cover for ' + qstring_to_unicode(self.title.text())) u'Choose cover for ' + qstring_to_unicode(self.title.text()))
if not files: if not files:
return return
@ -77,7 +78,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
if _file: if _file:
_file = os.path.abspath(_file) _file = os.path.abspath(_file)
if not os.access(_file, os.R_OK): if not os.access(_file, os.R_OK):
d = error_dialog(self.window, _('Cannot read'), d = error_dialog(self.window, _('Cannot read'),
_('You do not have permission to read the file: ') + _file) _('You do not have permission to read the file: ') + _file)
d.exec_() d.exec_()
return return
@ -85,7 +86,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
try: try:
cf = open(_file, "rb") cf = open(_file, "rb")
cover = cf.read() cover = cf.read()
except IOError, e: except IOError, e:
d = error_dialog(self.window, _('Error reading file'), d = error_dialog(self.window, _('Error reading file'),
_("<p>There was an error reading from file: <br /><b>") + _file + "</b></p><br />"+str(e)) _("<p>There was an error reading from file: <br /><b>") + _file + "</b></p><br />"+str(e))
d.exec_() d.exec_()
@ -99,15 +100,15 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
self.cover_path.setText(_file) self.cover_path.setText(_file)
self.cover.setPixmap(pix) self.cover.setPixmap(pix)
self.cover_changed = True self.cover_changed = True
self.cpixmap = pix self.cpixmap = pix
def add_format(self, x): def add_format(self, x):
files = choose_files(self, 'add formats dialog', files = choose_files(self, 'add formats dialog',
"Choose formats for " + qstring_to_unicode((self.title.text())), "Choose formats for " + qstring_to_unicode((self.title.text())),
[('Books', BOOK_EXTENSIONS)]) [('Books', BOOK_EXTENSIONS)])
if not files: if not files:
return return
for _file in files: for _file in files:
_file = os.path.abspath(_file) _file = os.path.abspath(_file)
if not os.access(_file, os.R_OK): if not os.access(_file, os.R_OK):
@ -124,13 +125,13 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
break break
Format(self.formats, ext, size, path=_file) Format(self.formats, ext, size, path=_file)
self.formats_changed = True self.formats_changed = True
def remove_format(self, x): def remove_format(self, x):
rows = self.formats.selectionModel().selectedRows(0) rows = self.formats.selectionModel().selectedRows(0)
for row in rows: for row in rows:
self.formats.takeItem(row.row()) self.formats.takeItem(row.row())
self.formats_changed = True self.formats_changed = True
def set_cover(self): def set_cover(self):
row = self.formats.currentRow() row = self.formats.currentRow()
fmt = self.formats.item(row) fmt = self.formats.item(row)
@ -155,19 +156,19 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
elif mi.cover_data[1] is not None: elif mi.cover_data[1] is not None:
cdata = mi.cover_data[1] cdata = mi.cover_data[1]
if cdata is None: if cdata is None:
error_dialog(self, _('Could not read cover'), error_dialog(self, _('Could not read cover'),
_('Could not read cover from %s format')%ext).exec_() _('Could not read cover from %s format')%ext).exec_()
return return
pix = QPixmap() pix = QPixmap()
pix.loadFromData(cdata) pix.loadFromData(cdata)
if pix.isNull(): if pix.isNull():
error_dialog(self, _('Could not read cover'), error_dialog(self, _('Could not read cover'),
_('The cover in the %s format is invalid')%ext).exec_() _('The cover in the %s format is invalid')%ext).exec_()
return return
self.cover.setPixmap(pix) self.cover.setPixmap(pix)
self.cover_changed = True self.cover_changed = True
self.cpixmap = pix self.cpixmap = pix
def sync_formats(self): def sync_formats(self):
old_extensions, new_extensions, paths = set(), set(), {} old_extensions, new_extensions, paths = set(), set(), {}
for row in range(self.formats.count()): for row in range(self.formats.count()):
@ -187,7 +188,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
for ext in db_extensions: for ext in db_extensions:
if ext not in extensions: if ext not in extensions:
self.db.remove_format(self.row, ext, notify=False) self.db.remove_format(self.row, ext, notify=False)
def __init__(self, window, row, db, accepted_callback=None): def __init__(self, window, row, db, accepted_callback=None):
ResizableDialog.__init__(self, window) ResizableDialog.__init__(self, window)
self.bc_box.layout().setAlignment(self.cover, Qt.AlignCenter|Qt.AlignHCenter) self.bc_box.layout().setAlignment(self.cover, Qt.AlignCenter|Qt.AlignHCenter)
@ -211,12 +212,12 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
self.add_format) self.add_format)
QObject.connect(self.remove_format_button, SIGNAL("clicked(bool)"), \ QObject.connect(self.remove_format_button, SIGNAL("clicked(bool)"), \
self.remove_format) self.remove_format)
QObject.connect(self.fetch_metadata_button, SIGNAL('clicked()'), QObject.connect(self.fetch_metadata_button, SIGNAL('clicked()'),
self.fetch_metadata) self.fetch_metadata)
QObject.connect(self.fetch_cover_button, SIGNAL('clicked()'), QObject.connect(self.fetch_cover_button, SIGNAL('clicked()'),
self.fetch_cover) self.fetch_cover)
QObject.connect(self.tag_editor_button, SIGNAL('clicked()'), QObject.connect(self.tag_editor_button, SIGNAL('clicked()'),
self.edit_tags) self.edit_tags)
QObject.connect(self.remove_series_button, SIGNAL('clicked()'), QObject.connect(self.remove_series_button, SIGNAL('clicked()'),
self.remove_unused_series) self.remove_unused_series)
@ -242,28 +243,28 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
tags = self.db.tags(row) tags = self.db.tags(row)
self.tags.setText(tags if tags else '') self.tags.setText(tags if tags else '')
rating = self.db.rating(row) rating = self.db.rating(row)
if rating > 0: if rating > 0:
self.rating.setValue(int(rating/2.)) self.rating.setValue(int(rating/2.))
comments = self.db.comments(row) comments = self.db.comments(row)
self.comments.setPlainText(comments if comments else '') self.comments.setPlainText(comments if comments else '')
cover = self.db.cover(row) cover = self.db.cover(row)
exts = self.db.formats(row) exts = self.db.formats(row)
if exts: if exts:
exts = exts.split(',') exts = exts.split(',')
for ext in exts: for ext in exts:
if not ext: if not ext:
ext = '' ext = ''
size = self.db.sizeof_format(row, ext) size = self.db.sizeof_format(row, ext)
Format(self.formats, ext, size) Format(self.formats, ext, size)
self.initialize_series_and_publisher() self.initialize_series_and_publisher()
self.series_index.setValue(self.db.series_index(row)) self.series_index.setValue(self.db.series_index(row))
QObject.connect(self.series, SIGNAL('currentIndexChanged(int)'), self.enable_series_index) QObject.connect(self.series, SIGNAL('currentIndexChanged(int)'), self.enable_series_index)
QObject.connect(self.series, SIGNAL('editTextChanged(QString)'), self.enable_series_index) QObject.connect(self.series, SIGNAL('editTextChanged(QString)'), self.enable_series_index)
QObject.connect(self.password_button, SIGNAL('clicked()'), self.change_password) QObject.connect(self.password_button, SIGNAL('clicked()'), self.change_password)
self.show() self.show()
height_of_rest = self.frameGeometry().height() - self.cover.height() height_of_rest = self.frameGeometry().height() - self.cover.height()
@ -274,14 +275,14 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
if cover: if cover:
pm = QPixmap() pm = QPixmap()
pm.loadFromData(cover) pm.loadFromData(cover)
if not pm.isNull(): if not pm.isNull():
self.cover.setPixmap(pm) self.cover.setPixmap(pm)
def deduce_author_sort(self): def deduce_author_sort(self):
au = unicode(self.authors.text()) au = unicode(self.authors.text())
authors = string_to_authors(au) authors = string_to_authors(au)
self.author_sort.setText(authors_to_sort_string(authors)) self.author_sort.setText(authors_to_sort_string(authors))
def swap_title_author(self): def swap_title_author(self):
title = self.title.text() title = self.title.text()
self.title.setText(self.authors.text()) self.title.setText(self.authors.text())
@ -290,7 +291,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
def cover_dropped(self): def cover_dropped(self):
self.cover_changed = True self.cover_changed = True
def initialize_series(self): def initialize_series(self):
all_series = self.db.all_series() all_series = self.db.all_series()
all_series.sort(cmp=lambda x, y : cmp(x[1], y[1])) all_series.sort(cmp=lambda x, y : cmp(x[1], y[1]))
@ -302,19 +303,19 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
idx = c idx = c
self.series.addItem(name) self.series.addItem(name)
c += 1 c += 1
self.series.lineEdit().setText('') self.series.lineEdit().setText('')
if idx is not None: if idx is not None:
self.series.setCurrentIndex(idx) self.series.setCurrentIndex(idx)
self.enable_series_index() self.enable_series_index()
pl = self.series.parentWidget().layout() pl = self.series.parentWidget().layout()
for i in range(pl.count()): for i in range(pl.count()):
l = pl.itemAt(i).layout() l = pl.itemAt(i).layout()
if l: if l:
l.invalidate() l.invalidate()
l.activate() l.activate()
def initialize_series_and_publisher(self): def initialize_series_and_publisher(self):
self.initialize_series() self.initialize_series()
all_publishers = self.db.all_publishers() all_publishers = self.db.all_publishers()
@ -327,33 +328,33 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
idx = c idx = c
self.publisher.addItem(name) self.publisher.addItem(name)
c += 1 c += 1
self.publisher.setEditText('') self.publisher.setEditText('')
if idx is not None: if idx is not None:
self.publisher.setCurrentIndex(idx) self.publisher.setCurrentIndex(idx)
self.layout().activate() self.layout().activate()
def edit_tags(self): def edit_tags(self):
d = TagEditor(self, self.db, self.row) d = TagEditor(self, self.db, self.row)
d.exec_() d.exec_()
if d.result() == QDialog.Accepted: if d.result() == QDialog.Accepted:
tag_string = ', '.join(d.tags) tag_string = ', '.join(d.tags)
self.tags.setText(tag_string) self.tags.setText(tag_string)
def lt_password_dialog(self): def lt_password_dialog(self):
return PasswordDialog(self, 'LibraryThing account', return PasswordDialog(self, 'LibraryThing account',
_('<p>Enter your username and password for <b>LibraryThing.com</b>. <br/>If you do not have one, you can <a href=\'http://www.librarything.com\'>register</a> for free!.</p>')) _('<p>Enter your username and password for <b>LibraryThing.com</b>. <br/>If you do not have one, you can <a href=\'http://www.librarything.com\'>register</a> for free!.</p>'))
def change_password(self): def change_password(self):
d = self.lt_password_dialog() d = self.lt_password_dialog()
d.exec_() d.exec_()
def fetch_cover(self): def fetch_cover(self):
isbn = qstring_to_unicode(self.isbn.text()) isbn = qstring_to_unicode(self.isbn.text())
if isbn: if isbn:
d = self.lt_password_dialog() d = self.lt_password_dialog()
if not d.username() or not d.password(): if not d.username() or not d.password():
d.exec_() d.exec_()
if d.result() != PasswordDialog.Accepted: if d.result() != PasswordDialog.Accepted:
@ -369,31 +370,31 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
self.pi.start(_('Downloading cover...')) self.pi.start(_('Downloading cover...'))
self._hangcheck.start(100) self._hangcheck.start(100)
else: else:
error_dialog(self, _('Cannot fetch cover'), error_dialog(self, _('Cannot fetch cover'),
_('You must specify the ISBN identifier for this book.')).exec_() _('You must specify the ISBN identifier for this book.')).exec_()
def hangcheck(self): def hangcheck(self):
if not (self.cover_fetcher.isFinished() or time.time()-self.cf_start_time > 150): if not (self.cover_fetcher.isFinished() or time.time()-self.cf_start_time > 150):
return return
self._hangcheck.stop() self._hangcheck.stop()
try: try:
if self.cover_fetcher.isRunning(): if self.cover_fetcher.isRunning():
self.cover_fetcher.terminate() self.cover_fetcher.terminate()
error_dialog(self, _('Cannot fetch cover'), error_dialog(self, _('Cannot fetch cover'),
_('<b>Could not fetch cover.</b><br/>')+ _('<b>Could not fetch cover.</b><br/>')+
_('The download timed out.')).exec_() _('The download timed out.')).exec_()
return return
if self.cover_fetcher.exception is not None: if self.cover_fetcher.exception is not None:
err = self.cover_fetcher.exception err = self.cover_fetcher.exception
error_dialog(self, _('Cannot fetch cover'), error_dialog(self, _('Cannot fetch cover'),
_('<b>Could not fetch cover.</b><br/>')+repr(err)).exec_() _('<b>Could not fetch cover.</b><br/>')+repr(err)).exec_()
return return
pix = QPixmap() pix = QPixmap()
pix.loadFromData(self.cover_fetcher.cover_data) pix.loadFromData(self.cover_fetcher.cover_data)
if pix.isNull(): if pix.isNull():
error_dialog(self.window, _('Bad cover'), error_dialog(self.window, _('Bad cover'),
_('The cover is not a valid picture')).exec_() _('The cover is not a valid picture')).exec_()
else: else:
self.cover.setPixmap(pix) self.cover.setPixmap(pix)
@ -403,16 +404,17 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
self.fetch_cover_button.setEnabled(True) self.fetch_cover_button.setEnabled(True)
self.unsetCursor() self.unsetCursor()
self.pi.stop() self.pi.stop()
def fetch_metadata(self): def fetch_metadata(self):
isbn = qstring_to_unicode(self.isbn.text()) isbn = qstring_to_unicode(self.isbn.text())
title = qstring_to_unicode(self.title.text()) title = qstring_to_unicode(self.title.text())
author = string_to_authors(unicode(self.authors.text()))[0] author = string_to_authors(unicode(self.authors.text()))[0]
publisher = qstring_to_unicode(self.publisher.currentText()) publisher = qstring_to_unicode(self.publisher.currentText())
if isbn or title or author or publisher: if isbn or title or author or publisher:
d = FetchMetadata(self, isbn, title, author, publisher, self.timeout) d = FetchMetadata(self, isbn, title, author, publisher, self.timeout)
d.exec_() with d:
d.exec_()
if d.result() == QDialog.Accepted: if d.result() == QDialog.Accepted:
book = d.selected_book() book = d.selected_book()
if book: if book:
@ -428,13 +430,13 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
prefix += '\n' prefix += '\n'
self.comments.setText(prefix + summ) self.comments.setText(prefix + summ)
else: else:
error_dialog(self, _('Cannot fetch metadata'), error_dialog(self, _('Cannot fetch metadata'),
_('You must specify at least one of ISBN, Title, ' _('You must specify at least one of ISBN, Title, '
'Authors or Publisher')) 'Authors or Publisher'))
def enable_series_index(self, *args): def enable_series_index(self, *args):
self.series_index.setEnabled(True) self.series_index.setEnabled(True)
def remove_unused_series(self): def remove_unused_series(self):
self.db.remove_unused_series() self.db.remove_unused_series()
idx = qstring_to_unicode(self.series.currentText()) idx = qstring_to_unicode(self.series.currentText())
@ -445,15 +447,15 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
if qstring_to_unicode(self.series.itemText(i)) == idx: if qstring_to_unicode(self.series.itemText(i)) == idx:
self.series.setCurrentIndex(i) self.series.setCurrentIndex(i)
break break
def accept(self): def accept(self):
if self.formats_changed: if self.formats_changed:
self.sync_formats() self.sync_formats()
title = qstring_to_unicode(self.title.text()) title = qstring_to_unicode(self.title.text())
self.db.set_title(self.id, title, notify=False) self.db.set_title(self.id, title, notify=False)
au = unicode(self.authors.text()) au = unicode(self.authors.text())
if au: if au:
self.db.set_authors(self.id, string_to_authors(au), notify=False) self.db.set_authors(self.id, string_to_authors(au), notify=False)
aus = qstring_to_unicode(self.author_sort.text()) aus = qstring_to_unicode(self.author_sort.text())
if aus: if aus:

View File

@ -5,7 +5,7 @@ import re, textwrap
DEPENDENCIES = [ DEPENDENCIES = [
#(Generic, version, gentoo, ubuntu, fedora) #(Generic, version, gentoo, ubuntu, fedora)
('python', '2.5', None, None, None), ('python', '2.6', None, None, None),
('setuptools', '0.6c5', 'setuptools', 'python-setuptools', 'python-setuptools-devel'), ('setuptools', '0.6c5', 'setuptools', 'python-setuptools', 'python-setuptools-devel'),
('Python Imaging Library', '1.1.6', 'imaging', 'python-imaging', 'python-imaging'), ('Python Imaging Library', '1.1.6', 'imaging', 'python-imaging', 'python-imaging'),
('libusb', '0.1.12', None, None, None), ('libusb', '0.1.12', None, None, None),
@ -20,10 +20,10 @@ DEPENDENCIES = [
('BeautifulSoup', '3.0.5', 'beautifulsoup', 'python-beautifulsoup', 'python-BeautifulSoup'), ('BeautifulSoup', '3.0.5', 'beautifulsoup', 'python-beautifulsoup', 'python-BeautifulSoup'),
('dnspython', '1.6.0', 'dnspython', 'dnspython', 'dnspython', 'dnspython'), ('dnspython', '1.6.0', 'dnspython', 'dnspython', 'dnspython', 'dnspython'),
] ]
class CoolDistro: class CoolDistro:
def __init__(self, name, title, prefix=''): def __init__(self, name, title, prefix=''):
self.title = title self.title = title
url = prefix + '/chrome/dl/images/%s_logo.png' url = prefix + '/chrome/dl/images/%s_logo.png'
@ -39,7 +39,7 @@ def get_linux_data(version='1.0.0'):
('foresight', 'Foresight 2.1'), ('foresight', 'Foresight 2.1'),
('ubuntu', 'Ubuntu Jaunty Jackalope'), ('ubuntu', 'Ubuntu Jaunty Jackalope'),
]: ]:
data['supported'].append(CoolDistro(name, title, data['supported'].append(CoolDistro(name, title,
prefix='http://calibre.kovidgoyal.net')) prefix='http://calibre.kovidgoyal.net'))
data['dependencies'] = DEPENDENCIES data['dependencies'] = DEPENDENCIES
return data return data
@ -54,51 +54,51 @@ if __name__ == '__main__':
return MarkupTemplate(raw).generate(**get_linux_data()).render('xhtml') return MarkupTemplate(raw).generate(**get_linux_data()).render('xhtml')
index.exposed = True index.exposed = True
t = Test() t = Test()
t.index() t.index()
cherrypy.quickstart(t) cherrypy.quickstart(t)
else: else:
from pkg_resources import resource_filename from pkg_resources import resource_filename
from trac.core import Component, implements from trac.core import Component, implements
from trac.web.chrome import INavigationContributor, ITemplateProvider, add_stylesheet from trac.web.chrome import INavigationContributor, ITemplateProvider, add_stylesheet
from trac.web.main import IRequestHandler from trac.web.main import IRequestHandler
from trac.util import Markup from trac.util import Markup
DOWNLOAD_DIR = '/var/www/calibre.kovidgoyal.net/htdocs/downloads' DOWNLOAD_DIR = '/var/www/calibre.kovidgoyal.net/htdocs/downloads'
MOBILEREAD = 'https://dev.mobileread.com/dist/kovid/calibre/' MOBILEREAD = 'https://dev.mobileread.com/dist/kovid/calibre/'
class OS(dict): class OS(dict):
"""Dictionary with a default value for unknown keys.""" """Dictionary with a default value for unknown keys."""
def __init__(self, dict): def __init__(self, dict):
self.update(dict) self.update(dict)
if not dict.has_key('img'): if not dict.has_key('img'):
self['img'] = self['name'] self['img'] = self['name']
class Download(Component): class Download(Component):
implements(INavigationContributor, IRequestHandler, ITemplateProvider) implements(INavigationContributor, IRequestHandler, ITemplateProvider)
request_pat = re.compile(r'\/download$|\/download_\S+') request_pat = re.compile(r'\/download$|\/download_\S+')
# INavigationContributor methods # INavigationContributor methods
def get_active_navigation_item(self, req): def get_active_navigation_item(self, req):
return 'download' return 'download'
def get_navigation_items(self, req): def get_navigation_items(self, req):
yield 'mainnav', 'download', Markup('<a href="/download">Get %s</a>'%(__appname__,)) yield 'mainnav', 'download', Markup('<a href="/download">Get %s</a>'%(__appname__,))
def get_templates_dirs(self): def get_templates_dirs(self):
return [resource_filename(__name__, 'templates')] return [resource_filename(__name__, 'templates')]
def get_htdocs_dirs(self): def get_htdocs_dirs(self):
return [('dl', resource_filename(__name__, 'htdocs'))] return [('dl', resource_filename(__name__, 'htdocs'))]
# IRequestHandler methods # IRequestHandler methods
def match_request(self, req): def match_request(self, req):
return self.__class__.request_pat.match(req.path_info) return self.__class__.request_pat.match(req.path_info)
def process_request(self, req): def process_request(self, req):
add_stylesheet(req, 'dl/css/download.css') add_stylesheet(req, 'dl/css/download.css')
if req.path_info == '/download': if req.path_info == '/download':
@ -115,29 +115,29 @@ else:
return self.osx(req) return self.osx(req)
elif os == 'linux': elif os == 'linux':
return self.linux(req) return self.linux(req)
def top_level(self, req): def top_level(self, req):
operating_systems = [ operating_systems = [
OS({'name' : 'windows', 'title' : 'Windows'}), OS({'name' : 'windows', 'title' : 'Windows'}),
OS({'name' : 'osx', 'title' : 'OS X'}), OS({'name' : 'osx', 'title' : 'OS X'}),
OS({'name' : 'linux', 'title' : 'Linux'}), OS({'name' : 'linux', 'title' : 'Linux'}),
] ]
data = dict(title='Get ' + __appname__, data = dict(title='Get ' + __appname__,
operating_systems=operating_systems, width=200, operating_systems=operating_systems, width=200,
font_size='xx-large', top_level=True) font_size='xx-large', top_level=True)
return 'download.html', data, None return 'download.html', data, None
def version_from_filename(self): def version_from_filename(self):
try: try:
return open(DOWNLOAD_DIR+'/latest_version', 'rb').read().strip() return open(DOWNLOAD_DIR+'/latest_version', 'rb').read().strip()
except: except:
return '0.0.0' return '0.0.0'
def windows(self, req): def windows(self, req):
version = self.version_from_filename() version = self.version_from_filename()
file = '%s-%s.exe'%(__appname__, version,) file = '%s-%s.exe'%(__appname__, version,)
data = dict(version = version, name='windows', data = dict(version = version, name='windows',
installer_name='Windows installer', installer_name='Windows installer',
title='Download %s for windows'%(__appname__), title='Download %s for windows'%(__appname__),
compatibility='%s works on Windows XP and Windows Vista.'%(__appname__,), compatibility='%s works on Windows XP and Windows Vista.'%(__appname__,),
path=MOBILEREAD+file, app=__appname__, path=MOBILEREAD+file, app=__appname__,
@ -146,7 +146,7 @@ else:
<p>If you are using the <b>SONY PRS-500</b> and %(appname)s does not detect your reader, read on:</p> <p>If you are using the <b>SONY PRS-500</b> and %(appname)s does not detect your reader, read on:</p>
<blockquote> <blockquote>
<p> <p>
If you are using 64-bit windows, you're out of luck. If you are using 64-bit windows, you're out of luck.
</p> </p>
<p> <p>
There may be a conflict with the USB driver from SONY. In windows, you cannot install two drivers There may be a conflict with the USB driver from SONY. In windows, you cannot install two drivers
@ -160,7 +160,7 @@ else:
</ul> </ul>
You can uninstall a driver by right clicking on it and selecting uninstall. You can uninstall a driver by right clicking on it and selecting uninstall.
</li> </li>
<li>Once the drivers have been uninstalled, find the file prs500.inf (it will be in the <li>Once the drivers have been uninstalled, find the file prs500.inf (it will be in the
driver folder in the folder in which you installed %(appname)s. Right click on it and driver folder in the folder in which you installed %(appname)s. Right click on it and
select Install.</li> select Install.</li>
</ol> </ol>
@ -168,12 +168,12 @@ else:
</blockquote> </blockquote>
'''%dict(appname=__appname__))) '''%dict(appname=__appname__)))
return 'binary.html', data, None return 'binary.html', data, None
def osx(self, req): def osx(self, req):
version = self.version_from_filename() version = self.version_from_filename()
file = 'calibre-%s.dmg'%(version,) file = 'calibre-%s.dmg'%(version,)
data = dict(version = version, name='osx', data = dict(version = version, name='osx',
installer_name='OS X universal dmg', installer_name='OS X universal dmg',
title='Download %s for OS X'%(__appname__), title='Download %s for OS X'%(__appname__),
compatibility='%s works on OS X Tiger and above.'%(__appname__,), compatibility='%s works on OS X Tiger and above.'%(__appname__,),
path=MOBILEREAD+file, app=__appname__, path=MOBILEREAD+file, app=__appname__,
@ -181,57 +181,57 @@ else:
u''' u'''
<ol> <ol>
<li>Before trying to use the command line tools, you must run the app at least once. This will ask you for you password and then setup the symbolic links for the command line tools.</li> <li>Before trying to use the command line tools, you must run the app at least once. This will ask you for you password and then setup the symbolic links for the command line tools.</li>
<li>The app cannot be run from within the dmg. You must drag it to a folder on your filesystem (The Desktop, Applications, wherever).</li> <li>The app cannot be run from within the dmg. You must drag it to a folder on your filesystem (The Desktop, Applications, wherever).</li>
<li>In order for localization of the user interface in your language, select your language in the preferences (by pressing u\2318+P) and select your language.</li> <li>In order for localization of the user interface in your language, select your language in the preferences (by pressing u\2318+P) and select your language.</li>
</ol> </ol>
''')) '''))
return 'binary.html', data, None return 'binary.html', data, None
def linux(self, req): def linux(self, req):
data = get_linux_data(version=self.version_from_filename()) data = get_linux_data(version=self.version_from_filename())
return 'linux.html', data, None return 'linux.html', data, None
LINUX_INSTALLER = textwrap.dedent(r''' LINUX_INSTALLER = textwrap.dedent(r'''
import sys, os, shutil, tarfile, subprocess, tempfile, urllib2, re, stat import sys, os, shutil, tarfile, subprocess, tempfile, urllib2, re, stat
MOBILEREAD='https://dev.mobileread.com/dist/kovid/calibre/' MOBILEREAD='https://dev.mobileread.com/dist/kovid/calibre/'
class TerminalController: class TerminalController:
BOL = '' #: Move the cursor to the beginning of the line BOL = '' #: Move the cursor to the beginning of the line
UP = '' #: Move the cursor up one line UP = '' #: Move the cursor up one line
DOWN = '' #: Move the cursor down one line DOWN = '' #: Move the cursor down one line
LEFT = '' #: Move the cursor left one char LEFT = '' #: Move the cursor left one char
RIGHT = '' #: Move the cursor right one char RIGHT = '' #: Move the cursor right one char
# Deletion: # Deletion:
CLEAR_SCREEN = '' #: Clear the screen and move to home position CLEAR_SCREEN = '' #: Clear the screen and move to home position
CLEAR_EOL = '' #: Clear to the end of the line. CLEAR_EOL = '' #: Clear to the end of the line.
CLEAR_BOL = '' #: Clear to the beginning of the line. CLEAR_BOL = '' #: Clear to the beginning of the line.
CLEAR_EOS = '' #: Clear to the end of the screen CLEAR_EOS = '' #: Clear to the end of the screen
# Output modes: # Output modes:
BOLD = '' #: Turn on bold mode BOLD = '' #: Turn on bold mode
BLINK = '' #: Turn on blink mode BLINK = '' #: Turn on blink mode
DIM = '' #: Turn on half-bright mode DIM = '' #: Turn on half-bright mode
REVERSE = '' #: Turn on reverse-video mode REVERSE = '' #: Turn on reverse-video mode
NORMAL = '' #: Turn off all modes NORMAL = '' #: Turn off all modes
# Cursor display: # Cursor display:
HIDE_CURSOR = '' #: Make the cursor invisible HIDE_CURSOR = '' #: Make the cursor invisible
SHOW_CURSOR = '' #: Make the cursor visible SHOW_CURSOR = '' #: Make the cursor visible
# Terminal size: # Terminal size:
COLS = None #: Width of the terminal (None for unknown) COLS = None #: Width of the terminal (None for unknown)
LINES = None #: Height of the terminal (None for unknown) LINES = None #: Height of the terminal (None for unknown)
# Foreground colors: # Foreground colors:
BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = '' BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = ''
# Background colors: # Background colors:
BG_BLACK = BG_BLUE = BG_GREEN = BG_CYAN = '' BG_BLACK = BG_BLUE = BG_GREEN = BG_CYAN = ''
BG_RED = BG_MAGENTA = BG_YELLOW = BG_WHITE = '' BG_RED = BG_MAGENTA = BG_YELLOW = BG_WHITE = ''
_STRING_CAPABILITIES = """ _STRING_CAPABILITIES = """
BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1 BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1
CLEAR_SCREEN=clear CLEAR_EOL=el CLEAR_BOL=el1 CLEAR_EOS=ed BOLD=bold CLEAR_SCREEN=clear CLEAR_EOL=el CLEAR_BOL=el1 CLEAR_EOS=ed BOLD=bold
@ -239,29 +239,29 @@ else:
HIDE_CURSOR=cinvis SHOW_CURSOR=cnorm""".split() HIDE_CURSOR=cinvis SHOW_CURSOR=cnorm""".split()
_COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split() _COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split()
_ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split() _ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split()
def __init__(self, term_stream=sys.stdout): def __init__(self, term_stream=sys.stdout):
# Curses isn't available on all platforms # Curses isn't available on all platforms
try: import curses try: import curses
except: return except: return
# If the stream isn't a tty, then assume it has no capabilities. # If the stream isn't a tty, then assume it has no capabilities.
if not hasattr(term_stream, 'isatty') or not term_stream.isatty(): return if not hasattr(term_stream, 'isatty') or not term_stream.isatty(): return
# Check the terminal type. If we fail, then assume that the # Check the terminal type. If we fail, then assume that the
# terminal has no capabilities. # terminal has no capabilities.
try: curses.setupterm() try: curses.setupterm()
except: return except: return
# Look up numeric capabilities. # Look up numeric capabilities.
self.COLS = curses.tigetnum('cols') self.COLS = curses.tigetnum('cols')
self.LINES = curses.tigetnum('lines') self.LINES = curses.tigetnum('lines')
# Look up string capabilities. # Look up string capabilities.
for capability in self._STRING_CAPABILITIES: for capability in self._STRING_CAPABILITIES:
(attrib, cap_name) = capability.split('=') (attrib, cap_name) = capability.split('=')
setattr(self, attrib, self._tigetstr(cap_name) or '') setattr(self, attrib, self._tigetstr(cap_name) or '')
# Colors # Colors
set_fg = self._tigetstr('setf') set_fg = self._tigetstr('setf')
if set_fg: if set_fg:
@ -279,7 +279,7 @@ else:
if set_bg_ansi: if set_bg_ansi:
for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS): for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
setattr(self, 'BG_'+color, curses.tparm(set_bg_ansi, i) or '') setattr(self, 'BG_'+color, curses.tparm(set_bg_ansi, i) or '')
def _tigetstr(self, cap_name): def _tigetstr(self, cap_name):
# String capabilities can include "delays" of the form "$<2>". # String capabilities can include "delays" of the form "$<2>".
# For any modern terminal, we should be able to just ignore # For any modern terminal, we should be able to just ignore
@ -287,19 +287,19 @@ else:
import curses import curses
cap = curses.tigetstr(cap_name) or '' cap = curses.tigetstr(cap_name) or ''
return re.sub(r'\$<\d+>[/*]?', '', cap) return re.sub(r'\$<\d+>[/*]?', '', cap)
def render(self, template): def render(self, template):
return re.sub(r'\$\$|\${\w+}', self._render_sub, template) return re.sub(r'\$\$|\${\w+}', self._render_sub, template)
def _render_sub(self, match): def _render_sub(self, match):
s = match.group() s = match.group()
if s == '$$': return s if s == '$$': return s
else: return getattr(self, s[2:-1]) else: return getattr(self, s[2:-1])
class ProgressBar: class ProgressBar:
BAR = '%3d%% ${GREEN}[${BOLD}%s%s${NORMAL}${GREEN}]${NORMAL}\n' BAR = '%3d%% ${GREEN}[${BOLD}%s%s${NORMAL}${GREEN}]${NORMAL}\n'
HEADER = '${BOLD}${CYAN}%s${NORMAL}\n\n' HEADER = '${BOLD}${CYAN}%s${NORMAL}\n\n'
def __init__(self, term, header): def __init__(self, term, header):
self.term = term self.term = term
if not (self.term.CLEAR_EOL and self.term.UP and self.term.BOL): if not (self.term.CLEAR_EOL and self.term.UP and self.term.BOL):
@ -309,7 +309,7 @@ else:
self.bar = term.render(self.BAR) self.bar = term.render(self.BAR)
self.header = self.term.render(self.HEADER % header.center(self.width)) self.header = self.term.render(self.HEADER % header.center(self.width))
self.cleared = 1 #: true if we haven't drawn the bar yet. self.cleared = 1 #: true if we haven't drawn the bar yet.
def update(self, percent, message=''): def update(self, percent, message=''):
if isinstance(message, unicode): if isinstance(message, unicode):
message = message.encode('utf-8', 'ignore') message = message.encode('utf-8', 'ignore')
@ -323,14 +323,14 @@ else:
(self.bar % (100*percent, '='*n, '-'*(self.width-10-n))) + (self.bar % (100*percent, '='*n, '-'*(self.width-10-n))) +
self.term.CLEAR_EOL + msg) self.term.CLEAR_EOL + msg)
sys.stdout.flush() sys.stdout.flush()
def clear(self): def clear(self):
if not self.cleared: if not self.cleared:
sys.stdout.write(self.term.BOL + self.term.CLEAR_EOL + sys.stdout.write(self.term.BOL + self.term.CLEAR_EOL +
self.term.UP + self.term.CLEAR_EOL + self.term.UP + self.term.CLEAR_EOL +
self.term.UP + self.term.CLEAR_EOL) self.term.UP + self.term.CLEAR_EOL)
self.cleared = 1 self.cleared = 1
def download_tarball(): def download_tarball():
try: try:
pb = ProgressBar(TerminalController(sys.stdout), 'Downloading calibre...') pb = ProgressBar(TerminalController(sys.stdout), 'Downloading calibre...')
@ -355,14 +355,14 @@ else:
print '%d%%, '%int(percent*100), print '%d%%, '%int(percent*100),
f.seek(0) f.seek(0)
return f return f
def extract_tarball(tar, destdir): def extract_tarball(tar, destdir):
print 'Extracting application files...' print 'Extracting application files...'
if hasattr(tar, 'read'): if hasattr(tar, 'read'):
subprocess.check_call(['tar', 'xjf', tar.name, '-C', destdir]) subprocess.check_call(['tar', 'xjf', tar.name, '-C', destdir])
else: else:
subprocess.check_call(['tar', 'xjf', tar, '-C', destdir]) subprocess.check_call(['tar', 'xjf', tar, '-C', destdir])
def main(): def main():
defdir = '/opt/calibre' defdir = '/opt/calibre'
destdir = raw_input('Enter the installation directory for calibre (Its contents will be deleted!)[%s]: '%defdir).strip() destdir = raw_input('Enter the installation directory for calibre (Its contents will be deleted!)[%s]: '%defdir).strip()
@ -372,12 +372,12 @@ else:
if os.path.exists(destdir): if os.path.exists(destdir):
shutil.rmtree(destdir) shutil.rmtree(destdir)
os.makedirs(destdir) os.makedirs(destdir)
f = download_tarball() f = download_tarball()
print 'Extracting files to %s ...'%destdir print 'Extracting files to %s ...'%destdir
extract_tarball(f, destdir) extract_tarball(f, destdir)
pi = os.path.join(destdir, 'calibre_postinstall') pi = os.path.join(destdir, 'calibre_postinstall')
subprocess.call(pi, shell=True) subprocess.call(pi, shell=True)
return 0 return 0
''') ''')

View File

@ -32,7 +32,10 @@ def create_mail(from_, to, subject, text=None, attachment_data=None,
if attachment_data is not None: if attachment_data is not None:
from email.mime.base import MIMEBase from email.mime.base import MIMEBase
assert attachment_data and attachment_name assert attachment_data and attachment_name
maintype, subtype = attachment_type.split('/', 1) try:
maintype, subtype = attachment_type.split('/', 1)
except AttributeError:
maintype, subtype = 'application', 'octet-stream'
msg = MIMEBase(maintype, subtype) msg = MIMEBase(maintype, subtype)
msg.set_payload(attachment_data) msg.set_payload(attachment_data)
encoders.encode_base64(msg) encoders.encode_base64(msg)
@ -50,9 +53,11 @@ def get_mx(host):
def sendmail_direct(from_, to, msg, timeout, localhost, verbose): def sendmail_direct(from_, to, msg, timeout, localhost, verbose):
import smtplib import smtplib
hosts = get_mx(to.split('@')[-1].strip())
if 'darwin' in sys.platform:
timeout=None # Non blocking sockets dont work on OS X
s = smtplib.SMTP(timeout=timeout, local_hostname=localhost) s = smtplib.SMTP(timeout=timeout, local_hostname=localhost)
s.set_debuglevel(verbose) s.set_debuglevel(verbose)
hosts = get_mx(to.split('@')[-1].strip())
if not hosts: if not hosts:
raise ValueError('No mail server found for address: %s'%to) raise ValueError('No mail server found for address: %s'%to)
last_error = last_traceback = None last_error = last_traceback = None
@ -76,6 +81,8 @@ def sendmail(msg, from_, to, localhost=None, verbose=0, timeout=30,
return sendmail_direct(from_, x, msg, timeout, localhost, verbose) return sendmail_direct(from_, x, msg, timeout, localhost, verbose)
import smtplib import smtplib
cls = smtplib.SMTP if encryption == 'TLS' else smtplib.SMTP_SSL cls = smtplib.SMTP if encryption == 'TLS' else smtplib.SMTP_SSL
if 'darwin' in sys.platform:
timeout = None # Non-blocking sockets in OS X don't work
s = cls(timeout=timeout, local_hostname=localhost) s = cls(timeout=timeout, local_hostname=localhost)
s.set_debuglevel(verbose) s.set_debuglevel(verbose)
if port < 0: if port < 0:

File diff suppressed because it is too large Load Diff

View File

@ -651,11 +651,6 @@ class upload_to_pypi(OptionlessCommand):
def run(self): def run(self):
check_call('python setup.py register'.split()) check_call('python setup.py register'.split())
check_call('rm -f dist/*', shell=True) check_call('rm -f dist/*', shell=True)
check_call('sudo rm -rf build src/calibre/plugins/*', shell=True)
os.mkdir('build')
check_call('python2.5 setup.py build_ext bdist_egg --exclude-source-files upload'.split())
check_call('sudo rm -rf build src/calibre/plugins/*', shell=True)
os.mkdir('build')
check_call('python setup.py build_ext bdist_egg --exclude-source-files upload'.split()) check_call('python setup.py build_ext bdist_egg --exclude-source-files upload'.split())
check_call('python setup.py sdist upload'.split()) check_call('python setup.py sdist upload'.split())