mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Update to 0.4.72 from trunk
This commit is contained in:
commit
cd1340871c
6
Makefile
6
Makefile
@ -1,8 +1,10 @@
|
|||||||
PYTHON = python
|
PYTHON = python
|
||||||
|
|
||||||
all : plugins pictureflow gui2 translations resources
|
all : plugins gui2 translations resources
|
||||||
|
|
||||||
plugins:
|
plugins : src/calibre/plugins pictureflow
|
||||||
|
|
||||||
|
src/calibre/plugins:
|
||||||
mkdir -p src/calibre/plugins
|
mkdir -p src/calibre/plugins
|
||||||
|
|
||||||
clean :
|
clean :
|
||||||
|
153
linux_installer.py
Normal file
153
linux_installer.py
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
'''
|
||||||
|
Create linux binary.
|
||||||
|
'''
|
||||||
|
import glob, sys, subprocess, tarfile, os, re
|
||||||
|
HOME = '/home/kovid'
|
||||||
|
PYINSTALLER = os.path.expanduser('~/build/pyinstaller')
|
||||||
|
CALIBREPREFIX = '___'
|
||||||
|
CLIT = '/usr/bin/clit'
|
||||||
|
PDFTOHTML = '/usr/bin/pdftohtml'
|
||||||
|
LIBUNRAR = '/usr/lib/libunrar.so'
|
||||||
|
QTDIR = '/usr/lib/qt4'
|
||||||
|
QTDLLS = ('QtCore', 'QtGui', 'QtNetwork', 'QtSvg', 'QtXml')
|
||||||
|
EXTRAS = ('/usr/lib/python2.5/site-packages/PIL', os.path.expanduser('~/ipython/IPython'))
|
||||||
|
|
||||||
|
|
||||||
|
CALIBRESRC = os.path.join(CALIBREPREFIX, 'src')
|
||||||
|
CALIBREPLUGINS = os.path.join(CALIBRESRC, 'calibre', 'plugins')
|
||||||
|
|
||||||
|
sys.path.insert(0, CALIBRESRC)
|
||||||
|
from calibre import __version__
|
||||||
|
|
||||||
|
def run_pyinstaller(args=sys.argv):
|
||||||
|
subprocess.check_call(('/usr/bin/sudo', 'chown', '-R', 'kovid:users', glob.glob('/usr/lib/python*/site-packages/')[-1]))
|
||||||
|
subprocess.check_call('rm -rf %(py)s/dist/* %(py)s/build/*'%dict(py=PYINSTALLER), shell=True)
|
||||||
|
subprocess.check_call('make plugins', shell=True)
|
||||||
|
cp = HOME+'/build/'+os.path.basename(os.getcwd())
|
||||||
|
spec = open(os.path.join(PYINSTALLER, 'calibre', 'calibre.spec'), 'wb')
|
||||||
|
raw = re.sub(r'CALIBREPREFIX\s+=\s+\'___\'', 'CALIBREPREFIX = '+repr(cp),
|
||||||
|
open(__file__).read())
|
||||||
|
spec.write(raw)
|
||||||
|
spec.close()
|
||||||
|
os.chdir(PYINSTALLER)
|
||||||
|
subprocess.check_call('python -OO Build.py calibre/calibre.spec', shell=True)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__' and 'linux_installer.py' in __file__:
|
||||||
|
sys.exit(run_pyinstaller())
|
||||||
|
|
||||||
|
|
||||||
|
loader = os.path.join(os.path.expanduser('~/temp'), 'calibre_installer_loader.py')
|
||||||
|
if not os.path.exists(loader):
|
||||||
|
open(loader, 'wb').write('''
|
||||||
|
import sys, os
|
||||||
|
sys.frozen_path = os.getcwd()
|
||||||
|
os.chdir(os.environ.get("ORIGWD", "."))
|
||||||
|
sys.path.insert(0, os.path.join(sys.frozen_path, "library.pyz"))
|
||||||
|
sys.path.insert(0, sys.frozen_path)
|
||||||
|
from PyQt4.QtCore import QCoreApplication
|
||||||
|
QCoreApplication.setLibraryPaths([sys.frozen_path, os.path.join(sys.frozen_path, "plugins")])
|
||||||
|
''')
|
||||||
|
excludes = ['gtk._gtk', 'gtk.glade', 'qt', 'matplotlib.nxutils', 'matplotlib._cntr',
|
||||||
|
'matplotlib.ttconv', 'matplotlib._image', 'matplotlib.ft2font',
|
||||||
|
'matplotlib._transforms', 'matplotlib._agg', 'matplotlib.backends._backend_agg',
|
||||||
|
'matplotlib.axes', 'matplotlib', 'matplotlib.pyparsing',
|
||||||
|
'TKinter', 'atk', 'gobject._gobject', 'pango', 'PIL', 'Image', 'IPython']
|
||||||
|
temp = ['keyword', 'codeop']
|
||||||
|
|
||||||
|
recipes = ['calibre', 'web', 'feeds', 'recipes']
|
||||||
|
prefix = '.'.join(recipes)+'.'
|
||||||
|
for f in glob.glob(os.path.join(CALIBRESRC, *(recipes+['*.py']))):
|
||||||
|
temp.append(prefix + os.path.basename(f).partition('.')[0])
|
||||||
|
hook = os.path.expanduser('~/temp/hook-calibre.py')
|
||||||
|
f = open(hook, 'wb')
|
||||||
|
hook_script = 'hiddenimports = %s'%repr(temp)
|
||||||
|
f.write(hook_script)
|
||||||
|
|
||||||
|
sys.path.insert(0, CALIBRESRC)
|
||||||
|
from calibre.linux import entry_points
|
||||||
|
|
||||||
|
executables, scripts = ['calibre_postinstall', 'parallel'], \
|
||||||
|
[os.path.join(CALIBRESRC, 'calibre', 'linux.py'), os.path.join(CALIBRESRC, 'calibre', 'parallel.py')]
|
||||||
|
|
||||||
|
for entry in entry_points['console_scripts'] + entry_points['gui_scripts']:
|
||||||
|
fields = entry.split('=')
|
||||||
|
executables.append(fields[0].strip())
|
||||||
|
scripts.append(os.path.join(CALIBRESRC, *map(lambda x: x.strip(), fields[1].split(':')[0].split('.')))+'.py')
|
||||||
|
|
||||||
|
recipes = Analysis(glob.glob(os.path.join(CALIBRESRC, 'calibre', 'web', 'feeds', 'recipes', '*.py')),
|
||||||
|
pathex=[CALIBRESRC], hookspath=[os.path.dirname(hook)], excludes=excludes)
|
||||||
|
analyses = [Analysis([os.path.join(HOMEPATH,'support/_mountzlib.py'), os.path.join(HOMEPATH,'support/useUnicode.py'), loader, script],
|
||||||
|
pathex=[PYINSTALLER, CALIBRESRC, CALIBREPLUGINS], excludes=excludes) for script in scripts]
|
||||||
|
|
||||||
|
pyz = TOC()
|
||||||
|
binaries = TOC()
|
||||||
|
|
||||||
|
for a in analyses:
|
||||||
|
pyz = a.pure + pyz
|
||||||
|
binaries = a.binaries + binaries
|
||||||
|
pyz = PYZ(pyz + recipes.pure, name='library.pyz')
|
||||||
|
|
||||||
|
built_executables = []
|
||||||
|
for script, exe, a in zip(scripts, executables, analyses):
|
||||||
|
built_executables.append(EXE(PYZ(TOC()),
|
||||||
|
a.scripts+[('O','','OPTION'),],
|
||||||
|
exclude_binaries=1,
|
||||||
|
name=os.path.join('buildcalibre', exe),
|
||||||
|
debug=False,
|
||||||
|
strip=True,
|
||||||
|
upx=False,
|
||||||
|
excludes=excludes,
|
||||||
|
console=1))
|
||||||
|
|
||||||
|
print 'Adding plugins...'
|
||||||
|
for f in glob.glob(os.path.join(CALIBREPLUGINS, '*.so')):
|
||||||
|
binaries += [(os.path.basename(f), f, 'BINARY')]
|
||||||
|
|
||||||
|
print 'Adding external programs...'
|
||||||
|
binaries += [('clit', CLIT, 'BINARY'), ('pdftohtml', PDFTOHTML, 'BINARY'),
|
||||||
|
('libunrar.so', LIBUNRAR, 'BINARY')]
|
||||||
|
qt = []
|
||||||
|
for dll in QTDLLS:
|
||||||
|
path = os.path.join(QTDIR, 'lib'+dll+'.so.4')
|
||||||
|
qt.append((os.path.basename(path), path, 'BINARY'))
|
||||||
|
binaries += qt
|
||||||
|
|
||||||
|
plugins = []
|
||||||
|
plugdir = os.path.join(QTDIR, 'plugins')
|
||||||
|
for dirpath, dirnames, filenames in os.walk(plugdir):
|
||||||
|
for f in filenames:
|
||||||
|
if not f.endswith('.so') or 'designer' in dirpath or 'codcs' in dirpath or 'sqldrivers' in dirpath : continue
|
||||||
|
f = os.path.join(dirpath, f)
|
||||||
|
plugins.append(('plugins/'+f.replace(plugdir, ''), f, 'BINARY'))
|
||||||
|
binaries += plugins
|
||||||
|
|
||||||
|
manifest = '/tmp/manifest'
|
||||||
|
open(manifest, 'wb').write('\\n'.join(executables))
|
||||||
|
version = '/tmp/version'
|
||||||
|
open(version, 'wb').write(__version__)
|
||||||
|
coll = COLLECT(binaries, pyz,
|
||||||
|
[('manifest', manifest, 'DATA'), ('version', version, 'DATA')],
|
||||||
|
*built_executables,
|
||||||
|
**dict(strip=True,
|
||||||
|
upx=False,
|
||||||
|
excludes=excludes,
|
||||||
|
name='dist'))
|
||||||
|
|
||||||
|
os.chdir(os.path.join(HOMEPATH, 'calibre', 'dist'))
|
||||||
|
for folder in EXTRAS:
|
||||||
|
subprocess.check_call('cp -rf %s .'%folder, shell=True)
|
||||||
|
|
||||||
|
print 'Building tarball...'
|
||||||
|
tbz2 = 'calibre-%s-i686.tar.bz2'%__version__
|
||||||
|
tf = tarfile.open(os.path.join('/tmp', tbz2), 'w:bz2')
|
||||||
|
|
||||||
|
for f in os.listdir('.'):
|
||||||
|
tf.add(f)
|
@ -99,8 +99,7 @@ _check_symlinks_prescript()
|
|||||||
includes=list(self.includes) + main_modules['console'],
|
includes=list(self.includes) + main_modules['console'],
|
||||||
packages=self.packages,
|
packages=self.packages,
|
||||||
excludes=self.excludes,
|
excludes=self.excludes,
|
||||||
debug=debug,
|
debug=debug)
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def makedmg(cls, d, volname,
|
def makedmg(cls, d, volname,
|
||||||
@ -249,6 +248,11 @@ _check_symlinks_prescript()
|
|||||||
else:
|
else:
|
||||||
os.link(src, os.path.join(module_dir, dest))
|
os.link(src, os.path.join(module_dir, dest))
|
||||||
print
|
print
|
||||||
|
print 'Adding IPython'
|
||||||
|
dst = os.path.join(resource_dir, 'lib', 'python2.5', 'IPython')
|
||||||
|
if os.path.exists(dst): shutil.rmtree(dst)
|
||||||
|
shutil.copytree(os.path.expanduser('~/build/ipython/IPython'), dst)
|
||||||
|
print
|
||||||
print 'Installing prescipt'
|
print 'Installing prescipt'
|
||||||
sf = [os.path.basename(s) for s in all_names]
|
sf = [os.path.basename(s) for s in all_names]
|
||||||
cs = BuildAPP.CHECK_SYMLINKS_PRESCRIPT % dict(dest_path=repr('/usr/bin'),
|
cs = BuildAPP.CHECK_SYMLINKS_PRESCRIPT % dict(dest_path=repr('/usr/bin'),
|
||||||
@ -277,13 +281,7 @@ sys.frameworks_dir = os.path.join(os.path.dirname(os.environ['RESOURCEPATH']), '
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# auto = '--auto' in sys.argv
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
|
||||||
# if auto:
|
|
||||||
# sys.argv.remove('--auto')
|
|
||||||
# if auto and not os.path.exists('dist/auto'):
|
|
||||||
# print '%s does not exist'%os.path.abspath('dist/auto')
|
|
||||||
# return 1
|
|
||||||
#
|
|
||||||
sys.argv[1:2] = ['py2app']
|
sys.argv[1:2] = ['py2app']
|
||||||
setup(
|
setup(
|
||||||
name = APPNAME,
|
name = APPNAME,
|
||||||
@ -300,12 +298,12 @@ def main():
|
|||||||
'PyQt4.QtSvg',
|
'PyQt4.QtSvg',
|
||||||
'mechanize', 'ClientForm', 'usbobserver',
|
'mechanize', 'ClientForm', 'usbobserver',
|
||||||
'genshi', 'calibre.web.feeds.recipes.*',
|
'genshi', 'calibre.web.feeds.recipes.*',
|
||||||
'IPython.Extensions.*', 'pydoc'],
|
'keyword', 'codeop', 'pydoc'],
|
||||||
'packages' : ['PIL', 'Authorization', 'rtf2xml', 'lxml'],
|
'packages' : ['PIL', 'Authorization', 'rtf2xml', 'lxml'],
|
||||||
'excludes' : [],
|
'excludes' : ['IPython'],
|
||||||
'plist' : { 'CFBundleGetInfoString' : '''calibre, an E-book management application.'''
|
'plist' : { 'CFBundleGetInfoString' : '''calibre, an E-book management application.'''
|
||||||
''' Visit http://calibre.kovidgoyal.net for details.''',
|
''' Visit http://calibre.kovidgoyal.net for details.''',
|
||||||
'CFBundleIdentifier':'net.kovidgoyal.librs500',
|
'CFBundleIdentifier':'net.kovidgoyal.calibre',
|
||||||
'CFBundleShortVersionString':VERSION,
|
'CFBundleShortVersionString':VERSION,
|
||||||
'CFBundleVersion':APPNAME + ' ' + VERSION,
|
'CFBundleVersion':APPNAME + ' ' + VERSION,
|
||||||
'LSMinimumSystemVersion':'10.4.3',
|
'LSMinimumSystemVersion':'10.4.3',
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
''' E-book management software'''
|
''' E-book management software'''
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
__version__ = '0.4.70'
|
__version__ = '0.4.72'
|
||||||
__docformat__ = "epytext"
|
__docformat__ = "epytext"
|
||||||
__author__ = "Kovid Goyal <kovid at kovidgoyal.net>"
|
__author__ = "Kovid Goyal <kovid at kovidgoyal.net>"
|
||||||
__appname__ = 'calibre'
|
__appname__ = 'calibre'
|
||||||
@ -447,14 +447,13 @@ class Settings(QSettings):
|
|||||||
self.setValue(str(key), QVariant(QByteArray(val)))
|
self.setValue(str(key), QVariant(QByteArray(val)))
|
||||||
|
|
||||||
_settings = Settings()
|
_settings = Settings()
|
||||||
|
|
||||||
if not _settings.get('rationalized'):
|
if not _settings.get('rationalized'):
|
||||||
__settings = Settings(name='calibre')
|
__settings = Settings(name='calibre')
|
||||||
dbpath = os.path.join(os.path.expanduser('~'), 'library1.db').decode(sys.getfilesystemencoding())
|
dbpath = os.path.join(os.path.expanduser('~'), 'library1.db').decode(sys.getfilesystemencoding())
|
||||||
dbpath = unicode(__settings.value('database path',
|
dbpath = unicode(__settings.value('database path',
|
||||||
QVariant(QString.fromUtf8(dbpath.encode('utf-8')))).toString())
|
QVariant(QString.fromUtf8(dbpath.encode('utf-8')))).toString())
|
||||||
cmdline = __settings.value('LRF conversion defaults', QVariant(QByteArray(''))).toByteArray().data()
|
cmdline = __settings.value('LRF conversion defaults', QVariant(QByteArray(''))).toByteArray().data()
|
||||||
|
|
||||||
|
|
||||||
_settings.set('database path', dbpath)
|
_settings.set('database path', dbpath)
|
||||||
if cmdline:
|
if cmdline:
|
||||||
cmdline = cPickle.loads(cmdline)
|
cmdline = cPickle.loads(cmdline)
|
||||||
@ -464,6 +463,7 @@ if not _settings.get('rationalized'):
|
|||||||
os.unlink(unicode(__settings.fileName()))
|
os.unlink(unicode(__settings.fileName()))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
_settings.set('database path', dbpath)
|
||||||
|
|
||||||
_spat = re.compile(r'^the\s+|^a\s+|^an\s+', re.IGNORECASE)
|
_spat = re.compile(r'^the\s+|^a\s+|^an\s+', re.IGNORECASE)
|
||||||
def english_sort(x, y):
|
def english_sort(x, y):
|
||||||
|
@ -275,10 +275,10 @@ class PRS505(Device):
|
|||||||
if not iswindows:
|
if not iswindows:
|
||||||
if self._main_prefix is not None:
|
if self._main_prefix is not None:
|
||||||
stats = os.statvfs(self._main_prefix)
|
stats = os.statvfs(self._main_prefix)
|
||||||
msz = stats.f_bsize * stats.f_bavail
|
msz = stats.f_frsize * stats.f_bavail
|
||||||
if self._card_prefix is not None:
|
if self._card_prefix is not None:
|
||||||
stats = os.statvfs(self._card_prefix)
|
stats = os.statvfs(self._card_prefix)
|
||||||
csz = stats.f_bsize * stats.f_bavail
|
csz = stats.f_frsize * stats.f_bavail
|
||||||
else:
|
else:
|
||||||
msz = self._windows_space(self._main_prefix)[1]
|
msz = self._windows_space(self._main_prefix)[1]
|
||||||
csz = self._windows_space(self._card_prefix)[1]
|
csz = self._windows_space(self._card_prefix)[1]
|
||||||
|
@ -12,7 +12,7 @@ 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
|
from calibre.gui2 import error_dialog, NONE, info_dialog
|
||||||
from calibre.ebooks.metadata.isbndb import create_books, option_parser
|
from calibre.ebooks.metadata.isbndb import create_books, option_parser, ISBNDBError
|
||||||
from calibre import Settings
|
from calibre import Settings
|
||||||
|
|
||||||
class Matches(QAbstractTableModel):
|
class Matches(QAbstractTableModel):
|
||||||
@ -88,8 +88,7 @@ class FetchMetadata(QDialog, Ui_FetchMetadata):
|
|||||||
self.connect(self.matches, SIGNAL('activated(QModelIndex)'), self.chosen)
|
self.connect(self.matches, SIGNAL('activated(QModelIndex)'), self.chosen)
|
||||||
key = str(self.key.text())
|
key = str(self.key.text())
|
||||||
if key:
|
if key:
|
||||||
QTimer.singleShot(100, self.fetch.click)
|
QTimer.singleShot(100, self.fetch_metadata)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def show_summary(self, current, previous):
|
def show_summary(self, current, previous):
|
||||||
@ -106,7 +105,7 @@ class FetchMetadata(QDialog, Ui_FetchMetadata):
|
|||||||
_('You must specify a valid access key for isbndb.com'))
|
_('You must specify a valid access key for isbndb.com'))
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
Settings().set('isbndb.com key', str(self.key.text()))
|
Settings().set('isbndb.com key', key)
|
||||||
|
|
||||||
args = ['isbndb']
|
args = ['isbndb']
|
||||||
if self.isbn:
|
if self.isbn:
|
||||||
@ -121,36 +120,41 @@ class FetchMetadata(QDialog, Ui_FetchMetadata):
|
|||||||
self.fetch.setEnabled(False)
|
self.fetch.setEnabled(False)
|
||||||
self.setCursor(Qt.WaitCursor)
|
self.setCursor(Qt.WaitCursor)
|
||||||
QCoreApplication.instance().processEvents()
|
QCoreApplication.instance().processEvents()
|
||||||
|
try:
|
||||||
args.append(key)
|
args.append(key)
|
||||||
parser = option_parser()
|
parser = option_parser()
|
||||||
opts, args = parser.parse_args(args)
|
opts, args = parser.parse_args(args)
|
||||||
|
|
||||||
self.logger = logging.getLogger('Job #'+str(id))
|
self.logger = logging.getLogger('Job #'+str(id))
|
||||||
self.logger.setLevel(logging.DEBUG)
|
self.logger.setLevel(logging.DEBUG)
|
||||||
self.log_dest = cStringIO.StringIO()
|
self.log_dest = cStringIO.StringIO()
|
||||||
handler = logging.StreamHandler(self.log_dest)
|
handler = logging.StreamHandler(self.log_dest)
|
||||||
handler.setLevel(logging.DEBUG)
|
handler.setLevel(logging.DEBUG)
|
||||||
handler.setFormatter(logging.Formatter('[%(levelname)s] %(filename)s:%(lineno)s: %(message)s'))
|
handler.setFormatter(logging.Formatter('[%(levelname)s] %(filename)s:%(lineno)s: %(message)s'))
|
||||||
self.logger.addHandler(handler)
|
self.logger.addHandler(handler)
|
||||||
|
|
||||||
books = create_books(opts, args, self.logger, self.timeout)
|
try:
|
||||||
|
books = create_books(opts, args, self.logger, self.timeout)
|
||||||
self.model = Matches(books)
|
except ISBNDBError, err:
|
||||||
if self.model.rowCount() < 1:
|
error_dialog(self, _('Error fetching metadata'), str(err)).exec_()
|
||||||
info_dialog(self, _('No metadata found'), _('No metadata found, try adjusting the title and author or the ISBN key.')).exec_()
|
return
|
||||||
self.reject()
|
|
||||||
|
self.model = Matches(books)
|
||||||
self.matches.setModel(self.model)
|
if self.model.rowCount() < 1:
|
||||||
QObject.connect(self.matches.selectionModel(), SIGNAL('currentRowChanged(QModelIndex, QModelIndex)'),
|
info_dialog(self, _('No metadata found'), _('No metadata found, try adjusting the title and author or the ISBN key.')).exec_()
|
||||||
self.show_summary)
|
self.reject()
|
||||||
self.model.reset()
|
|
||||||
self.matches.selectionModel().select(self.model.index(0, 0),
|
self.matches.setModel(self.model)
|
||||||
QItemSelectionModel.Select | QItemSelectionModel.Rows)
|
QObject.connect(self.matches.selectionModel(), SIGNAL('currentRowChanged(QModelIndex, QModelIndex)'),
|
||||||
self.matches.setCurrentIndex(self.model.index(0, 0))
|
self.show_summary)
|
||||||
self.fetch.setEnabled(True)
|
self.model.reset()
|
||||||
self.unsetCursor()
|
self.matches.selectionModel().select(self.model.index(0, 0),
|
||||||
self.matches.resizeColumnsToContents()
|
QItemSelectionModel.Select | QItemSelectionModel.Rows)
|
||||||
|
self.matches.setCurrentIndex(self.model.index(0, 0))
|
||||||
|
finally:
|
||||||
|
self.fetch.setEnabled(True)
|
||||||
|
self.unsetCursor()
|
||||||
|
self.matches.resizeColumnsToContents()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label_2" >
|
<widget class="QLabel" name="label_2" >
|
||||||
<property name="text" >
|
<property name="text" >
|
||||||
<string>&Access Key;</string>
|
<string>&Access Key:</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="buddy" >
|
<property name="buddy" >
|
||||||
<cstring>key</cstring>
|
<cstring>key</cstring>
|
||||||
|
@ -6,7 +6,7 @@ add/remove formats
|
|||||||
'''
|
'''
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from PyQt4.QtCore import SIGNAL, QObject, QCoreApplication, Qt, QVariant
|
from PyQt4.QtCore import SIGNAL, QObject, QCoreApplication, Qt
|
||||||
from PyQt4.QtGui import QPixmap, QListWidgetItem, QErrorMessage, QDialog
|
from PyQt4.QtGui import QPixmap, QListWidgetItem, QErrorMessage, QDialog
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,8 +8,8 @@ from math import cos, sin, pi
|
|||||||
from itertools import repeat
|
from itertools import repeat
|
||||||
from PyQt4.QtGui import QTableView, QProgressDialog, QAbstractItemView, QColor, \
|
from PyQt4.QtGui import QTableView, QProgressDialog, QAbstractItemView, QColor, \
|
||||||
QItemDelegate, QPainterPath, QLinearGradient, QBrush, \
|
QItemDelegate, QPainterPath, QLinearGradient, QBrush, \
|
||||||
QPen, QStyle, QPainter, QLineEdit, QApplication, \
|
QPen, QStyle, QPainter, QLineEdit, \
|
||||||
QPalette, QImage
|
QPalette, QImage, QApplication
|
||||||
from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, QString, \
|
from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, QString, \
|
||||||
QCoreApplication, SIGNAL, QObject, QSize, QModelIndex
|
QCoreApplication, SIGNAL, QObject, QSize, QModelIndex
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ class LibraryDelegate(QItemDelegate):
|
|||||||
COLOR = QColor("blue")
|
COLOR = QColor("blue")
|
||||||
SIZE = 16
|
SIZE = 16
|
||||||
PEN = QPen(COLOR, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
|
PEN = QPen(COLOR, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
QItemDelegate.__init__(self, parent)
|
QItemDelegate.__init__(self, parent)
|
||||||
self.star_path = QPainterPath()
|
self.star_path = QPainterPath()
|
||||||
@ -41,10 +41,10 @@ class LibraryDelegate(QItemDelegate):
|
|||||||
def sizeHint(self, option, index):
|
def sizeHint(self, option, index):
|
||||||
#num = index.model().data(index, Qt.DisplayRole).toInt()[0]
|
#num = index.model().data(index, Qt.DisplayRole).toInt()[0]
|
||||||
return QSize(5*(self.SIZE), self.SIZE+4)
|
return QSize(5*(self.SIZE), self.SIZE+4)
|
||||||
|
|
||||||
def paint(self, painter, option, index):
|
def paint(self, painter, option, index):
|
||||||
num = index.model().data(index, Qt.DisplayRole).toInt()[0]
|
num = index.model().data(index, Qt.DisplayRole).toInt()[0]
|
||||||
def draw_star():
|
def draw_star():
|
||||||
painter.save()
|
painter.save()
|
||||||
painter.scale(self.factor, self.factor)
|
painter.scale(self.factor, self.factor)
|
||||||
painter.translate(50.0, 50.0)
|
painter.translate(50.0, 50.0)
|
||||||
@ -52,16 +52,18 @@ class LibraryDelegate(QItemDelegate):
|
|||||||
painter.translate(-50.0, -50.0)
|
painter.translate(-50.0, -50.0)
|
||||||
painter.drawPath(self.star_path)
|
painter.drawPath(self.star_path)
|
||||||
painter.restore()
|
painter.restore()
|
||||||
|
|
||||||
painter.save()
|
painter.save()
|
||||||
|
if hasattr(QStyle, 'CE_ItemViewItem'):
|
||||||
|
QApplication.style().drawControl(QStyle.CE_ItemViewItem, option, painter)
|
||||||
|
elif option.state & QStyle.State_Selected:
|
||||||
|
painter.fillRect(option.rect, option.palette.highlight())
|
||||||
try:
|
try:
|
||||||
if option.state & QStyle.State_Selected:
|
|
||||||
painter.fillRect(option.rect, option.palette.highlight())
|
|
||||||
painter.setRenderHint(QPainter.Antialiasing)
|
painter.setRenderHint(QPainter.Antialiasing)
|
||||||
y = option.rect.center().y()-self.SIZE/2.
|
y = option.rect.center().y()-self.SIZE/2.
|
||||||
x = option.rect.right() - self.SIZE
|
x = option.rect.right() - self.SIZE
|
||||||
painter.setPen(self.PEN)
|
painter.setPen(self.PEN)
|
||||||
painter.setBrush(self.brush)
|
painter.setBrush(self.brush)
|
||||||
painter.translate(x, y)
|
painter.translate(x, y)
|
||||||
i = 0
|
i = 0
|
||||||
while i < num:
|
while i < num:
|
||||||
@ -71,7 +73,7 @@ class LibraryDelegate(QItemDelegate):
|
|||||||
except Exception, e:
|
except Exception, e:
|
||||||
traceback.print_exc(e)
|
traceback.print_exc(e)
|
||||||
painter.restore()
|
painter.restore()
|
||||||
|
|
||||||
def createEditor(self, parent, option, index):
|
def createEditor(self, parent, option, index):
|
||||||
sb = QItemDelegate.createEditor(self, parent, option, index)
|
sb = QItemDelegate.createEditor(self, parent, option, index)
|
||||||
sb.setMinimum(0)
|
sb.setMinimum(0)
|
||||||
@ -105,22 +107,22 @@ class BooksModel(QAbstractTableModel):
|
|||||||
self.read_config()
|
self.read_config()
|
||||||
self.buffer_size = buffer
|
self.buffer_size = buffer
|
||||||
self.cover_cache = None
|
self.cover_cache = None
|
||||||
|
|
||||||
def clear_caches(self):
|
def clear_caches(self):
|
||||||
if self.cover_cache:
|
if self.cover_cache:
|
||||||
self.cover_cache.clear_cache()
|
self.cover_cache.clear_cache()
|
||||||
|
|
||||||
def read_config(self):
|
def read_config(self):
|
||||||
self.use_roman_numbers = Settings().get('use roman numerals for series number', True)
|
self.use_roman_numbers = Settings().get('use roman numerals for series number', True)
|
||||||
|
|
||||||
|
|
||||||
def set_database(self, db):
|
def set_database(self, db):
|
||||||
if isinstance(db, (QString, basestring)):
|
if isinstance(db, (QString, basestring)):
|
||||||
if isinstance(db, QString):
|
if isinstance(db, QString):
|
||||||
db = qstring_to_unicode(db)
|
db = qstring_to_unicode(db)
|
||||||
db = LibraryDatabase(os.path.expanduser(db))
|
db = LibraryDatabase(os.path.expanduser(db))
|
||||||
self.db = db
|
self.db = db
|
||||||
|
|
||||||
def refresh_ids(self, ids, current_row=-1):
|
def refresh_ids(self, ids, current_row=-1):
|
||||||
rows = self.db.refresh_ids(ids)
|
rows = self.db.refresh_ids(ids)
|
||||||
for row in rows:
|
for row in rows:
|
||||||
@ -132,24 +134,24 @@ class BooksModel(QAbstractTableModel):
|
|||||||
self.get_book_display_info(row))
|
self.get_book_display_info(row))
|
||||||
self.emit(SIGNAL('dataChanged(QModelIndex,QModelIndex)'),
|
self.emit(SIGNAL('dataChanged(QModelIndex,QModelIndex)'),
|
||||||
self.index(row, 0), self.index(row, self.columnCount(None)-1))
|
self.index(row, 0), self.index(row, self.columnCount(None)-1))
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self.db.close()
|
self.db.close()
|
||||||
self.db = None
|
self.db = None
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
def add_books(self, paths, formats, metadata, uris=[], add_duplicates=False):
|
def add_books(self, paths, formats, metadata, uris=[], add_duplicates=False):
|
||||||
return self.db.add_books(paths, formats, metadata, uris,
|
return self.db.add_books(paths, formats, metadata, uris,
|
||||||
add_duplicates=add_duplicates)
|
add_duplicates=add_duplicates)
|
||||||
|
|
||||||
def row_indices(self, index):
|
def row_indices(self, index):
|
||||||
''' Return list indices of all cells in index.row()'''
|
''' Return list indices of all cells in index.row()'''
|
||||||
return [ self.index(index.row(), c) for c in range(self.columnCount(None))]
|
return [ self.index(index.row(), c) for c in range(self.columnCount(None))]
|
||||||
|
|
||||||
def save_to_disk(self, rows, path, single_dir=False):
|
def save_to_disk(self, rows, path, single_dir=False):
|
||||||
rows = [row.row() for row in rows]
|
rows = [row.row() for row in rows]
|
||||||
self.db.export_to_dir(path, rows, self.sorted_on[0] == 1, single_dir=single_dir)
|
self.db.export_to_dir(path, rows, self.sorted_on[0] == 1, single_dir=single_dir)
|
||||||
|
|
||||||
def delete_books(self, indices):
|
def delete_books(self, indices):
|
||||||
ids = [ self.id(i) for i in indices ]
|
ids = [ self.id(i) for i in indices ]
|
||||||
for id in ids:
|
for id in ids:
|
||||||
@ -159,10 +161,10 @@ class BooksModel(QAbstractTableModel):
|
|||||||
self.endRemoveRows()
|
self.endRemoveRows()
|
||||||
self.clear_caches()
|
self.clear_caches()
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
def search_tokens(self, text):
|
def search_tokens(self, text):
|
||||||
return text_to_tokens(text)
|
return text_to_tokens(text)
|
||||||
|
|
||||||
def search(self, text, refinement, reset=True):
|
def search(self, text, refinement, reset=True):
|
||||||
tokens, OR = self.search_tokens(text)
|
tokens, OR = self.search_tokens(text)
|
||||||
self.db.filter(tokens, refilter=refinement, OR=OR)
|
self.db.filter(tokens, refilter=refinement, OR=OR)
|
||||||
@ -170,7 +172,7 @@ class BooksModel(QAbstractTableModel):
|
|||||||
if reset:
|
if reset:
|
||||||
self.clear_caches()
|
self.clear_caches()
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
def sort(self, col, order, reset=True):
|
def sort(self, col, order, reset=True):
|
||||||
if not self.db:
|
if not self.db:
|
||||||
return
|
return
|
||||||
@ -179,30 +181,30 @@ class BooksModel(QAbstractTableModel):
|
|||||||
self.research()
|
self.research()
|
||||||
if reset:
|
if reset:
|
||||||
self.clear_caches()
|
self.clear_caches()
|
||||||
self.reset()
|
self.reset()
|
||||||
self.sorted_on = (col, order)
|
self.sorted_on = (col, order)
|
||||||
|
|
||||||
def resort(self, reset=True):
|
def resort(self, reset=True):
|
||||||
self.sort(*self.sorted_on, **dict(reset=reset))
|
self.sort(*self.sorted_on, **dict(reset=reset))
|
||||||
|
|
||||||
def research(self, reset=True):
|
def research(self, reset=True):
|
||||||
self.search(self.last_search, False, reset=reset)
|
self.search(self.last_search, False, reset=reset)
|
||||||
|
|
||||||
def database_needs_migration(self):
|
def database_needs_migration(self):
|
||||||
path = os.path.expanduser('~/library.db')
|
path = os.path.expanduser('~/library.db')
|
||||||
return self.db.is_empty() and \
|
return self.db.is_empty() and \
|
||||||
os.path.exists(path) and\
|
os.path.exists(path) and\
|
||||||
LibraryDatabase.sizeof_old_database(path) > 0
|
LibraryDatabase.sizeof_old_database(path) > 0
|
||||||
|
|
||||||
def columnCount(self, parent):
|
def columnCount(self, parent):
|
||||||
return len(self.cols)
|
return len(self.cols)
|
||||||
|
|
||||||
def rowCount(self, parent):
|
def rowCount(self, parent):
|
||||||
return self.db.rows() if self.db else 0
|
return self.db.rows() if self.db else 0
|
||||||
|
|
||||||
def count(self):
|
def count(self):
|
||||||
return self.rowCount(None)
|
return self.rowCount(None)
|
||||||
|
|
||||||
def get_book_display_info(self, idx):
|
def get_book_display_info(self, idx):
|
||||||
data = {}
|
data = {}
|
||||||
cdata = self.cover(idx)
|
cdata = self.cover(idx)
|
||||||
@ -229,9 +231,9 @@ class BooksModel(QAbstractTableModel):
|
|||||||
sidx = self.db.series_index(idx)
|
sidx = self.db.series_index(idx)
|
||||||
sidx = self.__class__.roman(sidx) if self.use_roman_numbers else str(sidx)
|
sidx = self.__class__.roman(sidx) if self.use_roman_numbers else str(sidx)
|
||||||
data[_('Series')] = _('Book <font face="serif">%s</font> of %s.')%(sidx, series)
|
data[_('Series')] = _('Book <font face="serif">%s</font> of %s.')%(sidx, series)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def set_cache(self, idx):
|
def set_cache(self, idx):
|
||||||
l, r = 0, self.count()-1
|
l, r = 0, self.count()-1
|
||||||
if self.cover_cache:
|
if self.cover_cache:
|
||||||
@ -244,7 +246,7 @@ class BooksModel(QAbstractTableModel):
|
|||||||
ids = ids + [i for i in range(l, r, 1) if i not in ids]
|
ids = ids + [i for i in range(l, r, 1) if i not in ids]
|
||||||
ids = [self.db.id(i) for i in ids]
|
ids = [self.db.id(i) for i in ids]
|
||||||
self.cover_cache.set_cache(ids)
|
self.cover_cache.set_cache(ids)
|
||||||
|
|
||||||
def current_changed(self, current, previous, emit_signal=True):
|
def current_changed(self, current, previous, emit_signal=True):
|
||||||
idx = current.row()
|
idx = current.row()
|
||||||
self.set_cache(idx)
|
self.set_cache(idx)
|
||||||
@ -253,7 +255,7 @@ class BooksModel(QAbstractTableModel):
|
|||||||
self.emit(SIGNAL('new_bookdisplay_data(PyQt_PyObject)'), data)
|
self.emit(SIGNAL('new_bookdisplay_data(PyQt_PyObject)'), data)
|
||||||
else:
|
else:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_book_info(self, index):
|
def get_book_info(self, index):
|
||||||
data = self.current_changed(index, None, False)
|
data = self.current_changed(index, None, False)
|
||||||
row = index.row()
|
row = index.row()
|
||||||
@ -264,8 +266,8 @@ class BooksModel(QAbstractTableModel):
|
|||||||
au = ', '.join([a.strip() for a in au.split(',')])
|
au = ', '.join([a.strip() for a in au.split(',')])
|
||||||
data[_('Author(s)')] = au
|
data[_('Author(s)')] = au
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def get_metadata(self, rows):
|
def get_metadata(self, rows):
|
||||||
metadata = []
|
metadata = []
|
||||||
for row in rows:
|
for row in rows:
|
||||||
@ -296,11 +298,11 @@ class BooksModel(QAbstractTableModel):
|
|||||||
'comments': self.db.comments(row),
|
'comments': self.db.comments(row),
|
||||||
}
|
}
|
||||||
if series is not None:
|
if series is not None:
|
||||||
mi['tag order'] = {series:self.db.books_in_series_of(row)}
|
mi['tag order'] = {series:self.db.books_in_series_of(row)}
|
||||||
|
|
||||||
metadata.append(mi)
|
metadata.append(mi)
|
||||||
return metadata
|
return metadata
|
||||||
|
|
||||||
def get_preferred_formats(self, rows, formats):
|
def get_preferred_formats(self, rows, formats):
|
||||||
ans = []
|
ans = []
|
||||||
for row in (row.row() for row in rows):
|
for row in (row.row() for row in rows):
|
||||||
@ -313,17 +315,17 @@ class BooksModel(QAbstractTableModel):
|
|||||||
pt = PersistentTemporaryFile(suffix='.'+format)
|
pt = PersistentTemporaryFile(suffix='.'+format)
|
||||||
pt.write(self.db.format(row, format))
|
pt.write(self.db.format(row, format))
|
||||||
pt.seek(0)
|
pt.seek(0)
|
||||||
ans.append(pt)
|
ans.append(pt)
|
||||||
else:
|
else:
|
||||||
ans.append(None)
|
ans.append(None)
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def id(self, row):
|
def id(self, row):
|
||||||
return self.db.id(row.row())
|
return self.db.id(row.row())
|
||||||
|
|
||||||
def title(self, row_number):
|
def title(self, row_number):
|
||||||
return self.db.title(row_number)
|
return self.db.title(row_number)
|
||||||
|
|
||||||
def cover(self, row_number):
|
def cover(self, row_number):
|
||||||
id = self.db.id(row_number)
|
id = self.db.id(row_number)
|
||||||
data = None
|
data = None
|
||||||
@ -342,15 +344,15 @@ class BooksModel(QAbstractTableModel):
|
|||||||
if img.isNull():
|
if img.isNull():
|
||||||
img = self.default_image
|
img = self.default_image
|
||||||
return img
|
return img
|
||||||
|
|
||||||
def data(self, index, role):
|
def data(self, index, role):
|
||||||
if role == Qt.DisplayRole or role == Qt.EditRole:
|
if role == Qt.DisplayRole or role == Qt.EditRole:
|
||||||
row, col = index.row(), index.column()
|
row, col = index.row(), index.column()
|
||||||
if col == 0:
|
if col == 0:
|
||||||
text = self.db.title(row)
|
text = self.db.title(row)
|
||||||
if text:
|
if text:
|
||||||
return QVariant(text)
|
return QVariant(text)
|
||||||
elif col == 1:
|
elif col == 1:
|
||||||
au = self.db.authors(row)
|
au = self.db.authors(row)
|
||||||
if au:
|
if au:
|
||||||
au = au.split(',')
|
au = au.split(',')
|
||||||
@ -364,18 +366,18 @@ class BooksModel(QAbstractTableModel):
|
|||||||
if dt:
|
if dt:
|
||||||
dt = dt - timedelta(seconds=time.timezone) + timedelta(hours=time.daylight)
|
dt = dt - timedelta(seconds=time.timezone) + timedelta(hours=time.daylight)
|
||||||
return QVariant(dt.strftime(BooksView.TIME_FMT).decode(preferred_encoding, 'replace'))
|
return QVariant(dt.strftime(BooksView.TIME_FMT).decode(preferred_encoding, 'replace'))
|
||||||
elif col == 4:
|
elif col == 4:
|
||||||
r = self.db.rating(row)
|
r = self.db.rating(row)
|
||||||
r = r/2 if r else 0
|
r = r/2 if r else 0
|
||||||
return QVariant(r)
|
return QVariant(r)
|
||||||
elif col == 5:
|
elif col == 5:
|
||||||
pub = self.db.publisher(row)
|
pub = self.db.publisher(row)
|
||||||
if pub:
|
if pub:
|
||||||
return QVariant(pub)
|
return QVariant(pub)
|
||||||
elif col == 6:
|
elif col == 6:
|
||||||
tags = self.db.tags(row)
|
tags = self.db.tags(row)
|
||||||
if tags:
|
if tags:
|
||||||
return QVariant(', '.join(tags.split(',')))
|
return QVariant(', '.join(tags.split(',')))
|
||||||
elif col == 7:
|
elif col == 7:
|
||||||
series = self.db.series(row)
|
series = self.db.series(row)
|
||||||
if series:
|
if series:
|
||||||
@ -383,16 +385,16 @@ class BooksModel(QAbstractTableModel):
|
|||||||
return NONE
|
return NONE
|
||||||
elif role == Qt.TextAlignmentRole and index.column() in [2, 3, 4]:
|
elif role == Qt.TextAlignmentRole and index.column() in [2, 3, 4]:
|
||||||
return QVariant(Qt.AlignRight | Qt.AlignVCenter)
|
return QVariant(Qt.AlignRight | Qt.AlignVCenter)
|
||||||
elif role == Qt.ToolTipRole and index.isValid():
|
elif role == Qt.ToolTipRole and index.isValid():
|
||||||
if index.column() in self.editable_cols:
|
if index.column() in self.editable_cols:
|
||||||
return QVariant(_("Double click to <b>edit</b> me<br><br>"))
|
return QVariant(_("Double click to <b>edit</b> me<br><br>"))
|
||||||
return NONE
|
return NONE
|
||||||
|
|
||||||
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 = _("Size (MB)")
|
elif section == 2: text = _("Size (MB)")
|
||||||
@ -402,16 +404,16 @@ class BooksModel(QAbstractTableModel):
|
|||||||
elif section == 6: text = _("Tags")
|
elif section == 6: text = _("Tags")
|
||||||
elif section == 7: text = _("Series")
|
elif section == 7: text = _("Series")
|
||||||
return QVariant(text)
|
return QVariant(text)
|
||||||
else:
|
else:
|
||||||
return QVariant(section+1)
|
return QVariant(section+1)
|
||||||
|
|
||||||
def flags(self, index):
|
def flags(self, index):
|
||||||
flags = QAbstractTableModel.flags(self, index)
|
flags = QAbstractTableModel.flags(self, index)
|
||||||
if index.isValid():
|
if index.isValid():
|
||||||
if index.column() in self.editable_cols:
|
if index.column() in self.editable_cols:
|
||||||
flags |= Qt.ItemIsEditable
|
flags |= Qt.ItemIsEditable
|
||||||
return flags
|
return flags
|
||||||
|
|
||||||
def setData(self, index, value, role):
|
def setData(self, index, value, role):
|
||||||
done = False
|
done = False
|
||||||
if role == Qt.EditRole:
|
if role == Qt.EditRole:
|
||||||
@ -424,7 +426,7 @@ class BooksModel(QAbstractTableModel):
|
|||||||
val = 0 if val < 0 else 5 if val > 5 else val
|
val = 0 if val < 0 else 5 if val > 5 else val
|
||||||
val *= 2
|
val *= 2
|
||||||
column = self.cols[col]
|
column = self.cols[col]
|
||||||
self.db.set(row, column, val)
|
self.db.set(row, column, val)
|
||||||
self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \
|
self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \
|
||||||
index, index)
|
index, index)
|
||||||
if col == self.sorted_on[0]:
|
if col == self.sorted_on[0]:
|
||||||
@ -435,17 +437,17 @@ class BooksModel(QAbstractTableModel):
|
|||||||
class BooksView(TableView):
|
class BooksView(TableView):
|
||||||
TIME_FMT = '%d %b %Y'
|
TIME_FMT = '%d %b %Y'
|
||||||
wrapper = textwrap.TextWrapper(width=20)
|
wrapper = textwrap.TextWrapper(width=20)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def wrap(cls, s, width=20):
|
def wrap(cls, s, width=20):
|
||||||
cls.wrapper.width = width
|
cls.wrapper.width = width
|
||||||
return cls.wrapper.fill(s)
|
return cls.wrapper.fill(s)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def human_readable(cls, size, precision=1):
|
def human_readable(cls, size, precision=1):
|
||||||
""" Convert a size in bytes into megabytes """
|
""" Convert a size in bytes into megabytes """
|
||||||
return ('%.'+str(precision)+'f') % ((size/(1024.*1024.)),)
|
return ('%.'+str(precision)+'f') % ((size/(1024.*1024.)),)
|
||||||
|
|
||||||
def __init__(self, parent, modelcls=BooksModel):
|
def __init__(self, parent, modelcls=BooksModel):
|
||||||
TableView.__init__(self, parent)
|
TableView.__init__(self, parent)
|
||||||
self.display_parent = parent
|
self.display_parent = parent
|
||||||
@ -454,7 +456,7 @@ class BooksView(TableView):
|
|||||||
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||||
self.setSortingEnabled(True)
|
self.setSortingEnabled(True)
|
||||||
if self.__class__.__name__ == 'BooksView': # Subclasses may not have rating as col 4
|
if self.__class__.__name__ == 'BooksView': # Subclasses may not have rating as col 4
|
||||||
self.setItemDelegateForColumn(4, LibraryDelegate(self))
|
self.setItemDelegateForColumn(4, LibraryDelegate(self))
|
||||||
QObject.connect(self.selectionModel(), SIGNAL('currentRowChanged(QModelIndex, QModelIndex)'),
|
QObject.connect(self.selectionModel(), SIGNAL('currentRowChanged(QModelIndex, QModelIndex)'),
|
||||||
self._model.current_changed)
|
self._model.current_changed)
|
||||||
# Adding and removing rows should resize rows to contents
|
# Adding and removing rows should resize rows to contents
|
||||||
@ -463,42 +465,42 @@ class BooksView(TableView):
|
|||||||
# Resetting the model should resize rows (model is reset after search and sort operations)
|
# Resetting the model should resize rows (model is reset after search and sort operations)
|
||||||
QObject.connect(self.model(), SIGNAL('modelReset()'), self.resizeRowsToContents)
|
QObject.connect(self.model(), SIGNAL('modelReset()'), self.resizeRowsToContents)
|
||||||
self.set_visible_columns()
|
self.set_visible_columns()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def paths_from_event(cls, event):
|
def paths_from_event(cls, event):
|
||||||
'''
|
'''
|
||||||
Accept a drop event and return a list of paths that can be read from
|
Accept a drop event and return a list of paths that can be read from
|
||||||
and represent files with extensions.
|
and represent files with extensions.
|
||||||
'''
|
'''
|
||||||
if event.mimeData().hasFormat('text/uri-list'):
|
if event.mimeData().hasFormat('text/uri-list'):
|
||||||
urls = [qstring_to_unicode(u.toLocalFile()) for u in event.mimeData().urls()]
|
urls = [qstring_to_unicode(u.toLocalFile()) for u in event.mimeData().urls()]
|
||||||
return [u for u in urls if os.path.splitext(u)[1] and os.access(u, os.R_OK)]
|
return [u for u in urls if os.path.splitext(u)[1] and os.access(u, os.R_OK)]
|
||||||
|
|
||||||
def dragEnterEvent(self, event):
|
def dragEnterEvent(self, event):
|
||||||
if int(event.possibleActions() & Qt.CopyAction) + \
|
if int(event.possibleActions() & Qt.CopyAction) + \
|
||||||
int(event.possibleActions() & Qt.MoveAction) == 0:
|
int(event.possibleActions() & Qt.MoveAction) == 0:
|
||||||
return
|
return
|
||||||
paths = self.paths_from_event(event)
|
paths = self.paths_from_event(event)
|
||||||
|
|
||||||
if paths:
|
if paths:
|
||||||
event.acceptProposedAction()
|
event.acceptProposedAction()
|
||||||
|
|
||||||
def dragMoveEvent(self, event):
|
def dragMoveEvent(self, event):
|
||||||
event.acceptProposedAction()
|
event.acceptProposedAction()
|
||||||
|
|
||||||
def dropEvent(self, event):
|
def dropEvent(self, event):
|
||||||
paths = self.paths_from_event(event)
|
paths = self.paths_from_event(event)
|
||||||
event.setDropAction(Qt.CopyAction)
|
event.setDropAction(Qt.CopyAction)
|
||||||
event.accept()
|
event.accept()
|
||||||
self.emit(SIGNAL('files_dropped(PyQt_PyObject)'), paths, Qt.QueuedConnection)
|
self.emit(SIGNAL('files_dropped(PyQt_PyObject)'), paths, Qt.QueuedConnection)
|
||||||
|
|
||||||
|
|
||||||
def set_database(self, db):
|
def set_database(self, db):
|
||||||
self._model.set_database(db)
|
self._model.set_database(db)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self._model.close()
|
self._model.close()
|
||||||
|
|
||||||
def migrate_database(self):
|
def migrate_database(self):
|
||||||
if self.model().database_needs_migration():
|
if self.model().database_needs_migration():
|
||||||
print 'Migrating database from pre 0.4.0 version'
|
print 'Migrating database from pre 0.4.0 version'
|
||||||
@ -510,39 +512,39 @@ class BooksView(TableView):
|
|||||||
progress.setModal(True)
|
progress.setModal(True)
|
||||||
progress.setValue(0)
|
progress.setValue(0)
|
||||||
app = QCoreApplication.instance()
|
app = QCoreApplication.instance()
|
||||||
|
|
||||||
def meter(count):
|
def meter(count):
|
||||||
progress.setValue(count)
|
progress.setValue(count)
|
||||||
app.processEvents()
|
app.processEvents()
|
||||||
progress.setWindowTitle('Upgrading database')
|
progress.setWindowTitle('Upgrading database')
|
||||||
progress.show()
|
progress.show()
|
||||||
LibraryDatabase.import_old_database(path, self._model.db.conn, meter)
|
LibraryDatabase.import_old_database(path, self._model.db.conn, meter)
|
||||||
|
|
||||||
def connect_to_search_box(self, sb):
|
def connect_to_search_box(self, sb):
|
||||||
QObject.connect(sb, SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'),
|
QObject.connect(sb, SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'),
|
||||||
self._model.search)
|
self._model.search)
|
||||||
|
|
||||||
def connect_to_book_display(self, bd):
|
def connect_to_book_display(self, bd):
|
||||||
QObject.connect(self._model, SIGNAL('new_bookdisplay_data(PyQt_PyObject)'),
|
QObject.connect(self._model, SIGNAL('new_bookdisplay_data(PyQt_PyObject)'),
|
||||||
bd)
|
bd)
|
||||||
|
|
||||||
|
|
||||||
class DeviceBooksView(BooksView):
|
class DeviceBooksView(BooksView):
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
BooksView.__init__(self, parent, DeviceBooksModel)
|
BooksView.__init__(self, parent, DeviceBooksModel)
|
||||||
self.columns_resized = False
|
self.columns_resized = False
|
||||||
self.resize_on_select = False
|
self.resize_on_select = False
|
||||||
|
|
||||||
def resizeColumnsToContents(self):
|
def resizeColumnsToContents(self):
|
||||||
QTableView.resizeColumnsToContents(self)
|
QTableView.resizeColumnsToContents(self)
|
||||||
self.columns_resized = True
|
self.columns_resized = True
|
||||||
|
|
||||||
def connect_dirtied_signal(self, slot):
|
def connect_dirtied_signal(self, slot):
|
||||||
QObject.connect(self._model, SIGNAL('booklist_dirtied()'), slot)
|
QObject.connect(self._model, SIGNAL('booklist_dirtied()'), slot)
|
||||||
|
|
||||||
class DeviceBooksModel(BooksModel):
|
class DeviceBooksModel(BooksModel):
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
BooksModel.__init__(self, parent)
|
BooksModel.__init__(self, parent)
|
||||||
self.db = []
|
self.db = []
|
||||||
@ -550,15 +552,15 @@ class DeviceBooksModel(BooksModel):
|
|||||||
self.sorted_map = []
|
self.sorted_map = []
|
||||||
self.unknown = str(self.trUtf8('Unknown'))
|
self.unknown = str(self.trUtf8('Unknown'))
|
||||||
self.marked_for_deletion = {}
|
self.marked_for_deletion = {}
|
||||||
|
|
||||||
|
|
||||||
def mark_for_deletion(self, id, rows):
|
def mark_for_deletion(self, id, rows):
|
||||||
self.marked_for_deletion[id] = self.indices(rows)
|
self.marked_for_deletion[id] = self.indices(rows)
|
||||||
for row in rows:
|
for row in rows:
|
||||||
indices = self.row_indices(row)
|
indices = self.row_indices(row)
|
||||||
self.emit(SIGNAL('dataChanged(QModelIndex, QModelIndex)'), indices[0], indices[-1])
|
self.emit(SIGNAL('dataChanged(QModelIndex, QModelIndex)'), indices[0], indices[-1])
|
||||||
|
|
||||||
|
|
||||||
def deletion_done(self, id, succeeded=True):
|
def deletion_done(self, id, succeeded=True):
|
||||||
if not self.marked_for_deletion.has_key(id):
|
if not self.marked_for_deletion.has_key(id):
|
||||||
return
|
return
|
||||||
@ -566,29 +568,29 @@ class DeviceBooksModel(BooksModel):
|
|||||||
for row in rows:
|
for row in rows:
|
||||||
if not succeeded:
|
if not succeeded:
|
||||||
indices = self.row_indices(self.index(row, 0))
|
indices = self.row_indices(self.index(row, 0))
|
||||||
self.emit(SIGNAL('dataChanged(QModelIndex, QModelIndex)'), indices[0], indices[-1])
|
self.emit(SIGNAL('dataChanged(QModelIndex, QModelIndex)'), indices[0], indices[-1])
|
||||||
|
|
||||||
def paths_deleted(self, paths):
|
def paths_deleted(self, paths):
|
||||||
self.map = list(range(0, len(self.db)))
|
self.map = list(range(0, len(self.db)))
|
||||||
self.resort(False)
|
self.resort(False)
|
||||||
self.research(True)
|
self.research(True)
|
||||||
|
|
||||||
def indices_to_be_deleted(self):
|
def indices_to_be_deleted(self):
|
||||||
ans = []
|
ans = []
|
||||||
for v in self.marked_for_deletion.values():
|
for v in self.marked_for_deletion.values():
|
||||||
ans.extend(v)
|
ans.extend(v)
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def flags(self, index):
|
def flags(self, index):
|
||||||
if self.map[index.row()] in self.indices_to_be_deleted():
|
if self.map[index.row()] in self.indices_to_be_deleted():
|
||||||
return Qt.ItemIsUserCheckable # Can't figure out how to get the disabled flag in python
|
return Qt.ItemIsUserCheckable # Can't figure out how to get the disabled flag in python
|
||||||
flags = QAbstractTableModel.flags(self, index)
|
flags = QAbstractTableModel.flags(self, index)
|
||||||
if index.isValid():
|
if index.isValid():
|
||||||
if index.column() in [0, 1] or (index.column() == 4 and self.db.supports_tags()):
|
if index.column() in [0, 1] or (index.column() == 4 and self.db.supports_tags()):
|
||||||
flags |= Qt.ItemIsEditable
|
flags |= Qt.ItemIsEditable
|
||||||
return flags
|
return flags
|
||||||
|
|
||||||
|
|
||||||
def search(self, text, refinement, reset=True):
|
def search(self, text, refinement, reset=True):
|
||||||
tokens, OR = self.search_tokens(text)
|
tokens, OR = self.search_tokens(text)
|
||||||
base = self.map if refinement else self.sorted_map
|
base = self.map if refinement else self.sorted_map
|
||||||
@ -611,13 +613,13 @@ class DeviceBooksModel(BooksModel):
|
|||||||
break
|
break
|
||||||
if add:
|
if add:
|
||||||
result.append(i)
|
result.append(i)
|
||||||
|
|
||||||
self.map = result
|
self.map = result
|
||||||
|
|
||||||
if reset:
|
if reset:
|
||||||
self.reset()
|
self.reset()
|
||||||
self.last_search = text
|
self.last_search = text
|
||||||
|
|
||||||
def sort(self, col, order, reset=True):
|
def sort(self, col, order, reset=True):
|
||||||
descending = order != Qt.AscendingOrder
|
descending = order != Qt.AscendingOrder
|
||||||
def strcmp(attr):
|
def strcmp(attr):
|
||||||
@ -632,7 +634,7 @@ class DeviceBooksModel(BooksModel):
|
|||||||
x, y = x.strip().lower(), y.strip().lower()
|
x, y = x.strip().lower(), y.strip().lower()
|
||||||
return cmp(x, y)
|
return cmp(x, y)
|
||||||
return _strcmp
|
return _strcmp
|
||||||
def datecmp(x, y):
|
def datecmp(x, y):
|
||||||
x = self.db[x].datetime
|
x = self.db[x].datetime
|
||||||
y = self.db[y].datetime
|
y = self.db[y].datetime
|
||||||
return cmp(datetime(*x[0:6]), datetime(*y[0:6]))
|
return cmp(datetime(*x[0:6]), datetime(*y[0:6]))
|
||||||
@ -643,7 +645,7 @@ class DeviceBooksModel(BooksModel):
|
|||||||
x, y = ','.join(self.db[x].tags), ','.join(self.db[y].tags)
|
x, y = ','.join(self.db[x].tags), ','.join(self.db[y].tags)
|
||||||
return cmp(x, y)
|
return cmp(x, y)
|
||||||
fcmp = strcmp('title_sorter') if col == 0 else strcmp('authors') if col == 1 else \
|
fcmp = strcmp('title_sorter') if col == 0 else strcmp('authors') if col == 1 else \
|
||||||
sizecmp if col == 2 else datecmp if col == 3 else tagscmp
|
sizecmp if col == 2 else datecmp if col == 3 else tagscmp
|
||||||
self.map.sort(cmp=fcmp, reverse=descending)
|
self.map.sort(cmp=fcmp, reverse=descending)
|
||||||
if len(self.map) == len(self.db):
|
if len(self.map) == len(self.db):
|
||||||
self.sorted_map = list(self.map)
|
self.sorted_map = list(self.map)
|
||||||
@ -653,17 +655,17 @@ class DeviceBooksModel(BooksModel):
|
|||||||
self.sorted_on = (col, order)
|
self.sorted_on = (col, order)
|
||||||
if reset:
|
if reset:
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
def columnCount(self, parent):
|
def columnCount(self, parent):
|
||||||
return 5
|
return 5
|
||||||
|
|
||||||
def rowCount(self, parent):
|
def rowCount(self, parent):
|
||||||
return len(self.map)
|
return len(self.map)
|
||||||
|
|
||||||
def set_database(self, db):
|
def set_database(self, db):
|
||||||
self.db = db
|
self.db = db
|
||||||
self.map = list(range(0, len(db)))
|
self.map = list(range(0, len(db)))
|
||||||
|
|
||||||
def current_changed(self, current, previous):
|
def current_changed(self, current, previous):
|
||||||
data = {}
|
data = {}
|
||||||
item = self.db[self.map[current.row()]]
|
item = self.db[self.map[current.row()]]
|
||||||
@ -686,26 +688,26 @@ class DeviceBooksModel(BooksModel):
|
|||||||
data[_('Timestamp')] = dt.ctime()
|
data[_('Timestamp')] = dt.ctime()
|
||||||
data[_('Tags')] = ', '.join(item.tags)
|
data[_('Tags')] = ', '.join(item.tags)
|
||||||
self.emit(SIGNAL('new_bookdisplay_data(PyQt_PyObject)'), data)
|
self.emit(SIGNAL('new_bookdisplay_data(PyQt_PyObject)'), data)
|
||||||
|
|
||||||
def paths(self, rows):
|
def paths(self, rows):
|
||||||
return [self.db[self.map[r.row()]].path for r in rows ]
|
return [self.db[self.map[r.row()]].path for r in rows ]
|
||||||
|
|
||||||
def indices(self, rows):
|
def indices(self, rows):
|
||||||
'''
|
'''
|
||||||
Return indices into underlying database from rows
|
Return indices into underlying database from rows
|
||||||
'''
|
'''
|
||||||
return [ self.map[r.row()] for r in rows]
|
return [ self.map[r.row()] for r in rows]
|
||||||
|
|
||||||
|
|
||||||
def data(self, index, role):
|
def data(self, index, role):
|
||||||
if role == Qt.DisplayRole or role == Qt.EditRole:
|
if role == Qt.DisplayRole or role == Qt.EditRole:
|
||||||
row, col = index.row(), index.column()
|
row, col = index.row(), index.column()
|
||||||
if col == 0:
|
if col == 0:
|
||||||
text = self.db[self.map[row]].title
|
text = self.db[self.map[row]].title
|
||||||
if not text:
|
if not text:
|
||||||
text = self.unknown
|
text = self.unknown
|
||||||
return QVariant(text)
|
return QVariant(text)
|
||||||
elif col == 1:
|
elif col == 1:
|
||||||
au = self.db[self.map[row]].authors
|
au = self.db[self.map[row]].authors
|
||||||
if not au:
|
if not au:
|
||||||
au = self.unknown
|
au = self.unknown
|
||||||
@ -726,40 +728,40 @@ class DeviceBooksModel(BooksModel):
|
|||||||
dt = dt - timedelta(seconds=time.timezone) + timedelta(hours=time.daylight)
|
dt = dt - timedelta(seconds=time.timezone) + timedelta(hours=time.daylight)
|
||||||
return QVariant(dt.strftime(BooksView.TIME_FMT))
|
return QVariant(dt.strftime(BooksView.TIME_FMT))
|
||||||
elif col == 4:
|
elif col == 4:
|
||||||
tags = self.db[self.map[row]].tags
|
tags = self.db[self.map[row]].tags
|
||||||
if tags:
|
if tags:
|
||||||
return QVariant(', '.join(tags))
|
return QVariant(', '.join(tags))
|
||||||
elif role == Qt.TextAlignmentRole and index.column() in [2, 3]:
|
elif role == Qt.TextAlignmentRole and index.column() in [2, 3]:
|
||||||
return QVariant(Qt.AlignRight | Qt.AlignVCenter)
|
return QVariant(Qt.AlignRight | Qt.AlignVCenter)
|
||||||
elif role == Qt.ToolTipRole and index.isValid():
|
elif role == Qt.ToolTipRole and index.isValid():
|
||||||
if self.map[index.row()] in self.indices_to_be_deleted():
|
if self.map[index.row()] in self.indices_to_be_deleted():
|
||||||
return QVariant('Marked for deletion')
|
return QVariant('Marked for deletion')
|
||||||
col = index.column()
|
col = index.column()
|
||||||
if col in [0, 1] or (col == 4 and self.db.supports_tags()):
|
if col in [0, 1] or (col == 4 and self.db.supports_tags()):
|
||||||
return QVariant("Double click to <b>edit</b> me<br><br>")
|
return QVariant("Double click to <b>edit</b> me<br><br>")
|
||||||
return NONE
|
return NONE
|
||||||
|
|
||||||
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 = _("Size (MB)")
|
elif section == 2: text = _("Size (MB)")
|
||||||
elif section == 3: text = _("Date")
|
elif section == 3: text = _("Date")
|
||||||
elif section == 4: text = _("Tags")
|
elif section == 4: text = _("Tags")
|
||||||
return QVariant(text)
|
return QVariant(text)
|
||||||
else:
|
else:
|
||||||
return QVariant(section+1)
|
return QVariant(section+1)
|
||||||
|
|
||||||
def setData(self, index, value, role):
|
def setData(self, index, value, role):
|
||||||
done = False
|
done = False
|
||||||
if role == Qt.EditRole:
|
if role == Qt.EditRole:
|
||||||
row, col = index.row(), index.column()
|
row, col = index.row(), index.column()
|
||||||
if col in [2, 3]:
|
if col in [2, 3]:
|
||||||
return False
|
return False
|
||||||
val = qstring_to_unicode(value.toString()).strip()
|
val = qstring_to_unicode(value.toString()).strip()
|
||||||
idx = self.map[row]
|
idx = self.map[row]
|
||||||
if col == 0:
|
if col == 0:
|
||||||
self.db[idx].title = val
|
self.db[idx].title = val
|
||||||
@ -778,9 +780,9 @@ class DeviceBooksModel(BooksModel):
|
|||||||
return done
|
return done
|
||||||
|
|
||||||
class SearchBox(QLineEdit):
|
class SearchBox(QLineEdit):
|
||||||
|
|
||||||
INTERVAL = 1000 #: Time to wait before emitting search signal
|
INTERVAL = 1000 #: Time to wait before emitting search signal
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
QLineEdit.__init__(self, parent)
|
QLineEdit.__init__(self, parent)
|
||||||
self.help_text = _('Search (For Advanced Search click the button to the left)')
|
self.help_text = _('Search (For Advanced Search click the button to the left)')
|
||||||
@ -792,40 +794,40 @@ class SearchBox(QLineEdit):
|
|||||||
self.timer = None
|
self.timer = None
|
||||||
self.clear_to_help()
|
self.clear_to_help()
|
||||||
QObject.connect(self, SIGNAL('textEdited(QString)'), self.text_edited_slot)
|
QObject.connect(self, SIGNAL('textEdited(QString)'), self.text_edited_slot)
|
||||||
|
|
||||||
|
|
||||||
def normalize_state(self):
|
def normalize_state(self):
|
||||||
self.setText('')
|
self.setText('')
|
||||||
self.setPalette(self.default_palette)
|
self.setPalette(self.default_palette)
|
||||||
|
|
||||||
def clear_to_help(self):
|
def clear_to_help(self):
|
||||||
self.setPalette(self.gray)
|
self.setPalette(self.gray)
|
||||||
self.setText(self.help_text)
|
self.setText(self.help_text)
|
||||||
self.home(False)
|
self.home(False)
|
||||||
self.initial_state = True
|
self.initial_state = True
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self.clear_to_help()
|
self.clear_to_help()
|
||||||
self.emit(SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), '', False)
|
self.emit(SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), '', False)
|
||||||
|
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
def keyPressEvent(self, event):
|
||||||
if self.initial_state:
|
if self.initial_state:
|
||||||
self.normalize_state()
|
self.normalize_state()
|
||||||
self.initial_state = False
|
self.initial_state = False
|
||||||
QLineEdit.keyPressEvent(self, event)
|
QLineEdit.keyPressEvent(self, event)
|
||||||
|
|
||||||
def mouseReleaseEvent(self, event):
|
def mouseReleaseEvent(self, event):
|
||||||
if self.initial_state:
|
if self.initial_state:
|
||||||
self.normalize_state()
|
self.normalize_state()
|
||||||
self.initial_state = False
|
self.initial_state = False
|
||||||
QLineEdit.mouseReleaseEvent(self, event)
|
QLineEdit.mouseReleaseEvent(self, event)
|
||||||
|
|
||||||
def text_edited_slot(self, text):
|
def text_edited_slot(self, text):
|
||||||
text = qstring_to_unicode(text) if isinstance(text, QString) else unicode(text)
|
text = qstring_to_unicode(text) if isinstance(text, QString) else unicode(text)
|
||||||
self.prev_text = text
|
self.prev_text = text
|
||||||
self.timer = self.startTimer(self.__class__.INTERVAL)
|
self.timer = self.startTimer(self.__class__.INTERVAL)
|
||||||
|
|
||||||
def timerEvent(self, event):
|
def timerEvent(self, event):
|
||||||
self.killTimer(event.timerId())
|
self.killTimer(event.timerId())
|
||||||
if event.timerId() == self.timer:
|
if event.timerId() == self.timer:
|
||||||
@ -833,7 +835,7 @@ class SearchBox(QLineEdit):
|
|||||||
refinement = text.startswith(self.prev_search) and ':' not in text
|
refinement = text.startswith(self.prev_search) and ':' not in text
|
||||||
self.prev_search = text
|
self.prev_search = text
|
||||||
self.emit(SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), text, refinement)
|
self.emit(SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), text, refinement)
|
||||||
|
|
||||||
def set_search_string(self, txt):
|
def set_search_string(self, txt):
|
||||||
self.normalize_state()
|
self.normalize_state()
|
||||||
self.setText(txt)
|
self.setText(txt)
|
||||||
|
@ -4,7 +4,7 @@ import os, sys, textwrap, collections, traceback, time
|
|||||||
from xml.parsers.expat import ExpatError
|
from xml.parsers.expat import ExpatError
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, \
|
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, \
|
||||||
QVariant, QThread, QSize, QUrl
|
QVariant, QThread, QUrl, QSize
|
||||||
from PyQt4.QtGui import QPixmap, QColor, QPainter, QMenu, QIcon, QMessageBox, \
|
from PyQt4.QtGui import QPixmap, QColor, QPainter, QMenu, QIcon, QMessageBox, \
|
||||||
QToolButton, QDialog, QDesktopServices
|
QToolButton, QDialog, QDesktopServices
|
||||||
from PyQt4.QtSvg import QSvgRenderer
|
from PyQt4.QtSvg import QSvgRenderer
|
||||||
@ -1210,4 +1210,14 @@ def main(args=sys.argv):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
sys.exit(main())
|
try:
|
||||||
|
sys.exit(main())
|
||||||
|
except:
|
||||||
|
if not iswindows: raise
|
||||||
|
from PyQt4.QtGui import QErrorMessage
|
||||||
|
logfile = os.path.expanduser('~/calibre.log')
|
||||||
|
if os.path.exists(logfile):
|
||||||
|
log = open(logfile).read()
|
||||||
|
if log.strip():
|
||||||
|
d = QErrorMessage()
|
||||||
|
d.showMessage(log)
|
||||||
|
@ -27,9 +27,9 @@
|
|||||||
<property name="geometry" >
|
<property name="geometry" >
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>86</y>
|
<y>79</y>
|
||||||
<width>865</width>
|
<width>865</width>
|
||||||
<height>712</height>
|
<height>716</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" >
|
<layout class="QGridLayout" >
|
||||||
@ -44,7 +44,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="LocationView" name="location_view" >
|
<widget class="LocationView" name="location_view" >
|
||||||
<property name="sizePolicy" >
|
<property name="sizePolicy" >
|
||||||
<sizepolicy vsizetype="Minimum" hsizetype="Expanding" >
|
<sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
|
||||||
<horstretch>0</horstretch>
|
<horstretch>0</horstretch>
|
||||||
<verstretch>0</verstretch>
|
<verstretch>0</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
@ -52,19 +52,25 @@
|
|||||||
<property name="maximumSize" >
|
<property name="maximumSize" >
|
||||||
<size>
|
<size>
|
||||||
<width>10000</width>
|
<width>10000</width>
|
||||||
<height>100</height>
|
<height>110</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="verticalScrollBarPolicy" >
|
<property name="verticalScrollBarPolicy" >
|
||||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="horizontalScrollBarPolicy" >
|
<property name="horizontalScrollBarPolicy" >
|
||||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
<enum>Qt::ScrollBarAsNeeded</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tabKeyNavigation" >
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="showDropIndicator" stdset="0" >
|
||||||
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="iconSize" >
|
<property name="iconSize" >
|
||||||
<size>
|
<size>
|
||||||
<width>32</width>
|
<width>40</width>
|
||||||
<height>32</height>
|
<height>40</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="movement" >
|
<property name="movement" >
|
||||||
@ -77,14 +83,11 @@
|
|||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="spacing" >
|
<property name="spacing" >
|
||||||
<number>20</number>
|
<number>10</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="viewMode" >
|
<property name="viewMode" >
|
||||||
<enum>QListView::IconMode</enum>
|
<enum>QListView::IconMode</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="uniformItemSizes" >
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
@ -332,8 +335,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>847</width>
|
<width>857</width>
|
||||||
<height>553</height>
|
<height>552</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" >
|
<layout class="QGridLayout" >
|
||||||
@ -380,7 +383,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>865</width>
|
<width>865</width>
|
||||||
<height>86</height>
|
<height>79</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="minimumSize" >
|
<property name="minimumSize" >
|
||||||
@ -425,9 +428,9 @@
|
|||||||
<property name="geometry" >
|
<property name="geometry" >
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>798</y>
|
<y>795</y>
|
||||||
<width>865</width>
|
<width>865</width>
|
||||||
<height>24</height>
|
<height>27</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="mouseTracking" >
|
<property name="mouseTracking" >
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import os, sys, glob
|
import os, sys, glob, shutil
|
||||||
import sipconfig
|
import sipconfig
|
||||||
if os.environ.get('PYQT4PATH', None):
|
if os.environ.get('PYQT4PATH', None):
|
||||||
print os.environ['PYQT4PATH']
|
print os.environ['PYQT4PATH']
|
||||||
@ -37,7 +37,7 @@ makefile = pyqtconfig.QtGuiModuleMakefile (
|
|||||||
# ".dll" extension on Windows).
|
# ".dll" extension on Windows).
|
||||||
if 'linux' in sys.platform:
|
if 'linux' in sys.platform:
|
||||||
for f in glob.glob('../../.build/libpictureflow.a'):
|
for f in glob.glob('../../.build/libpictureflow.a'):
|
||||||
os.link(f, './'+os.path.basename(f))
|
shutil.copyfile(f, os.path.basename(f))
|
||||||
makefile.extra_lib_dirs = ['.']
|
makefile.extra_lib_dirs = ['.']
|
||||||
else:
|
else:
|
||||||
makefile.extra_lib_dirs = ['..\\..\\.build\\release', '../../.build', '.']
|
makefile.extra_lib_dirs = ['..\\..\\.build\\release', '../../.build', '.']
|
||||||
|
@ -7,9 +7,9 @@ import re, os
|
|||||||
from PyQt4.QtGui import QListView, QIcon, QFont, QLabel, QListWidget, \
|
from PyQt4.QtGui import QListView, QIcon, QFont, QLabel, QListWidget, \
|
||||||
QListWidgetItem, QTextCharFormat, QApplication, \
|
QListWidgetItem, QTextCharFormat, QApplication, \
|
||||||
QSyntaxHighlighter, QCursor, QColor, QWidget, \
|
QSyntaxHighlighter, QCursor, QColor, QWidget, \
|
||||||
QAbstractItemDelegate, QPixmap
|
QAbstractItemDelegate, QPixmap, QStyle, QFontMetrics
|
||||||
from PyQt4.QtCore import QAbstractListModel, QVariant, Qt, QRect, SIGNAL, \
|
from PyQt4.QtCore import QAbstractListModel, QVariant, Qt, SIGNAL, \
|
||||||
QObject, QRegExp, QRectF
|
QObject, QRegExp, QString
|
||||||
|
|
||||||
from calibre.gui2.jobs import DetailView
|
from calibre.gui2.jobs import DetailView
|
||||||
from calibre.gui2 import human_readable, NONE, TableView, qstring_to_unicode, error_dialog
|
from calibre.gui2 import human_readable, NONE, TableView, qstring_to_unicode, error_dialog
|
||||||
@ -123,40 +123,43 @@ class LocationDelegate(QAbstractItemDelegate):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
QAbstractItemDelegate.__init__(self)
|
QAbstractItemDelegate.__init__(self)
|
||||||
self.icon_rect = QRect(0, 10, 150, 45)
|
self.pixmap = QPixmap(40, 40)
|
||||||
self.buffer = 5
|
self.text = QString('Reader\n999.9 MB Available202')
|
||||||
|
|
||||||
def get_rects(self, index, option):
|
def rects(self, option):
|
||||||
row = index.row()
|
style = QApplication.style()
|
||||||
irect = QRect(self.icon_rect)
|
font = QFont(option.font)
|
||||||
irect.translate(row*(irect.width()+self.buffer), 0)
|
font.setBold(True)
|
||||||
trect = irect.translated(0, irect.height())
|
irect = style.itemPixmapRect(option.rect, Qt.AlignHCenter|Qt.AlignTop, self.pixmap)
|
||||||
trect.adjust(0, 7, 0, 0)
|
trect = style.itemTextRect(QFontMetrics(font), option.rect,
|
||||||
return irect.adjusted(50, 0, -50, 0), trect
|
Qt.AlignHCenter|Qt.AlignTop, True, self.text)
|
||||||
|
trect.moveTop(irect.bottom())
|
||||||
|
return irect, trect
|
||||||
|
|
||||||
def sizeHint(self, option, index):
|
def sizeHint(self, option, index):
|
||||||
irect, trect = self.get_rects(index, option)
|
irect, trect = self.rects(option)
|
||||||
return irect.united(trect).size()
|
return irect.united(trect).size()
|
||||||
|
|
||||||
def paint(self, painter, option, index):
|
def paint(self, painter, option, index):
|
||||||
font = QFont()
|
style = QApplication.style()
|
||||||
font.setPointSize(9)
|
|
||||||
icon = QIcon(index.model().data(index, Qt.DecorationRole))
|
|
||||||
highlight = getattr(index.model(), 'highlight_row', -1) == index.row()
|
|
||||||
text = index.model().data(index, Qt.DisplayRole).toString()
|
|
||||||
painter.save()
|
painter.save()
|
||||||
irect, trect = self.get_rects(index, option)
|
if hasattr(QStyle, 'CE_ItemViewItem'):
|
||||||
|
QApplication.style().drawControl(QStyle.CE_ItemViewItem, option, painter)
|
||||||
mode = QIcon.Normal
|
highlight = getattr(index.model(), 'highlight_row', -1) == index.row()
|
||||||
if highlight:
|
mode = QIcon.Active if highlight else QIcon.Normal
|
||||||
font.setBold(True)
|
pixmap = QIcon(index.model().data(index, Qt.DecorationRole)).pixmap(self.pixmap.size())
|
||||||
mode = QIcon.Active
|
pixmap = style.generatedIconPixmap(mode, pixmap, option)
|
||||||
|
text = index.model().data(index, Qt.DisplayRole).toString()
|
||||||
|
irect, trect = self.rects(option)
|
||||||
|
style.drawItemPixmap(painter, irect, Qt.AlignHCenter|Qt.AlignTop, pixmap)
|
||||||
|
font = QFont(option.font)
|
||||||
|
font.setBold(highlight)
|
||||||
painter.setFont(font)
|
painter.setFont(font)
|
||||||
icon.paint(painter, irect, Qt.AlignHCenter|Qt.AlignTop, mode, QIcon.On)
|
style.drawItemText(painter, trect, Qt.AlignHCenter|Qt.AlignBottom,
|
||||||
painter.drawText(QRectF(trect), Qt.AlignTop|Qt.AlignHCenter, text)
|
option.palette, True, text)
|
||||||
painter.restore()
|
painter.restore()
|
||||||
|
|
||||||
|
|
||||||
class LocationModel(QAbstractListModel):
|
class LocationModel(QAbstractListModel):
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
QAbstractListModel.__init__(self, parent)
|
QAbstractListModel.__init__(self, parent)
|
||||||
|
@ -15,6 +15,7 @@ from calibre.gui2 import SingleApplication
|
|||||||
from calibre.ebooks.metadata.meta import get_metadata
|
from calibre.ebooks.metadata.meta import get_metadata
|
||||||
from calibre.library.database2 import LibraryDatabase2
|
from calibre.library.database2 import LibraryDatabase2
|
||||||
from calibre.library.database import text_to_tokens
|
from calibre.library.database import text_to_tokens
|
||||||
|
from calibre.ebooks.metadata.opf import OPFCreator, OPFReader
|
||||||
|
|
||||||
FIELDS = set(['title', 'authors', 'publisher', 'rating', 'timestamp', 'size', 'tags', 'comments', 'series', 'series_index', 'formats'])
|
FIELDS = set(['title', 'authors', 'publisher', 'rating', 'timestamp', 'size', 'tags', 'comments', 'series', 'series_index', 'formats'])
|
||||||
|
|
||||||
@ -304,9 +305,67 @@ do nothing.
|
|||||||
do_remove_format(get_db(dbpath, opts), id, fmt)
|
do_remove_format(get_db(dbpath, opts), id, fmt)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
def do_show_metadata(db, id, as_opf):
|
||||||
|
if not db.has_id(id):
|
||||||
|
raise ValueError('Id #%d is not present in database.'%id)
|
||||||
|
mi = db.get_metadata(id, index_is_id=True)
|
||||||
|
if as_opf:
|
||||||
|
mi = OPFCreator(os.getcwd(), mi)
|
||||||
|
mi.render(sys.stdout)
|
||||||
|
else:
|
||||||
|
print mi
|
||||||
|
|
||||||
|
def command_show_metadata(args, dbpath):
|
||||||
|
parser = get_parser(_(
|
||||||
|
'''
|
||||||
|
%prog show_metadata [options] id
|
||||||
|
|
||||||
|
Show the metadata stored in the calibre database for the book identified by id.
|
||||||
|
id is an id number from the list command.
|
||||||
|
'''))
|
||||||
|
parser.add_option('--as-opf', default=False, action='store_true',
|
||||||
|
help=_('Print metadata in OPF form (XML)'))
|
||||||
|
opts, args = parser.parse_args(sys.argv[1:]+args)
|
||||||
|
if len(args) < 2:
|
||||||
|
parser.print_help()
|
||||||
|
print
|
||||||
|
print _('You must specify an id')
|
||||||
|
return 1
|
||||||
|
id = int(args[1])
|
||||||
|
do_show_metadata(get_db(dbpath, opts), id, opts.as_opf)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def do_set_metadata(db, id, stream):
|
||||||
|
mi = OPFReader(stream)
|
||||||
|
db.set_metadata(id, mi)
|
||||||
|
do_show_metadata(db, id, False)
|
||||||
|
if SingleApplication is not None:
|
||||||
|
sa = SingleApplication('calibre GUI')
|
||||||
|
sa.send_message('refreshdb:')
|
||||||
|
|
||||||
|
def command_set_metadata(args, dbpath):
|
||||||
|
parser = get_parser(_(
|
||||||
|
'''
|
||||||
|
%prog set_metadata [options] id /path/to/metadata.opf
|
||||||
|
|
||||||
|
Set the metadata stored in the calibre database for the book identified by id
|
||||||
|
from the OPF file metadata.opf. id is an id number from the list command. You
|
||||||
|
can get a quick feel for the OPF format by using the --as-opf switch to the
|
||||||
|
show_metadata command.
|
||||||
|
'''))
|
||||||
|
opts, args = parser.parse_args(sys.argv[1:]+args)
|
||||||
|
if len(args) < 3:
|
||||||
|
parser.print_help()
|
||||||
|
print
|
||||||
|
print _('You must specify an id and a metadata file')
|
||||||
|
return 1
|
||||||
|
id, opf = int(args[1]), open(args[2], 'rb')
|
||||||
|
do_set_metadata(get_db(dbpath, opts), id, opf)
|
||||||
|
return 0
|
||||||
|
|
||||||
def main(args=sys.argv):
|
def main(args=sys.argv):
|
||||||
commands = ('list', 'add', 'remove', 'add_format', 'remove_format')
|
commands = ('list', 'add', 'remove', 'add_format', 'remove_format',
|
||||||
|
'show_metadata', 'set_metadata')
|
||||||
parser = OptionParser(_(
|
parser = OptionParser(_(
|
||||||
'''\
|
'''\
|
||||||
%%prog command [options] [arguments]
|
%%prog command [options] [arguments]
|
||||||
|
@ -911,13 +911,19 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
|||||||
def title(self, index, index_is_id=False):
|
def title(self, index, index_is_id=False):
|
||||||
if not index_is_id:
|
if not index_is_id:
|
||||||
return self.data[index][1]
|
return self.data[index][1]
|
||||||
return self.conn.execute('SELECT title FROM meta WHERE id=?',(index,)).fetchone()[0]
|
try:
|
||||||
|
return self.conn.execute('SELECT title FROM meta WHERE id=?',(index,)).fetchone()[0]
|
||||||
|
except:
|
||||||
|
return _('Unknown')
|
||||||
|
|
||||||
def authors(self, index, index_is_id=False):
|
def authors(self, index, index_is_id=False):
|
||||||
''' Authors as a comma separated list or None'''
|
''' Authors as a comma separated list or None'''
|
||||||
if not index_is_id:
|
if not index_is_id:
|
||||||
return self.data[index][2]
|
return self.data[index][2]
|
||||||
return self.conn.execute('SELECT authors FROM meta WHERE id=?',(index,)).fetchone()[0]
|
try:
|
||||||
|
return self.conn.execute('SELECT authors FROM meta WHERE id=?',(index,)).fetchone()[0]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
def isbn(self, idx, index_is_id=False):
|
def isbn(self, idx, index_is_id=False):
|
||||||
id = idx if index_is_id else self.id(idx)
|
id = idx if index_is_id else self.id(idx)
|
||||||
@ -929,22 +935,22 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
|||||||
|
|
||||||
def publisher(self, index, index_is_id=False):
|
def publisher(self, index, index_is_id=False):
|
||||||
if index_is_id:
|
if index_is_id:
|
||||||
return self.conn.execute('SELECT publisher FROM meta WHERE id=?', (id,)).fetchone()[0]
|
return self.conn.execute('SELECT publisher FROM meta WHERE id=?', (index,)).fetchone()[0]
|
||||||
return self.data[index][3]
|
return self.data[index][3]
|
||||||
|
|
||||||
def rating(self, index, index_is_id=False):
|
def rating(self, index, index_is_id=False):
|
||||||
if index_is_id:
|
if index_is_id:
|
||||||
return self.conn.execute('SELECT rating FROM meta WHERE id=?', (id,)).fetchone()[0]
|
return self.conn.execute('SELECT rating FROM meta WHERE id=?', (index,)).fetchone()[0]
|
||||||
return self.data[index][4]
|
return self.data[index][4]
|
||||||
|
|
||||||
def timestamp(self, index, index_is_id=False):
|
def timestamp(self, index, index_is_id=False):
|
||||||
if index_is_id:
|
if index_is_id:
|
||||||
return self.conn.execute('SELECT timestamp FROM meta WHERE id=?', (id,)).fetchone()[0]
|
return self.conn.execute('SELECT timestamp FROM meta WHERE id=?', (index,)).fetchone()[0]
|
||||||
return self.data[index][5]
|
return self.data[index][5]
|
||||||
|
|
||||||
def max_size(self, index, index_is_id=False):
|
def max_size(self, index, index_is_id=False):
|
||||||
if index_is_id:
|
if index_is_id:
|
||||||
return self.conn.execute('SELECT size FROM meta WHERE id=?', (id,)).fetchone()[0]
|
return self.conn.execute('SELECT size FROM meta WHERE id=?', (index,)).fetchone()[0]
|
||||||
return self.data[index][6]
|
return self.data[index][6]
|
||||||
|
|
||||||
def cover(self, index, index_is_id=False):
|
def cover(self, index, index_is_id=False):
|
||||||
@ -1251,6 +1257,8 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
|||||||
'''
|
'''
|
||||||
Set metadata for the book C{id} from the L{MetaInformation} object C{mi}
|
Set metadata for the book C{id} from the L{MetaInformation} object C{mi}
|
||||||
'''
|
'''
|
||||||
|
if mi.title:
|
||||||
|
self.set_title(id, mi.title)
|
||||||
if not mi.authors:
|
if not mi.authors:
|
||||||
mi.authors = ['Unknown']
|
mi.authors = ['Unknown']
|
||||||
authors = []
|
authors = []
|
||||||
@ -1516,6 +1524,9 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
|||||||
def has_book(self, mi):
|
def has_book(self, mi):
|
||||||
return bool(self.conn.execute('SELECT id FROM books where title=?', (mi.title,)).fetchone())
|
return bool(self.conn.execute('SELECT id FROM books where title=?', (mi.title,)).fetchone())
|
||||||
|
|
||||||
|
def has_id(self, id):
|
||||||
|
return self.conn.execute('SELECT id FROM books where id=?', (id,)).fetchone() is not None
|
||||||
|
|
||||||
def recursive_import(self, root, single_book_per_directory=True):
|
def recursive_import(self, root, single_book_per_directory=True):
|
||||||
root = os.path.abspath(root)
|
root = os.path.abspath(root)
|
||||||
duplicates = []
|
duplicates = []
|
||||||
|
@ -131,7 +131,7 @@ Why does |app| show only some of my fonts on OS X?
|
|||||||
|
|
||||||
The graphical user interface of |app| is not starting on Windows?
|
The graphical user interface of |app| is not starting on Windows?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
If you've never used the graphical user interface before, try deleting the file library1.db (it will be somewhere under :file:`C:\\Documents and Settings` on Windows XP and :file:`C:\\Users` on Windows Vista. If that doesn't fix the problem, locate the file libprs500.log (in the same places as library1.db) and post its contents in a help message on the `Forums <http://calibre.kovidgoyal.net/discussion>`_.
|
If you've never used the graphical user interface before, try deleting the file library1.db (it will be somewhere under :file:`C:\\Documents and Settings` on Windows XP and :file:`C:\\Users` on Windows Vista. If that doesn't fix the problem, locate the file calibre.log (in the same places as library1.db) and post its contents in a help message on the `Forums <http://calibre.kovidgoyal.net/discussion>`_.
|
||||||
|
|
||||||
I want some feature added to |app|. What can I do?
|
I want some feature added to |app|. What can I do?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -13,7 +13,7 @@ msgstr ""
|
|||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"X-Launchpad-Export-Date: 2008-06-14 07:16+0000\n"
|
"X-Launchpad-Export-Date: 2008-06-15 22:20+0000\n"
|
||||||
"X-Generator: Launchpad (build Unknown)\n"
|
"X-Generator: Launchpad (build Unknown)\n"
|
||||||
"Generated-By: pygettext.py 1.5\n"
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ msgstr ""
|
|||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"X-Launchpad-Export-Date: 2008-06-14 07:16+0000\n"
|
"X-Launchpad-Export-Date: 2008-06-15 22:20+0000\n"
|
||||||
"X-Generator: Launchpad (build Unknown)\n"
|
"X-Generator: Launchpad (build Unknown)\n"
|
||||||
|
|
||||||
#~ msgid ""
|
#~ msgid ""
|
||||||
|
@ -14,7 +14,7 @@ msgstr ""
|
|||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"X-Launchpad-Export-Date: 2008-06-14 07:16+0000\n"
|
"X-Launchpad-Export-Date: 2008-06-15 22:20+0000\n"
|
||||||
"X-Generator: Launchpad (build Unknown)\n"
|
"X-Generator: Launchpad (build Unknown)\n"
|
||||||
"Generated-By: pygettext.py 1.5\n"
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
@ -11,13 +11,13 @@ msgstr ""
|
|||||||
"Project-Id-Version: es\n"
|
"Project-Id-Version: es\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2008-06-12 20:18+0000\n"
|
"POT-Creation-Date: 2008-06-12 20:18+0000\n"
|
||||||
"PO-Revision-Date: 2008-06-12 22:40+0000\n"
|
"PO-Revision-Date: 2008-06-15 08:31+0000\n"
|
||||||
"Last-Translator: S. Dorscht <Unknown>\n"
|
"Last-Translator: S. Dorscht <Unknown>\n"
|
||||||
"Language-Team: Spanish\n"
|
"Language-Team: Spanish\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"X-Launchpad-Export-Date: 2008-06-14 07:16+0000\n"
|
"X-Launchpad-Export-Date: 2008-06-15 22:20+0000\n"
|
||||||
"X-Generator: Launchpad (build Unknown)\n"
|
"X-Generator: Launchpad (build Unknown)\n"
|
||||||
|
|
||||||
#~ msgid ""
|
#~ msgid ""
|
||||||
@ -151,7 +151,7 @@ msgstr "Creado por "
|
|||||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:146
|
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:146
|
||||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:174
|
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:174
|
||||||
msgid "Unable to detect the %s disk drive. Try rebooting."
|
msgid "Unable to detect the %s disk drive. Try rebooting."
|
||||||
msgstr ""
|
msgstr "No se ha podido detectar la unidad de disco %s. Trate de reiniciar."
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:73
|
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:73
|
||||||
msgid "Set the title. Default: filename."
|
msgid "Set the title. Default: filename."
|
||||||
@ -671,7 +671,6 @@ msgid "Creating XML..."
|
|||||||
msgstr "Creando XML..."
|
msgstr "Creando XML..."
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:156
|
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:156
|
||||||
#, fuzzy
|
|
||||||
msgid "LRS written to "
|
msgid "LRS written to "
|
||||||
msgstr "LRS escrito en "
|
msgstr "LRS escrito en "
|
||||||
|
|
||||||
@ -1130,7 +1129,6 @@ msgid "Show &text in toolbar buttons"
|
|||||||
msgstr "Mostrar &texto en los botones de la barra de herramientas"
|
msgstr "Mostrar &texto en los botones de la barra de herramientas"
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:219
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:219
|
||||||
#, fuzzy
|
|
||||||
msgid "Free unused diskspace from the database"
|
msgid "Free unused diskspace from the database"
|
||||||
msgstr "Espacio de disco disponible de la base de datos"
|
msgstr "Espacio de disco disponible de la base de datos"
|
||||||
|
|
||||||
@ -1896,18 +1894,16 @@ msgstr ""
|
|||||||
"actual"
|
"actual"
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:63
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:63
|
||||||
#, fuzzy
|
|
||||||
msgid "No recipe selected"
|
msgid "No recipe selected"
|
||||||
msgstr "No hay ninguna receta seleccionada"
|
msgstr "No hay ninguna receta seleccionada"
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:69
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:69
|
||||||
#, fuzzy
|
|
||||||
msgid "The attached file: %s is a recipe to download %s."
|
msgid "The attached file: %s is a recipe to download %s."
|
||||||
msgstr "el archivo adjunto: %s es una receta para descargar %s"
|
msgstr "El archivo adjunto: %s es una receta para descargar %s"
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:70
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:70
|
||||||
msgid "Recipe for "
|
msgid "Recipe for "
|
||||||
msgstr ""
|
msgstr "Receta para "
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:86
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:86
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:96
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:96
|
||||||
@ -1941,9 +1937,8 @@ msgid "Already exists"
|
|||||||
msgstr "Ya existe"
|
msgstr "Ya existe"
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:121
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:121
|
||||||
#, fuzzy
|
|
||||||
msgid "This feed has already been added to the recipe"
|
msgid "This feed has already been added to the recipe"
|
||||||
msgstr "el Feed ya se ha añadido a la receta"
|
msgstr "Este Feed ya se ha añadido a la receta"
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:162
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:162
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:171
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:171
|
||||||
@ -1968,9 +1963,8 @@ msgid "A custom recipe named %s already exists. Do you want to replace it?"
|
|||||||
msgstr "una receta personalizada llamada %s ya existe. Quiere reemplazarla?"
|
msgstr "una receta personalizada llamada %s ya existe. Quiere reemplazarla?"
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:187
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:187
|
||||||
#, fuzzy
|
|
||||||
msgid "Choose a recipe file"
|
msgid "Choose a recipe file"
|
||||||
msgstr "Seleccionarr un archivo de receta"
|
msgstr "Seleccionar un archivo de receta"
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:187
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:187
|
||||||
msgid "Recipes"
|
msgid "Recipes"
|
||||||
@ -2651,6 +2645,9 @@ msgid ""
|
|||||||
"href=\"http://calibre.kovidgoyal.net/wiki/Changelog\">new features</a>. "
|
"href=\"http://calibre.kovidgoyal.net/wiki/Changelog\">new features</a>. "
|
||||||
"Visit the download page?"
|
"Visit the download page?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"%s se ha actualizado a la versión %s. Ver las <a "
|
||||||
|
"href=\"http://calibre.kovidgoyal.net/wiki/Changelog\">nuevas "
|
||||||
|
"características</a>. Visita la página de descarga?"
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1155
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1155
|
||||||
msgid "Update available"
|
msgid "Update available"
|
||||||
@ -2833,6 +2830,8 @@ msgid ""
|
|||||||
"Path to the calibre database. Default is to use the path stored in the "
|
"Path to the calibre database. Default is to use the path stored in the "
|
||||||
"settings."
|
"settings."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Camino a la base de datos calibre. El valor predeterminado es a usar la ruta "
|
||||||
|
"almacenada en la configuración."
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:80
|
#: /home/kovid/work/calibre/src/calibre/library/cli.py:80
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -2840,6 +2839,9 @@ msgid ""
|
|||||||
"\n"
|
"\n"
|
||||||
"List the books available in the calibre database. \n"
|
"List the books available in the calibre database. \n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"%prog list [options]\n"
|
||||||
|
"\n"
|
||||||
|
"Mostrar los libros disponibles en la base de datos calibre. \n"
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:88
|
#: /home/kovid/work/calibre/src/calibre/library/cli.py:88
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -2866,6 +2868,9 @@ msgid ""
|
|||||||
"please see the search related documentation in the User Manual. Default is "
|
"please see the search related documentation in the User Manual. Default is "
|
||||||
"to do no filtering."
|
"to do no filtering."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Filtrar los resultados de la consulta de búsqueda. Para el formato de la "
|
||||||
|
"consulta de búsqueda consulte la documentación relacionada con la búsqueda "
|
||||||
|
"en el Manual del usuario. El valor predeterminado es a no hacer el filtrado."
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:101
|
#: /home/kovid/work/calibre/src/calibre/library/cli.py:101
|
||||||
msgid "Invalid fields. Available fields:"
|
msgid "Invalid fields. Available fields:"
|
||||||
@ -2891,12 +2896,20 @@ msgid ""
|
|||||||
"directories, see\n"
|
"directories, see\n"
|
||||||
"the directory related options below. \n"
|
"the directory related options below. \n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"%prog add [options] file1 file2 file3 ...\n"
|
||||||
|
"\n"
|
||||||
|
"Añadir los archivos especificados como libros a la base de datos. También "
|
||||||
|
"puede especificar\n"
|
||||||
|
"directorios, consulte las opciones relacionadas a los directorios más abajo. "
|
||||||
|
"\n"
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:204
|
#: /home/kovid/work/calibre/src/calibre/library/cli.py:204
|
||||||
msgid ""
|
msgid ""
|
||||||
"Assume that each directory has only a single logical book and that all files "
|
"Assume that each directory has only a single logical book and that all files "
|
||||||
"in it are different e-book formats of that book"
|
"in it are different e-book formats of that book"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Supongamos que cada directorio tiene un solo libro lógico y que todos los "
|
||||||
|
"archivos en este directorio son diferentes formatos de este libro"
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:206
|
#: /home/kovid/work/calibre/src/calibre/library/cli.py:206
|
||||||
msgid "Process directories recursively"
|
msgid "Process directories recursively"
|
||||||
@ -2922,6 +2935,12 @@ msgid ""
|
|||||||
"separated list of id numbers (you can get id numbers by using the list "
|
"separated list of id numbers (you can get id numbers by using the list "
|
||||||
"command). For example, 23,34,57-85\n"
|
"command). For example, 23,34,57-85\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"%prog remove ids\n"
|
||||||
|
"\n"
|
||||||
|
"Eliminar los libros identificados por ID de la base de datos. ID debe ser "
|
||||||
|
"una lista separada por comas de números de identificación (se puede obtener "
|
||||||
|
"números de identificación utilizando el commando \"list\"). Por ejemplo, "
|
||||||
|
"23,34,57-85\n"
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:243
|
#: /home/kovid/work/calibre/src/calibre/library/cli.py:243
|
||||||
msgid "You must specify at least one book to remove"
|
msgid "You must specify at least one book to remove"
|
||||||
@ -2953,6 +2972,13 @@ msgid ""
|
|||||||
"by using the list command. fmt should be a file extension like LRF or TXT or "
|
"by using the list command. fmt should be a file extension like LRF or TXT or "
|
||||||
"EPUB. If the logical book does not have fmt available, do nothing.\n"
|
"EPUB. If the logical book does not have fmt available, do nothing.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"\n"
|
||||||
|
"%prog remove_format [options] id fmt\n"
|
||||||
|
"\n"
|
||||||
|
"Eliminar el formato fmt del libro lógico identificado por id. Usted puede "
|
||||||
|
"obtener id utilizando el comando \"list\". fmt debe ser una extensión de "
|
||||||
|
"archivo como LRF o TXT o EPUB. Si el libro lógico no tiene fmt disponible, "
|
||||||
|
"no hacer nada.\n"
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:300
|
#: /home/kovid/work/calibre/src/calibre/library/cli.py:300
|
||||||
msgid "You must specify an id and a format"
|
msgid "You must specify an id and a format"
|
||||||
@ -2976,11 +3002,11 @@ msgstr "Trabajo detenido por el usuario"
|
|||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/utils/fontconfig.py:124
|
#: /home/kovid/work/calibre/src/calibre/utils/fontconfig.py:124
|
||||||
msgid "Could not initialize the fontconfig library"
|
msgid "Could not initialize the fontconfig library"
|
||||||
msgstr ""
|
msgstr "No se ha podido inicializar la biblioteca fontconfig"
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/utils/sftp.py:53
|
#: /home/kovid/work/calibre/src/calibre/utils/sftp.py:53
|
||||||
msgid "URL must have the scheme sftp"
|
msgid "URL must have the scheme sftp"
|
||||||
msgstr ""
|
msgstr "La URL debe tener el régimen de sftp"
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/utils/sftp.py:57
|
#: /home/kovid/work/calibre/src/calibre/utils/sftp.py:57
|
||||||
msgid "host must be of the form user@hostname"
|
msgid "host must be of the form user@hostname"
|
||||||
@ -2988,11 +3014,11 @@ msgstr ""
|
|||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/utils/sftp.py:68
|
#: /home/kovid/work/calibre/src/calibre/utils/sftp.py:68
|
||||||
msgid "Failed to negotiate SSH session: "
|
msgid "Failed to negotiate SSH session: "
|
||||||
msgstr ""
|
msgstr "No se ha podido negociar período de sesiones SSH: "
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/utils/sftp.py:71
|
#: /home/kovid/work/calibre/src/calibre/utils/sftp.py:71
|
||||||
msgid "Failed to authenticate with server: %s"
|
msgid "Failed to authenticate with server: %s"
|
||||||
msgstr ""
|
msgstr "No se ha podido autenticar con el servidor: %s"
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:56
|
#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:56
|
||||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:77
|
#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:77
|
||||||
|
@ -13,7 +13,7 @@ msgstr ""
|
|||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"X-Launchpad-Export-Date: 2008-06-14 07:16+0000\n"
|
"X-Launchpad-Export-Date: 2008-06-15 22:20+0000\n"
|
||||||
"X-Generator: Launchpad (build Unknown)\n"
|
"X-Generator: Launchpad (build Unknown)\n"
|
||||||
"Generated-By: pygettext.py 1.5\n"
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ msgstr ""
|
|||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"X-Launchpad-Export-Date: 2008-06-14 07:16+0000\n"
|
"X-Launchpad-Export-Date: 2008-06-15 22:20+0000\n"
|
||||||
"X-Generator: Launchpad (build Unknown)\n"
|
"X-Generator: Launchpad (build Unknown)\n"
|
||||||
"Generated-By: pygettext.py 1.5\n"
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ msgstr ""
|
|||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"X-Launchpad-Export-Date: 2008-06-14 07:16+0000\n"
|
"X-Launchpad-Export-Date: 2008-06-15 22:20+0000\n"
|
||||||
"X-Generator: Launchpad (build Unknown)\n"
|
"X-Generator: Launchpad (build Unknown)\n"
|
||||||
"Generated-By: pygettext.py 1.5\n"
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ msgstr ""
|
|||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"X-Launchpad-Export-Date: 2008-06-14 07:16+0000\n"
|
"X-Launchpad-Export-Date: 2008-06-15 22:20+0000\n"
|
||||||
"X-Generator: Launchpad (build Unknown)\n"
|
"X-Generator: Launchpad (build Unknown)\n"
|
||||||
|
|
||||||
#~ msgid ""
|
#~ msgid ""
|
||||||
|
@ -13,7 +13,7 @@ msgstr ""
|
|||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"X-Launchpad-Export-Date: 2008-06-14 07:16+0000\n"
|
"X-Launchpad-Export-Date: 2008-06-15 22:20+0000\n"
|
||||||
"X-Generator: Launchpad (build Unknown)\n"
|
"X-Generator: Launchpad (build Unknown)\n"
|
||||||
"Generated-By: pygettext.py 1.5\n"
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ msgstr ""
|
|||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"X-Launchpad-Export-Date: 2008-06-14 07:16+0000\n"
|
"X-Launchpad-Export-Date: 2008-06-15 22:20+0000\n"
|
||||||
"X-Generator: Launchpad (build Unknown)\n"
|
"X-Generator: Launchpad (build Unknown)\n"
|
||||||
"Generated-By: pygettext.py 1.5\n"
|
"Generated-By: pygettext.py 1.5\n"
|
||||||
|
|
||||||
|
@ -109,6 +109,8 @@ class Feed(object):
|
|||||||
if id in self.added_articles:
|
if id in self.added_articles:
|
||||||
return
|
return
|
||||||
published = item.get('date_parsed', time.gmtime())
|
published = item.get('date_parsed', time.gmtime())
|
||||||
|
if not published:
|
||||||
|
published = time.gmtime()
|
||||||
self.id_counter += 1
|
self.id_counter += 1
|
||||||
self.added_articles.append(id)
|
self.added_articles.append(id)
|
||||||
|
|
||||||
|
234
upload.py
234
upload.py
@ -1,13 +1,19 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
import sys, os, shutil, time, tempfile, socket
|
import sys, os, shutil, time, tempfile, socket, fcntl, struct
|
||||||
sys.path.append('src')
|
sys.path.append('src')
|
||||||
import subprocess
|
import subprocess
|
||||||
from subprocess import check_call as _check_call
|
from subprocess import check_call as _check_call
|
||||||
from functools import partial
|
from functools import partial
|
||||||
#from pyvix.vix import Host, VIX_SERVICEPROVIDER_VMWARE_WORKSTATION
|
#from pyvix.vix import Host, VIX_SERVICEPROVIDER_VMWARE_WORKSTATION
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
def get_ip_address(ifname):
|
||||||
s.connect(('google.com', 0))
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
HOST=s.getsockname()[0]
|
return socket.inet_ntoa(fcntl.ioctl(
|
||||||
|
s.fileno(),
|
||||||
|
0x8915, # SIOCGIFADDR
|
||||||
|
struct.pack('256s', ifname[:15])
|
||||||
|
)[20:24])
|
||||||
|
|
||||||
|
HOST=get_ip_address('eth0')
|
||||||
PROJECT=os.path.basename(os.getcwd())
|
PROJECT=os.path.basename(os.getcwd())
|
||||||
|
|
||||||
from calibre import __version__, __appname__
|
from calibre import __version__, __appname__
|
||||||
@ -21,11 +27,12 @@ TXT2LRF = "src/calibre/ebooks/lrf/txt/demo"
|
|||||||
BUILD_SCRIPT ='''\
|
BUILD_SCRIPT ='''\
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
cd ~/build && \
|
cd ~/build && \
|
||||||
rsync -avz --exclude docs --exclude .bzr --exclude .build --exclude build --exclude dist --exclude "*.pyc" --exclude "*.pyo" rsync://%(host)s/work/%(project)s . && \
|
rsync -avz --exclude src/calibre/plugins --exclude docs --exclude .bzr --exclude .build --exclude build --exclude dist --exclude "*.pyc" --exclude "*.pyo" rsync://%(host)s/work/%(project)s . && \
|
||||||
cd %(project)s && \
|
cd %(project)s && \
|
||||||
mkdir -p build dist && \
|
mkdir -p build dist src/calibre/plugins && \
|
||||||
|
%%s && \
|
||||||
rm -rf build/* dist/* && \
|
rm -rf build/* dist/* && \
|
||||||
python %%s
|
%%s %%s
|
||||||
'''%dict(host=HOST, project=PROJECT)
|
'''%dict(host=HOST, project=PROJECT)
|
||||||
check_call = partial(_check_call, shell=True)
|
check_call = partial(_check_call, shell=True)
|
||||||
#h = Host(hostType=VIX_SERVICEPROVIDER_VMWARE_WORKSTATION)
|
#h = Host(hostType=VIX_SERVICEPROVIDER_VMWARE_WORKSTATION)
|
||||||
@ -41,22 +48,24 @@ def installer_name(ext):
|
|||||||
return 'dist/%s-%s.%s'%(__appname__, __version__, ext)
|
return 'dist/%s-%s.%s'%(__appname__, __version__, ext)
|
||||||
return 'dist/%s-%s-i686.%s'%(__appname__, __version__, ext)
|
return 'dist/%s-%s-i686.%s'%(__appname__, __version__, ext)
|
||||||
|
|
||||||
def start_vm(vm, ssh_host, build_script, sleep):
|
def start_vm(vm, ssh_host, build_script, sleep=75):
|
||||||
vmware = ('vmware', '-q', '-x', '-n', vm)
|
vmware = ('vmware', '-q', '-x', '-n', vm)
|
||||||
subprocess.Popen(vmware)
|
subprocess.Popen(vmware)
|
||||||
t = tempfile.NamedTemporaryFile(suffix='.sh')
|
t = tempfile.NamedTemporaryFile(suffix='.sh')
|
||||||
t.write(build_script)
|
t.write(build_script)
|
||||||
t.flush()
|
t.flush()
|
||||||
print 'Waiting for VM to startup'
|
print 'Waiting for VM to startup'
|
||||||
time.sleep(sleep)
|
while subprocess.call('ping -q -c1 '+ssh_host, shell=True, stdout=open('/dev/null', 'w')) != 0:
|
||||||
|
time.sleep(5)
|
||||||
|
time.sleep(20)
|
||||||
print 'Trying to SSH into VM'
|
print 'Trying to SSH into VM'
|
||||||
subprocess.check_call(('scp', t.name, ssh_host+':build-'+PROJECT))
|
subprocess.check_call(('scp', t.name, ssh_host+':build-'+PROJECT))
|
||||||
|
subprocess.check_call('ssh -t %s bash build-%s'%(ssh_host, PROJECT), shell=True)
|
||||||
|
|
||||||
def build_windows():
|
def build_windows():
|
||||||
installer = installer_name('exe')
|
installer = installer_name('exe')
|
||||||
vm = '/vmware/Windows XP/Windows XP Professional.vmx'
|
vm = '/vmware/Windows XP/Windows XP Professional.vmx'
|
||||||
start_vm(vm, 'windows', BUILD_SCRIPT%'windows_installer.py', 75)
|
start_vm(vm, 'windows', BUILD_SCRIPT%('python setup.py develop', 'python','windows_installer.py'))
|
||||||
subprocess.check_call(('ssh', 'windows', '/bin/bash', '~/build-'+PROJECT))
|
|
||||||
subprocess.check_call(('scp', 'windows:build/%s/dist/*.exe'%PROJECT, 'dist'))
|
subprocess.check_call(('scp', 'windows:build/%s/dist/*.exe'%PROJECT, 'dist'))
|
||||||
if not os.path.exists(installer):
|
if not os.path.exists(installer):
|
||||||
raise Exception('Failed to build installer '+installer)
|
raise Exception('Failed to build installer '+installer)
|
||||||
@ -66,160 +75,24 @@ def build_windows():
|
|||||||
def build_osx():
|
def build_osx():
|
||||||
installer = installer_name('dmg')
|
installer = installer_name('dmg')
|
||||||
vm = '/vmware/Mac OSX/Mac OSX.vmx'
|
vm = '/vmware/Mac OSX/Mac OSX.vmx'
|
||||||
vmware = ('vmware', '-q', '-x', '-n', vm)
|
python = '/Library/Frameworks/Python.framework/Versions/Current/bin/python'
|
||||||
start_vm(vm, 'osx', BUILD_SCRIPT%'osx_installer.py', 120)
|
start_vm(vm, 'osx', BUILD_SCRIPT%('sudo %s setup.py develop'%python, python, 'osx_installer.py'))
|
||||||
subprocess.check_call(('ssh', 'osx', '/bin/bash', '~/build-'+PROJECT))
|
subprocess.check_call(('scp', 'osx:build/%s/dist/*.dmg'%PROJECT, 'dist'))
|
||||||
subprocess.check_call(('scp', 'windows:build/%s/dist/*.dmg'%PROJECT, 'dist'))
|
|
||||||
if not os.path.exists(installer):
|
if not os.path.exists(installer):
|
||||||
raise Exception('Failed to build installer '+installer)
|
raise Exception('Failed to build installer '+installer)
|
||||||
subprocess.Popen(('ssh', 'osx', 'sudo', '/sbin/shutdown', '-h', 'now'))
|
subprocess.Popen(('ssh', 'osx', 'sudo', '/sbin/shutdown', '-h', 'now'))
|
||||||
return os.path.basename(installer)
|
return os.path.basename(installer)
|
||||||
|
|
||||||
def _build_linux():
|
|
||||||
cwd = os.getcwd()
|
|
||||||
tbz2 = os.path.join(cwd, installer_name('tar.bz2'))
|
|
||||||
SPEC="""\
|
|
||||||
import os
|
|
||||||
HOME = '%(home)s'
|
|
||||||
PYINSTALLER = os.path.expanduser('~/build/pyinstaller')
|
|
||||||
CALIBREPREFIX = HOME+'/work/%(project)s'
|
|
||||||
CLIT = '/usr/bin/clit'
|
|
||||||
PDFTOHTML = '/usr/bin/pdftohtml'
|
|
||||||
LIBUNRAR = '/usr/lib/libunrar.so'
|
|
||||||
QTDIR = '/usr/lib/qt4'
|
|
||||||
QTDLLS = ('QtCore', 'QtGui', 'QtNetwork', 'QtSvg', 'QtXml')
|
|
||||||
EXTRAS = ('/usr/lib/python2.5/site-packages/PIL', os.path.expanduser('~/ipython/IPython'))
|
|
||||||
|
|
||||||
import glob, sys, subprocess, tarfile
|
|
||||||
CALIBRESRC = os.path.join(CALIBREPREFIX, 'src')
|
|
||||||
CALIBREPLUGINS = os.path.join(CALIBRESRC, 'calibre', 'plugins')
|
|
||||||
|
|
||||||
subprocess.check_call(('/usr/bin/sudo', 'chown', '-R', 'kovid:users', glob.glob('/usr/lib/python*/site-packages/')[-1]))
|
|
||||||
subprocess.check_call('rm -rf %%(py)s/dist/* %%(py)s/build/*'%%dict(py=PYINSTALLER), shell=True)
|
|
||||||
|
|
||||||
|
|
||||||
loader = os.path.join('/tmp', 'calibre_installer_loader.py')
|
|
||||||
if not os.path.exists(loader):
|
|
||||||
open(loader, 'wb').write('''
|
|
||||||
import sys, os
|
|
||||||
sys.frozen_path = os.getcwd()
|
|
||||||
os.chdir(os.environ.get("ORIGWD", "."))
|
|
||||||
sys.path.insert(0, os.path.join(sys.frozen_path, "library.pyz"))
|
|
||||||
sys.path.insert(0, sys.frozen_path)
|
|
||||||
from PyQt4.QtCore import QCoreApplication
|
|
||||||
QCoreApplication.setLibraryPaths([sys.frozen_path, os.path.join(sys.frozen_path, "plugins")])
|
|
||||||
''')
|
|
||||||
excludes = ['gtk._gtk', 'gtk.glade', 'qt', 'matplotlib.nxutils', 'matplotlib._cntr',
|
|
||||||
'matplotlib.ttconv', 'matplotlib._image', 'matplotlib.ft2font',
|
|
||||||
'matplotlib._transforms', 'matplotlib._agg', 'matplotlib.backends._backend_agg',
|
|
||||||
'matplotlib.axes', 'matplotlib', 'matplotlib.pyparsing',
|
|
||||||
'TKinter', 'atk', 'gobject._gobject', 'pango', 'PIL', 'Image', 'IPython']
|
|
||||||
temp = ['keyword', 'codeop']
|
|
||||||
|
|
||||||
recipes = ['calibre', 'web', 'feeds', 'recipes']
|
|
||||||
prefix = '.'.join(recipes)+'.'
|
|
||||||
for f in glob.glob(os.path.join(CALIBRESRC, *(recipes+['*.py']))):
|
|
||||||
temp.append(prefix + os.path.basename(f).partition('.')[0])
|
|
||||||
hook = '/tmp/hook-calibre.py'
|
|
||||||
open(hook, 'wb').write('hiddenimports = %%s'%%repr(temp) + '\\n')
|
|
||||||
|
|
||||||
sys.path.insert(0, CALIBRESRC)
|
|
||||||
from calibre.linux import entry_points
|
|
||||||
|
|
||||||
executables, scripts = ['calibre_postinstall', 'parallel'], \
|
|
||||||
[os.path.join(CALIBRESRC, 'calibre', 'linux.py'), os.path.join(CALIBRESRC, 'calibre', 'parallel.py')]
|
|
||||||
|
|
||||||
for entry in entry_points['console_scripts'] + entry_points['gui_scripts']:
|
|
||||||
fields = entry.split('=')
|
|
||||||
executables.append(fields[0].strip())
|
|
||||||
scripts.append(os.path.join(CALIBRESRC, *map(lambda x: x.strip(), fields[1].split(':')[0].split('.')))+'.py')
|
|
||||||
|
|
||||||
recipes = Analysis(glob.glob(os.path.join(CALIBRESRC, 'calibre', 'web', 'feeds', 'recipes', '*.py')),
|
|
||||||
pathex=[CALIBRESRC], hookspath=[os.path.dirname(hook)], excludes=excludes)
|
|
||||||
analyses = [Analysis([os.path.join(HOMEPATH,'support/_mountzlib.py'), os.path.join(HOMEPATH,'support/useUnicode.py'), loader, script],
|
|
||||||
pathex=[PYINSTALLER, CALIBRESRC, CALIBREPLUGINS], excludes=excludes) for script in scripts]
|
|
||||||
|
|
||||||
pyz = TOC()
|
|
||||||
binaries = TOC()
|
|
||||||
|
|
||||||
for a in analyses:
|
|
||||||
pyz = a.pure + pyz
|
|
||||||
binaries = a.binaries + binaries
|
|
||||||
pyz = PYZ(pyz + recipes.pure, name='library.pyz')
|
|
||||||
|
|
||||||
built_executables = []
|
|
||||||
for script, exe, a in zip(scripts, executables, analyses):
|
|
||||||
built_executables.append(EXE(PYZ(TOC()),
|
|
||||||
a.scripts+[('O','','OPTION'),],
|
|
||||||
exclude_binaries=1,
|
|
||||||
name=os.path.join('buildcalibre', exe),
|
|
||||||
debug=False,
|
|
||||||
strip=True,
|
|
||||||
upx=False,
|
|
||||||
excludes=excludes,
|
|
||||||
console=1))
|
|
||||||
|
|
||||||
print 'Adding plugins...'
|
|
||||||
for f in glob.glob(os.path.join(CALIBREPLUGINS, '*.so')):
|
|
||||||
binaries += [(os.path.basename(f), f, 'BINARY')]
|
|
||||||
|
|
||||||
print 'Adding external programs...'
|
|
||||||
binaries += [('clit', CLIT, 'BINARY'), ('pdftohtml', PDFTOHTML, 'BINARY'),
|
|
||||||
('libunrar.so', LIBUNRAR, 'BINARY')]
|
|
||||||
qt = []
|
|
||||||
for dll in QTDLLS:
|
|
||||||
path = os.path.join(QTDIR, 'lib'+dll+'.so.4')
|
|
||||||
qt.append((os.path.basename(path), path, 'BINARY'))
|
|
||||||
binaries += qt
|
|
||||||
|
|
||||||
plugins = []
|
|
||||||
plugdir = os.path.join(QTDIR, 'plugins')
|
|
||||||
for dirpath, dirnames, filenames in os.walk(plugdir):
|
|
||||||
for f in filenames:
|
|
||||||
if not f.endswith('.so') or 'designer' in dirpath or 'codcs' in dirpath or 'sqldrivers' in dirpath : continue
|
|
||||||
f = os.path.join(dirpath, f)
|
|
||||||
plugins.append(('plugins/'+f.replace(plugdir, ''), f, 'BINARY'))
|
|
||||||
binaries += plugins
|
|
||||||
|
|
||||||
manifest = '/tmp/manifest'
|
|
||||||
open(manifest, 'wb').write('\\n'.join(executables))
|
|
||||||
from calibre import __version__
|
|
||||||
version = '/tmp/version'
|
|
||||||
open(version, 'wb').write(__version__)
|
|
||||||
coll = COLLECT(binaries, pyz, [('manifest', manifest, 'DATA'), ('version', version, 'DATA')],
|
|
||||||
*built_executables,
|
|
||||||
**dict(strip=True,
|
|
||||||
upx=False,
|
|
||||||
excludes=excludes,
|
|
||||||
name='dist'))
|
|
||||||
|
|
||||||
os.chdir(os.path.join(HOMEPATH, 'calibre', 'dist'))
|
|
||||||
for folder in EXTRAS:
|
|
||||||
subprocess.check_call('cp -rf %%s .'%%folder, shell=True)
|
|
||||||
|
|
||||||
print 'Building tarball...'
|
|
||||||
tf = tarfile.open('%(tarfile)s', 'w:bz2')
|
|
||||||
|
|
||||||
for f in os.listdir('.'):
|
|
||||||
tf.add(f)
|
|
||||||
|
|
||||||
"""%dict(home='/mnt/hgfs/giskard/', tarfile=tbz2, project=PROJECT)
|
|
||||||
os.chdir(os.path.expanduser('~/build/pyinstaller'))
|
|
||||||
open('calibre/calibre.spec', 'wb').write(SPEC)
|
|
||||||
try:
|
|
||||||
subprocess.check_call(('/usr/bin/python', '-O', 'Build.py', 'calibre/calibre.spec'))
|
|
||||||
finally:
|
|
||||||
os.chdir(cwd)
|
|
||||||
return os.path.basename(tbz2)
|
|
||||||
|
|
||||||
def build_linux():
|
def build_linux():
|
||||||
|
installer = installer_name('tar.bz2')
|
||||||
vm = '/vmware/linux/libprs500-gentoo.vmx'
|
vm = '/vmware/linux/libprs500-gentoo.vmx'
|
||||||
vmware = ('vmware', '-q', '-x', '-n', vm)
|
start_vm(vm, 'linux', BUILD_SCRIPT%('sudo python setup.py develop', 'python','linux_installer.py'))
|
||||||
subprocess.Popen(vmware)
|
subprocess.check_call(('scp', 'linux:/tmp/%s'%os.path.basename(installer), 'dist'))
|
||||||
print 'Waiting for linux to boot up...'
|
if not os.path.exists(installer):
|
||||||
time.sleep(75)
|
raise Exception('Failed to build installer '+installer)
|
||||||
check_call('ssh linux make -C /mnt/hgfs/giskard/work/%s all egg linux_binary'%PROJECT)
|
subprocess.Popen(('ssh', 'linux', 'sudo', '/sbin/poweroff'))
|
||||||
check_call('ssh linux sudo poweroff')
|
return os.path.basename(installer)
|
||||||
|
|
||||||
def build_installers():
|
def build_installers():
|
||||||
return build_linux(), build_windows(), build_osx()
|
return build_linux(), build_windows(), build_osx()
|
||||||
@ -267,18 +140,14 @@ def upload_user_manual():
|
|||||||
finally:
|
finally:
|
||||||
os.chdir(cwd)
|
os.chdir(cwd)
|
||||||
|
|
||||||
def build_tarball():
|
def build_src_tarball():
|
||||||
cwd = os.getcwd()
|
|
||||||
check_call('bzr export dist/calibre-%s.tar.bz2'%__version__)
|
check_call('bzr export dist/calibre-%s.tar.bz2'%__version__)
|
||||||
|
|
||||||
def upload_tarball():
|
def upload_src_tarball():
|
||||||
check_call('ssh divok rm -f %s/calibre-\*.tar.bz2'%DOWNLOADS)
|
check_call('ssh divok rm -f %s/calibre-\*.tar.bz2'%DOWNLOADS)
|
||||||
check_call('scp dist/calibre-*.tar.bz2 divok:%s/'%DOWNLOADS)
|
check_call('scp dist/calibre-*.tar.bz2 divok:%s/'%DOWNLOADS)
|
||||||
|
|
||||||
|
def stage_one():
|
||||||
|
|
||||||
def main():
|
|
||||||
upload = len(sys.argv) < 2
|
|
||||||
shutil.rmtree('build')
|
shutil.rmtree('build')
|
||||||
os.mkdir('build')
|
os.mkdir('build')
|
||||||
shutil.rmtree('docs')
|
shutil.rmtree('docs')
|
||||||
@ -288,17 +157,32 @@ def main():
|
|||||||
check_call('make', shell=True)
|
check_call('make', shell=True)
|
||||||
tag_release()
|
tag_release()
|
||||||
upload_demo()
|
upload_demo()
|
||||||
|
|
||||||
|
def stage_two():
|
||||||
|
subprocess.check_call('rm -rf dist/*', shell=True)
|
||||||
build_installers()
|
build_installers()
|
||||||
build_tarball()
|
build_src_tarball()
|
||||||
if upload:
|
|
||||||
print 'Uploading installers...'
|
def stage_three():
|
||||||
upload_installers()
|
print 'Uploading installers...'
|
||||||
print 'Uploading to PyPI'
|
upload_installers()
|
||||||
upload_tarball()
|
print 'Uploading to PyPI'
|
||||||
upload_docs()
|
upload_src_tarball()
|
||||||
upload_user_manual()
|
upload_docs()
|
||||||
check_call('python setup.py register bdist_egg --exclude-source-files upload')
|
upload_user_manual()
|
||||||
check_call('''rm -rf dist/* build/*''')
|
check_call('python setup.py register bdist_egg --exclude-source-files upload')
|
||||||
|
check_call('''rm -rf dist/* build/*''')
|
||||||
|
|
||||||
|
def main(args=sys.argv):
|
||||||
|
print 'Starting stage one...'
|
||||||
|
stage_one()
|
||||||
|
print 'Starting stage two...'
|
||||||
|
stage_two()
|
||||||
|
print 'Starting stage three...'
|
||||||
|
stage_three()
|
||||||
|
print 'Finished'
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
sys.exit(main())
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
''' Create a windows installer '''
|
''' Create a windows installer '''
|
||||||
import sys, re, os, shutil, subprocess
|
import sys, re, os, shutil, subprocess, zipfile
|
||||||
from setup import VERSION, APPNAME, entry_points, scripts, basenames
|
from setup import VERSION, APPNAME, entry_points, scripts, basenames
|
||||||
from distutils.core import setup
|
from distutils.core import setup
|
||||||
from distutils.filelist import FileList
|
from distutils.filelist import FileList
|
||||||
@ -18,7 +18,7 @@ if os.path.exists(PY2EXE_DIR):
|
|||||||
class NSISInstaller(object):
|
class NSISInstaller(object):
|
||||||
TEMPLATE = r'''
|
TEMPLATE = r'''
|
||||||
; Do a Cyclic Redundancy Check to make sure the installer
|
; Do a Cyclic Redundancy Check to make sure the installer
|
||||||
; was not corrupted by the download.
|
; was not corrupted by the download.
|
||||||
CRCCheck on
|
CRCCheck on
|
||||||
|
|
||||||
SetCompressor lzma
|
SetCompressor lzma
|
||||||
@ -29,7 +29,7 @@ ShowUnInstDetails show
|
|||||||
;Include Modern UI
|
;Include Modern UI
|
||||||
!include "MUI2.nsh"
|
!include "MUI2.nsh"
|
||||||
!include "WinMessages.nsh"
|
!include "WinMessages.nsh"
|
||||||
|
|
||||||
;------------------------------------------------------------------------------------------------------
|
;------------------------------------------------------------------------------------------------------
|
||||||
;Variables
|
;Variables
|
||||||
Var STARTMENU_FOLDER
|
Var STARTMENU_FOLDER
|
||||||
@ -63,7 +63,7 @@ Loop:
|
|||||||
StrCmp "$R2" "$\r" RTrim
|
StrCmp "$R2" "$\r" RTrim
|
||||||
StrCmp "$R2" ";" RTrim
|
StrCmp "$R2" ";" RTrim
|
||||||
GoTo Done
|
GoTo Done
|
||||||
RTrim:
|
RTrim:
|
||||||
StrCpy $R1 "$R1" -1
|
StrCpy $R1 "$R1" -1
|
||||||
Goto Loop
|
Goto Loop
|
||||||
Done:
|
Done:
|
||||||
@ -82,7 +82,7 @@ FunctionEnd
|
|||||||
; Call StrStr
|
; Call StrStr
|
||||||
; Pop $R0
|
; Pop $R0
|
||||||
; ($R0 at this point is "ass string")
|
; ($R0 at this point is "ass string")
|
||||||
|
|
||||||
!macro StrStr un
|
!macro StrStr un
|
||||||
Function ${un}StrStr
|
Function ${un}StrStr
|
||||||
Exch $R1 ; st=haystack,old$R1, $R1=needle
|
Exch $R1 ; st=haystack,old$R1, $R1=needle
|
||||||
@ -123,7 +123,7 @@ Function AddToPath
|
|||||||
Push $3
|
Push $3
|
||||||
; don't add if the path doesn't exist
|
; don't add if the path doesn't exist
|
||||||
IfFileExists "$0\*.*" "" AddToPath_done
|
IfFileExists "$0\*.*" "" AddToPath_done
|
||||||
|
|
||||||
ReadEnvStr $1 PATH
|
ReadEnvStr $1 PATH
|
||||||
Push "$1;"
|
Push "$1;"
|
||||||
Push "$0;"
|
Push "$0;"
|
||||||
@ -146,7 +146,7 @@ Function AddToPath
|
|||||||
Call StrStr
|
Call StrStr
|
||||||
Pop $2
|
Pop $2
|
||||||
StrCmp $2 "" "" AddToPath_done
|
StrCmp $2 "" "" AddToPath_done
|
||||||
|
|
||||||
ReadRegStr $1 ${WriteEnvStr_RegKey} "PATH"
|
ReadRegStr $1 ${WriteEnvStr_RegKey} "PATH"
|
||||||
StrCmp $1 "" AddToPath_NTdoIt
|
StrCmp $1 "" AddToPath_NTdoIt
|
||||||
Push $1
|
Push $1
|
||||||
@ -156,7 +156,7 @@ Function AddToPath
|
|||||||
AddToPath_NTdoIt:
|
AddToPath_NTdoIt:
|
||||||
WriteRegExpandStr ${WriteEnvStr_RegKey} "PATH" $0
|
WriteRegExpandStr ${WriteEnvStr_RegKey} "PATH" $0
|
||||||
SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
|
SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
|
||||||
|
|
||||||
AddToPath_done:
|
AddToPath_done:
|
||||||
Pop $3
|
Pop $3
|
||||||
Pop $2
|
Pop $2
|
||||||
@ -172,9 +172,9 @@ Function un.RemoveFromPath
|
|||||||
Push $4
|
Push $4
|
||||||
Push $5
|
Push $5
|
||||||
Push $6
|
Push $6
|
||||||
|
|
||||||
IntFmt $6 "%%c" 26 # DOS EOF
|
IntFmt $6 "%%c" 26 # DOS EOF
|
||||||
|
|
||||||
ReadRegStr $1 ${WriteEnvStr_RegKey} "PATH"
|
ReadRegStr $1 ${WriteEnvStr_RegKey} "PATH"
|
||||||
StrCpy $5 $1 1 -1 # copy last char
|
StrCpy $5 $1 1 -1 # copy last char
|
||||||
StrCmp $5 ";" +2 # if last char != ;
|
StrCmp $5 ";" +2 # if last char != ;
|
||||||
@ -192,14 +192,14 @@ Function un.RemoveFromPath
|
|||||||
StrCpy $5 $1 -$4 # $5 is now the part before the path to remove
|
StrCpy $5 $1 -$4 # $5 is now the part before the path to remove
|
||||||
StrCpy $6 $2 "" $3 # $6 is now the part after the path to remove
|
StrCpy $6 $2 "" $3 # $6 is now the part after the path to remove
|
||||||
StrCpy $3 $5$6
|
StrCpy $3 $5$6
|
||||||
|
|
||||||
StrCpy $5 $3 1 -1 # copy last char
|
StrCpy $5 $3 1 -1 # copy last char
|
||||||
StrCmp $5 ";" 0 +2 # if last char == ;
|
StrCmp $5 ";" 0 +2 # if last char == ;
|
||||||
StrCpy $3 $3 -1 # remove last char
|
StrCpy $3 $3 -1 # remove last char
|
||||||
|
|
||||||
WriteRegExpandStr ${WriteEnvStr_RegKey} "PATH" $3
|
WriteRegExpandStr ${WriteEnvStr_RegKey} "PATH" $3
|
||||||
SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
|
SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
|
||||||
|
|
||||||
unRemoveFromPath_done:
|
unRemoveFromPath_done:
|
||||||
Pop $6
|
Pop $6
|
||||||
Pop $5
|
Pop $5
|
||||||
@ -219,13 +219,13 @@ FunctionEnd
|
|||||||
|
|
||||||
;Default installation folder
|
;Default installation folder
|
||||||
InstallDir "$PROGRAMFILES\${PRODUCT_NAME}"
|
InstallDir "$PROGRAMFILES\${PRODUCT_NAME}"
|
||||||
|
|
||||||
;Get installation folder from registry if available
|
;Get installation folder from registry if available
|
||||||
InstallDirRegKey HKCU "Software\${PRODUCT_NAME}" ""
|
InstallDirRegKey HKCU "Software\${PRODUCT_NAME}" ""
|
||||||
|
|
||||||
;Vista redirects $SMPROGRAMS to all users without this
|
;Vista redirects $SMPROGRAMS to all users without this
|
||||||
RequestExecutionLevel admin
|
RequestExecutionLevel admin
|
||||||
|
|
||||||
;------------------------------------------------------------------------------------------------------
|
;------------------------------------------------------------------------------------------------------
|
||||||
;Interface Settings
|
;Interface Settings
|
||||||
|
|
||||||
@ -241,25 +241,25 @@ FunctionEnd
|
|||||||
!insertmacro MUI_PAGE_COMPONENTS
|
!insertmacro MUI_PAGE_COMPONENTS
|
||||||
!insertmacro MUI_PAGE_DIRECTORY
|
!insertmacro MUI_PAGE_DIRECTORY
|
||||||
;Start Menu Folder Page Configuration
|
;Start Menu Folder Page Configuration
|
||||||
!define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKCU"
|
!define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKCU"
|
||||||
!define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\${PRODUCT_NAME}"
|
!define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\${PRODUCT_NAME}"
|
||||||
!define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder"
|
!define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder"
|
||||||
|
|
||||||
!insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER
|
!insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER
|
||||||
!insertmacro MUI_PAGE_INSTFILES
|
!insertmacro MUI_PAGE_INSTFILES
|
||||||
|
|
||||||
; Finish page with option to run program
|
; Finish page with option to run program
|
||||||
; Disabled as GUI requires PATH and working directory to be set correctly
|
; Disabled as GUI requires PATH and working directory to be set correctly
|
||||||
;!define MUI_FINISHPAGE_RUN "$INSTDIR\${PRODUCT_NAME}.exe"
|
;!define MUI_FINISHPAGE_RUN "$INSTDIR\${PRODUCT_NAME}.exe"
|
||||||
;!define MUI_FINISHPAGE_NOAUTOCLOSE
|
;!define MUI_FINISHPAGE_NOAUTOCLOSE
|
||||||
;!insertmacro MUI_PAGE_FINISH
|
;!insertmacro MUI_PAGE_FINISH
|
||||||
|
|
||||||
!insertmacro MUI_UNPAGE_CONFIRM
|
!insertmacro MUI_UNPAGE_CONFIRM
|
||||||
!insertmacro MUI_UNPAGE_INSTFILES
|
!insertmacro MUI_UNPAGE_INSTFILES
|
||||||
!insertmacro MUI_UNPAGE_FINISH
|
!insertmacro MUI_UNPAGE_FINISH
|
||||||
;------------------------------------------------------------------------------------------------------
|
;------------------------------------------------------------------------------------------------------
|
||||||
;Languages
|
;Languages
|
||||||
|
|
||||||
!insertmacro MUI_LANGUAGE "English"
|
!insertmacro MUI_LANGUAGE "English"
|
||||||
;------------------------------------------------------------------------------------------------------
|
;------------------------------------------------------------------------------------------------------
|
||||||
;Installer Sections
|
;Installer Sections
|
||||||
@ -268,7 +268,7 @@ Function .onInit
|
|||||||
; Prevent multiple instances of the installer from running
|
; Prevent multiple instances of the installer from running
|
||||||
System::Call 'kernel32::CreateMutexA(i 0, i 0, t "${PRODUCT_NAME}-setup") i .r1 ?e'
|
System::Call 'kernel32::CreateMutexA(i 0, i 0, t "${PRODUCT_NAME}-setup") i .r1 ?e'
|
||||||
Pop $R0
|
Pop $R0
|
||||||
|
|
||||||
StrCmp $R0 0 +3
|
StrCmp $R0 0 +3
|
||||||
MessageBox MB_OK|MB_ICONEXCLAMATION "The installer is already running."
|
MessageBox MB_OK|MB_ICONEXCLAMATION "The installer is already running."
|
||||||
Abort
|
Abort
|
||||||
@ -279,24 +279,24 @@ FunctionEnd
|
|||||||
Section "Main" "secmain"
|
Section "Main" "secmain"
|
||||||
|
|
||||||
SetOutPath "$INSTDIR"
|
SetOutPath "$INSTDIR"
|
||||||
|
|
||||||
;ADD YOUR OWN FILES HERE...
|
;ADD YOUR OWN FILES HERE...
|
||||||
File /r "${PY2EXE_DIR}\*"
|
File /r "${PY2EXE_DIR}\*"
|
||||||
File "${CLIT}"
|
File "${CLIT}"
|
||||||
File "${PDFTOHTML}"
|
File "${PDFTOHTML}"
|
||||||
File /r "${FONTCONFIG}\*"
|
File /r "${FONTCONFIG}\*"
|
||||||
|
|
||||||
SetOutPath "$INSTDIR\ImageMagick"
|
SetOutPath "$INSTDIR\ImageMagick"
|
||||||
File /r "${IMAGEMAGICK}\*"
|
File /r "${IMAGEMAGICK}\*"
|
||||||
|
|
||||||
|
|
||||||
SetOutPath "$SYSDIR"
|
SetOutPath "$SYSDIR"
|
||||||
File "${LIBUNRAR_DIR}\unrar.dll"
|
File "${LIBUNRAR_DIR}\unrar.dll"
|
||||||
DetailPrint " "
|
DetailPrint " "
|
||||||
|
|
||||||
;Store installation folder
|
;Store installation folder
|
||||||
WriteRegStr HKCU "Software\${PRODUCT_NAME}" "" $INSTDIR
|
WriteRegStr HKCU "Software\${PRODUCT_NAME}" "" $INSTDIR
|
||||||
|
|
||||||
;Create uninstaller
|
;Create uninstaller
|
||||||
WriteUninstaller "$INSTDIR\Uninstall.exe"
|
WriteUninstaller "$INSTDIR\Uninstall.exe"
|
||||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
|
||||||
@ -306,7 +306,7 @@ Section "Main" "secmain"
|
|||||||
|
|
||||||
SetOutPath "$INSTDIR"
|
SetOutPath "$INSTDIR"
|
||||||
!insertmacro MUI_STARTMENU_WRITE_BEGIN Application
|
!insertmacro MUI_STARTMENU_WRITE_BEGIN Application
|
||||||
|
|
||||||
;Create shortcuts
|
;Create shortcuts
|
||||||
WriteIniStr "$INSTDIR\${PRODUCT_NAME}.url" "InternetShortcut" "URL" "${WEBSITE}"
|
WriteIniStr "$INSTDIR\${PRODUCT_NAME}.url" "InternetShortcut" "URL" "${WEBSITE}"
|
||||||
CreateDirectory "$SMPROGRAMS\$STARTMENU_FOLDER"
|
CreateDirectory "$SMPROGRAMS\$STARTMENU_FOLDER"
|
||||||
@ -317,11 +317,11 @@ Section "Main" "secmain"
|
|||||||
CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\calibre.exe"
|
CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\calibre.exe"
|
||||||
|
|
||||||
!insertmacro MUI_STARTMENU_WRITE_END
|
!insertmacro MUI_STARTMENU_WRITE_END
|
||||||
|
|
||||||
;Add the installation directory to PATH for the commandline tools
|
;Add the installation directory to PATH for the commandline tools
|
||||||
Push "$INSTDIR"
|
Push "$INSTDIR"
|
||||||
Call AddToPath
|
Call AddToPath
|
||||||
|
|
||||||
SectionEnd
|
SectionEnd
|
||||||
|
|
||||||
Section /o "Device Drivers (only needed for PRS500)" "secdd"
|
Section /o "Device Drivers (only needed for PRS500)" "secdd"
|
||||||
@ -338,13 +338,13 @@ Section /o "Device Drivers (only needed for PRS500)" "secdd"
|
|||||||
File "${LIBUSB_DIR}\libusb0.sys"
|
File "${LIBUSB_DIR}\libusb0.sys"
|
||||||
;File "${LIBUSB_DIR}\libusb0_x64.dll"
|
;File "${LIBUSB_DIR}\libusb0_x64.dll"
|
||||||
;File "${LIBUSB_DIR}\libusb0_x64.sys"
|
;File "${LIBUSB_DIR}\libusb0_x64.sys"
|
||||||
|
|
||||||
; Uninstall USB drivers
|
; Uninstall USB drivers
|
||||||
DetailPrint "Uninstalling any existing device drivers"
|
DetailPrint "Uninstalling any existing device drivers"
|
||||||
ExecWait '"$INSTDIR\driver\devcon.exe" remove "USB\VID_054C&PID_029B"' $0
|
ExecWait '"$INSTDIR\driver\devcon.exe" remove "USB\VID_054C&PID_029B"' $0
|
||||||
DetailPrint "devcon returned exit code $0"
|
DetailPrint "devcon returned exit code $0"
|
||||||
|
|
||||||
|
|
||||||
DetailPrint "Installing USB driver for prs500..."
|
DetailPrint "Installing USB driver for prs500..."
|
||||||
ExecWait '"$INSTDIR\driver\devcon.exe" install "$INSTDIR\driver\prs500.inf" "USB\VID_054C&PID_029B"' $0
|
ExecWait '"$INSTDIR\driver\devcon.exe" install "$INSTDIR\driver\prs500.inf" "USB\VID_054C&PID_029B"' $0
|
||||||
DetailPrint "devcon returned exit code $0"
|
DetailPrint "devcon returned exit code $0"
|
||||||
@ -353,10 +353,10 @@ Section /o "Device Drivers (only needed for PRS500)" "secdd"
|
|||||||
Goto +2
|
Goto +2
|
||||||
MessageBox MB_OK '1. If you have the SONY Connect Reader software installed: $\nGoto Add Remove Programs and uninstall the entry "Windows Driver Package - Sony Corporation (PRSUSB)". $\n$\n2. If your reader is connected to the computer, disconnect and reconnect it now.'
|
MessageBox MB_OK '1. If you have the SONY Connect Reader software installed: $\nGoto Add Remove Programs and uninstall the entry "Windows Driver Package - Sony Corporation (PRSUSB)". $\n$\n2. If your reader is connected to the computer, disconnect and reconnect it now.'
|
||||||
DetailPrint " "
|
DetailPrint " "
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
SectionEnd
|
SectionEnd
|
||||||
|
|
||||||
;------------------------------------------------------------------------------------------------------
|
;------------------------------------------------------------------------------------------------------
|
||||||
@ -381,7 +381,7 @@ Section "un.DeviceDrivers"
|
|||||||
SectionEnd
|
SectionEnd
|
||||||
|
|
||||||
Section "Uninstall"
|
Section "Uninstall"
|
||||||
|
|
||||||
;ADD YOUR OWN FILES HERE...
|
;ADD YOUR OWN FILES HERE...
|
||||||
RMDir /r "$INSTDIR"
|
RMDir /r "$INSTDIR"
|
||||||
!insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
|
!insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
|
||||||
@ -404,15 +404,15 @@ Section "Uninstall"
|
|||||||
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
|
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
|
||||||
; Remove installation directory from PATH
|
; Remove installation directory from PATH
|
||||||
Push "$INSTDIR"
|
Push "$INSTDIR"
|
||||||
Call un.RemoveFromPath
|
Call un.RemoveFromPath
|
||||||
SectionEnd
|
SectionEnd
|
||||||
'''
|
'''
|
||||||
def __init__(self, name, py2exe_dir, output_dir):
|
def __init__(self, name, py2exe_dir, output_dir):
|
||||||
self.installer = self.__class__.TEMPLATE % dict(name=name, py2exe_dir=py2exe_dir,
|
self.installer = self.__class__.TEMPLATE % dict(name=name, py2exe_dir=py2exe_dir,
|
||||||
version=VERSION,
|
version=VERSION,
|
||||||
outpath=os.path.abspath(output_dir))
|
outpath=os.path.abspath(output_dir))
|
||||||
|
|
||||||
def build(self):
|
def build(self):
|
||||||
f = open('installer.nsi', 'w')
|
f = open('installer.nsi', 'w')
|
||||||
path = f.name
|
path = f.name
|
||||||
f.write(self.installer)
|
f.write(self.installer)
|
||||||
@ -420,23 +420,23 @@ SectionEnd
|
|||||||
try:
|
try:
|
||||||
subprocess.check_call('"C:\Program Files\NSIS\makensis.exe" /V2 ' + path, shell=True)
|
subprocess.check_call('"C:\Program Files\NSIS\makensis.exe" /V2 ' + path, shell=True)
|
||||||
except:
|
except:
|
||||||
print path
|
print path
|
||||||
else:
|
else:
|
||||||
os.remove(path)
|
os.remove(path)
|
||||||
|
|
||||||
|
|
||||||
class BuildEXE(build_exe):
|
class BuildEXE(build_exe):
|
||||||
manifest_resource_id = 0
|
manifest_resource_id = 0
|
||||||
QT_PREFIX = r'C:\\Qt\\4.4.0'
|
QT_PREFIX = r'C:\\Qt\\4.4.0'
|
||||||
MANIFEST_TEMPLATE = '''
|
MANIFEST_TEMPLATE = '''
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||||
<assemblyIdentity version="%(version)s"
|
<assemblyIdentity version="%(version)s"
|
||||||
processorArchitecture="x86"
|
processorArchitecture="x86"
|
||||||
name="net.kovidgoyal.%(prog)s"
|
name="net.kovidgoyal.%(prog)s"
|
||||||
type="win32"
|
type="win32"
|
||||||
/>
|
/>
|
||||||
<description>Ebook management application</description>
|
<description>Ebook management application</description>
|
||||||
<!-- Identify the application security requirements. -->
|
<!-- Identify the application security requirements. -->
|
||||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||||
<security>
|
<security>
|
||||||
@ -474,7 +474,7 @@ class BuildEXE(build_exe):
|
|||||||
shutil.rmtree('.build', True)
|
shutil.rmtree('.build', True)
|
||||||
finally:
|
finally:
|
||||||
os.chdir(cwd)
|
os.chdir(cwd)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if not os.path.exists(self.dist_dir):
|
if not os.path.exists(self.dist_dir):
|
||||||
os.makedirs(self.dist_dir)
|
os.makedirs(self.dist_dir)
|
||||||
@ -493,7 +493,7 @@ class BuildEXE(build_exe):
|
|||||||
shutil.copyfile(qtsvgdll, os.path.join(self.dist_dir, os.path.basename(qtsvgdll)))
|
shutil.copyfile(qtsvgdll, os.path.join(self.dist_dir, os.path.basename(qtsvgdll)))
|
||||||
qtxmldll = os.path.join(os.path.dirname(qtsvgdll), 'QtXml4.dll')
|
qtxmldll = os.path.join(os.path.dirname(qtsvgdll), 'QtXml4.dll')
|
||||||
print 'Adding', qtxmldll
|
print 'Adding', qtxmldll
|
||||||
shutil.copyfile(qtxmldll,
|
shutil.copyfile(qtxmldll,
|
||||||
os.path.join(self.dist_dir, os.path.basename(qtxmldll)))
|
os.path.join(self.dist_dir, os.path.basename(qtxmldll)))
|
||||||
print 'Adding plugins...',
|
print 'Adding plugins...',
|
||||||
qt_prefix = self.QT_PREFIX
|
qt_prefix = self.QT_PREFIX
|
||||||
@ -503,50 +503,45 @@ class BuildEXE(build_exe):
|
|||||||
for d in ('imageformats', 'codecs', 'iconengines'):
|
for d in ('imageformats', 'codecs', 'iconengines'):
|
||||||
print d,
|
print d,
|
||||||
imfd = os.path.join(plugdir, d)
|
imfd = os.path.join(plugdir, d)
|
||||||
tg = os.path.join(self.dist_dir, d)
|
tg = os.path.join(self.dist_dir, d)
|
||||||
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 'Adding GUI main.py'
|
||||||
|
f = zipfile.ZipFile(os.path.join('build', 'py2exe', 'library.zip'), 'a', zipfile.ZIP_DEFLATED)
|
||||||
|
f.write('src\\calibre\\gui2\\main.py', 'calibre\\gui2\\main.py')
|
||||||
|
f.close()
|
||||||
|
|
||||||
print
|
print
|
||||||
print
|
print
|
||||||
print 'Building Installer'
|
print 'Building Installer'
|
||||||
installer = NSISInstaller(APPNAME, self.dist_dir, 'dist')
|
installer = NSISInstaller(APPNAME, self.dist_dir, 'dist')
|
||||||
installer.build()
|
installer.build()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def manifest(cls, prog):
|
def manifest(cls, prog):
|
||||||
cls.manifest_resource_id += 1
|
cls.manifest_resource_id += 1
|
||||||
return (24, cls.manifest_resource_id,
|
return (24, cls.manifest_resource_id,
|
||||||
cls.MANIFEST_TEMPLATE % dict(prog=prog, version=VERSION+'.0'))
|
cls.MANIFEST_TEMPLATE % dict(prog=prog, version=VERSION+'.0'))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
auto = '--auto' in sys.argv
|
|
||||||
if auto:
|
|
||||||
sys.argv.remove('--auto')
|
|
||||||
sys.argv[1:2] = ['py2exe']
|
sys.argv[1:2] = ['py2exe']
|
||||||
if '--verbose' not in sys.argv:
|
console = [dict(dest_base=basenames['console'][i], script=scripts['console'][i])
|
||||||
sys.argv.append('--quiet') #py2exe produces too much output by default
|
|
||||||
subprocess.check_call('python setup.py develop', shell=True)
|
|
||||||
if auto and not os.path.exists('dist\\auto'):
|
|
||||||
print os.path.abspath('dist\\auto'), 'does not exist'
|
|
||||||
return 1
|
|
||||||
console = [dict(dest_base=basenames['console'][i], script=scripts['console'][i])
|
|
||||||
for i in range(len(scripts['console']))]
|
for i in range(len(scripts['console']))]
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
|
||||||
setup(
|
setup(
|
||||||
cmdclass = {'py2exe': BuildEXE},
|
cmdclass = {'py2exe': BuildEXE},
|
||||||
windows = [
|
windows = [
|
||||||
{'script' : scripts['gui'][0],
|
{'script' : scripts['gui'][0],
|
||||||
'dest_base' : APPNAME,
|
'dest_base' : APPNAME,
|
||||||
'icon_resources' : [(1, 'icons/library.ico')],
|
'icon_resources' : [(1, 'icons/library.ico')],
|
||||||
'other_resources' : [BuildEXE.manifest(APPNAME)],
|
'other_resources' : [BuildEXE.manifest(APPNAME)],
|
||||||
},
|
},
|
||||||
{'script' : scripts['gui'][1],
|
{'script' : scripts['gui'][1],
|
||||||
'dest_base' : 'lrfviewer',
|
'dest_base' : 'lrfviewer',
|
||||||
'icon_resources' : [(1, 'icons/viewer.ico')],
|
'icon_resources' : [(1, 'icons/viewer.ico')],
|
||||||
'other_resources' : [BuildEXE.manifest('lrfviewer')],
|
'other_resources' : [BuildEXE.manifest('lrfviewer')],
|
||||||
@ -557,24 +552,22 @@ def main():
|
|||||||
'optimize' : 2,
|
'optimize' : 2,
|
||||||
'dist_dir' : PY2EXE_DIR,
|
'dist_dir' : PY2EXE_DIR,
|
||||||
'includes' : [
|
'includes' : [
|
||||||
'sip', 'pkg_resources', 'PyQt4.QtSvg',
|
'sip', 'pkg_resources', 'PyQt4.QtSvg',
|
||||||
'mechanize', 'ClientForm', 'wmi',
|
'mechanize', 'ClientForm', 'wmi',
|
||||||
'win32file', 'pythoncom', 'rtf2xml',
|
'win32file', 'pythoncom', 'rtf2xml',
|
||||||
'lxml', 'lxml._elementpath', 'genshi',
|
'lxml', 'lxml._elementpath', 'genshi',
|
||||||
'path', 'pydoc', 'IPython.Extensions.*',
|
'path', 'pydoc', 'IPython.Extensions.*',
|
||||||
'calibre.web.feeds.recipes.*', 'pydoc',
|
'calibre.web.feeds.recipes.*', 'pydoc',
|
||||||
],
|
],
|
||||||
'packages' : ['PIL'],
|
'packages' : ['PIL'],
|
||||||
'excludes' : ["Tkconstants", "Tkinter", "tcl",
|
'excludes' : ["Tkconstants", "Tkinter", "tcl",
|
||||||
"_imagingtk", "ImageTk", "FixTk",
|
"_imagingtk", "ImageTk", "FixTk",
|
||||||
'pydoc'],
|
'pydoc'],
|
||||||
'dll_excludes' : ['mswsock.dll'],
|
'dll_excludes' : ['mswsock.dll'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
)
|
)
|
||||||
if auto:
|
|
||||||
subprocess.call(('shutdown', '-s', '-f', '-t', '01'))
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
Loading…
x
Reference in New Issue
Block a user