mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
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:
parent
52d968838c
commit
9ca023e9a7
@ -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:]
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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__':
|
||||||
|
@ -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,
|
||||||
|
@ -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()
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
''')
|
''')
|
||||||
|
@ -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
@ -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())
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user