mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Sync to trunk.
This commit is contained in:
commit
de809a8fdf
BIN
resources/images/star.png
Normal file
BIN
resources/images/star.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
@ -186,8 +186,13 @@ class Build(Command):
|
|||||||
parser.add_option('-1', '--only', choices=choices, default='all',
|
parser.add_option('-1', '--only', choices=choices, default='all',
|
||||||
help=('Build only the named extension. Available: '+
|
help=('Build only the named extension. Available: '+
|
||||||
', '.join(choices)+'. Default:%default'))
|
', '.join(choices)+'. Default:%default'))
|
||||||
|
parser.add_option('--no-compile', default=False, action='store_true',
|
||||||
|
help='Skip compiling all C/C++ extensions.')
|
||||||
|
|
||||||
def run(self, opts):
|
def run(self, opts):
|
||||||
|
if opts.no_compile:
|
||||||
|
self.info('--no-compile specified, skipping compilation')
|
||||||
|
return
|
||||||
self.obj_dir = os.path.join(os.path.dirname(SRC), 'build', 'objects')
|
self.obj_dir = os.path.join(os.path.dirname(SRC), 'build', 'objects')
|
||||||
if not os.path.exists(self.obj_dir):
|
if not os.path.exists(self.obj_dir):
|
||||||
os.makedirs(self.obj_dir)
|
os.makedirs(self.obj_dir)
|
||||||
|
@ -43,7 +43,7 @@ class Develop(Command):
|
|||||||
sub_commands = ['build', 'resources', 'gui']
|
sub_commands = ['build', 'resources', 'gui']
|
||||||
|
|
||||||
def add_options(self, parser):
|
def add_options(self, parser):
|
||||||
parser.add_option('--prefix',
|
parser.add_option('--prefix', '--root',
|
||||||
help='Binaries will be installed in <prefix>/bin')
|
help='Binaries will be installed in <prefix>/bin')
|
||||||
|
|
||||||
def pre_sub_commands(self, opts):
|
def pre_sub_commands(self, opts):
|
||||||
@ -91,7 +91,9 @@ class Develop(Command):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def run_postinstall(self):
|
def run_postinstall(self):
|
||||||
subprocess.check_call(['calibre_postinstall'])
|
env = dict(**os.environ)
|
||||||
|
env['DESTDIR'] = self.prefix
|
||||||
|
subprocess.check_call(['calibre_postinstall', '--use-destdir'], env=env)
|
||||||
|
|
||||||
def success(self):
|
def success(self):
|
||||||
self.info('\nDevelopment environment successfully setup')
|
self.info('\nDevelopment environment successfully setup')
|
||||||
@ -119,6 +121,8 @@ class Develop(Command):
|
|||||||
path=self.path, resources=self.resources,
|
path=self.path, resources=self.resources,
|
||||||
extensions=self.extensions)
|
extensions=self.extensions)
|
||||||
path = self.j(self.bindir, name)
|
path = self.j(self.bindir, name)
|
||||||
|
if not os.path.exists(self.bindir):
|
||||||
|
os.makedirs(self.bindir)
|
||||||
self.info('Installing binary:', path)
|
self.info('Installing binary:', path)
|
||||||
open(path, 'wb').write(script)
|
open(path, 'wb').write(script)
|
||||||
os.chmod(path, self.MODE)
|
os.chmod(path, self.MODE)
|
||||||
@ -134,10 +138,10 @@ class Install(Develop):
|
|||||||
The default <prefix> is the prefix of your python installation.
|
The default <prefix> is the prefix of your python installation.
|
||||||
''')
|
''')
|
||||||
|
|
||||||
sub_commands = ['build']
|
sub_commands = ['build', 'gui']
|
||||||
|
|
||||||
def add_options(self, parser):
|
def add_options(self, parser):
|
||||||
parser.add_option('--prefix', help='Installation prefix')
|
parser.add_option('--prefix', '--root', help='Installation prefix')
|
||||||
parser.add_option('--libdir', help='Where to put calibre library files')
|
parser.add_option('--libdir', help='Where to put calibre library files')
|
||||||
parser.add_option('--bindir', help='Where to install calibre binaries')
|
parser.add_option('--bindir', help='Where to install calibre binaries')
|
||||||
parser.add_option('--sharedir', help='Where to install calibre data files')
|
parser.add_option('--sharedir', help='Where to install calibre data files')
|
||||||
@ -194,6 +198,14 @@ class Sdist(Command):
|
|||||||
shutil.copytree(p, self.j(tdir, p))
|
shutil.copytree(p, self.j(tdir, p))
|
||||||
else:
|
else:
|
||||||
shutil.copy2(p, d)
|
shutil.copy2(p, d)
|
||||||
|
for x in os.walk(os.path.join(self.SRC, 'calibre')):
|
||||||
|
for f in x[-1]:
|
||||||
|
if not f.endswith('_ui.py'): continue
|
||||||
|
f = os.path.join(x[0], f)
|
||||||
|
f = os.path.relpath(f)
|
||||||
|
dest = os.path.join(tdir, self.d(f))
|
||||||
|
shutil.copy2(f, dest)
|
||||||
|
|
||||||
self.info('\tCreating tarfile...')
|
self.info('\tCreating tarfile...')
|
||||||
subprocess.check_call(' '.join(['tar', '-czf', self.a(self.DEST), '*']),
|
subprocess.check_call(' '.join(['tar', '-czf', self.a(self.DEST), '*']),
|
||||||
cwd=tdir, shell=True)
|
cwd=tdir, shell=True)
|
||||||
|
@ -20,7 +20,7 @@ class VMInstaller(Command):
|
|||||||
VM_NAME = None
|
VM_NAME = None
|
||||||
FREEZE_COMMAND = None
|
FREEZE_COMMAND = None
|
||||||
FREEZE_TEMPLATE = 'python setup.py {freeze_command}'
|
FREEZE_TEMPLATE = 'python setup.py {freeze_command}'
|
||||||
SHUTDOWN_CMD = ['sudo', 'shutdown', '-h', 'now']
|
SHUTDOWN_CMD = ['sudo', 'poweroff']
|
||||||
IS_64_BIT = False
|
IS_64_BIT = False
|
||||||
|
|
||||||
BUILD_CMD = 'ssh -t %s bash build-calibre'
|
BUILD_CMD = 'ssh -t %s bash build-calibre'
|
||||||
|
@ -46,13 +46,12 @@ class LinuxFreeze(Command):
|
|||||||
'/lib/libz.so.1',
|
'/lib/libz.so.1',
|
||||||
'/usr/lib/libtiff.so.3',
|
'/usr/lib/libtiff.so.3',
|
||||||
'/lib/libbz2.so.1',
|
'/lib/libbz2.so.1',
|
||||||
'/usr/lib/libpoppler.so.4',
|
'/usr/lib/libpoppler.so.5',
|
||||||
'/usr/lib/libpoppler-qt4.so.3',
|
'/usr/lib/libpoppler-qt4.so.3',
|
||||||
'/usr/lib/libxml2.so.2',
|
'/usr/lib/libxml2.so.2',
|
||||||
'/usr/lib/libopenjpeg.so.2',
|
'/usr/lib/libopenjpeg.so.2',
|
||||||
'/usr/lib/libxslt.so.1',
|
'/usr/lib/libxslt.so.1',
|
||||||
'/usr/lib64/libjpeg.so.7'.replace('64', '64' if is64bit
|
'/usr/lib/libjpeg.so.7',
|
||||||
else ''),
|
|
||||||
'/usr/lib/libxslt.so.1',
|
'/usr/lib/libxslt.so.1',
|
||||||
'/usr/lib/libgthread-2.0.so.0',
|
'/usr/lib/libgthread-2.0.so.0',
|
||||||
'/usr/lib/gcc/***-pc-linux-gnu/4.4.1/libstdc++.so.6'.replace('***',
|
'/usr/lib/gcc/***-pc-linux-gnu/4.4.1/libstdc++.so.6'.replace('***',
|
||||||
@ -60,7 +59,7 @@ class LinuxFreeze(Command):
|
|||||||
'/usr/lib/libpng12.so.0',
|
'/usr/lib/libpng12.so.0',
|
||||||
'/usr/lib/libexslt.so.0',
|
'/usr/lib/libexslt.so.0',
|
||||||
'/usr/lib/libMagickWand.so',
|
'/usr/lib/libMagickWand.so',
|
||||||
'/usr/lib/libMagickCore.so',
|
'/usr/lib/libMagickCore.so.2',
|
||||||
'/usr/lib/libgcrypt.so.11',
|
'/usr/lib/libgcrypt.so.11',
|
||||||
'/usr/lib/libgpg-error.so.0',
|
'/usr/lib/libgpg-error.so.0',
|
||||||
'/usr/lib/libphonon.so.4',
|
'/usr/lib/libphonon.so.4',
|
||||||
|
@ -25,3 +25,4 @@ class OSX32(VMInstaller):
|
|||||||
VM = '/vmware/bin/%s'%VM_NAME
|
VM = '/vmware/bin/%s'%VM_NAME
|
||||||
FREEZE_COMMAND = 'osx32_freeze'
|
FREEZE_COMMAND = 'osx32_freeze'
|
||||||
BUILD_PREFIX = VMInstaller.BUILD_PREFIX + ['source ~/.profile']
|
BUILD_PREFIX = VMInstaller.BUILD_PREFIX + ['source ~/.profile']
|
||||||
|
SHUTDOWN_CMD = ['sudo', 'halt']
|
||||||
|
@ -26,6 +26,7 @@ class Win32(VMInstaller):
|
|||||||
VM_NAME = 'xp_build'
|
VM_NAME = 'xp_build'
|
||||||
VM = '/vmware/bin/%s'%VM_NAME
|
VM = '/vmware/bin/%s'%VM_NAME
|
||||||
FREEZE_COMMAND = 'win32_freeze'
|
FREEZE_COMMAND = 'win32_freeze'
|
||||||
|
SHUTDOWN_CMD = ['shutdown', '-s']
|
||||||
|
|
||||||
def download_installer(self):
|
def download_installer(self):
|
||||||
installer = self.installer()
|
installer = self.installer()
|
||||||
|
@ -6,7 +6,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import sys, os, shutil, subprocess, re, time
|
import sys, os, shutil, subprocess, re, time, glob
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from setup import Command, __appname__, __version__
|
from setup import Command, __appname__, __version__
|
||||||
@ -31,6 +31,11 @@ class Stage2(Command):
|
|||||||
description = 'Stage 2 of the publish process'
|
description = 'Stage 2 of the publish process'
|
||||||
sub_commands = ['linux', 'win', 'osx']
|
sub_commands = ['linux', 'win', 'osx']
|
||||||
|
|
||||||
|
def pre_sub_commands(self, opts):
|
||||||
|
for x in glob.glob(os.path.join(self.d(self.SRC), 'dist', '*')):
|
||||||
|
os.remove(x)
|
||||||
|
|
||||||
|
|
||||||
class Stage3(Command):
|
class Stage3(Command):
|
||||||
|
|
||||||
description = 'Stage 3 of the publish process'
|
description = 'Stage 3 of the publish process'
|
||||||
@ -88,7 +93,7 @@ if os.environ.get('CALIBRE_BUILDBOT', None) == '1':
|
|||||||
else:
|
else:
|
||||||
class UploadRss(Command):
|
class UploadRss(Command):
|
||||||
|
|
||||||
description = 'Generate and uplaod a RSS feed of calibre releases'
|
description = 'Generate and upload a RSS feed of calibre releases'
|
||||||
|
|
||||||
from bzrlib import log as blog
|
from bzrlib import log as blog
|
||||||
|
|
||||||
@ -150,7 +155,9 @@ else:
|
|||||||
bzr_path = os.path.expanduser('~/work/calibre')
|
bzr_path = os.path.expanduser('~/work/calibre')
|
||||||
b = branch.Branch.open(bzr_path)
|
b = branch.Branch.open(bzr_path)
|
||||||
lf = UploadRss.ChangelogFormatter()
|
lf = UploadRss.ChangelogFormatter()
|
||||||
|
self.info('\tGenerating bzr log...')
|
||||||
log.show_log(b, lf)
|
log.show_log(b, lf)
|
||||||
lf.rss.write_xml(open('/tmp/releases.xml', 'wb'))
|
lf.rss.write_xml(open('/tmp/releases.xml', 'wb'))
|
||||||
#subprocess.check_call('scp /tmp/releases.xml divok:/var/www/calibre.kovidgoyal.net/htdocs/downloads'.split())
|
self.info('\tUploading RSS to server...')
|
||||||
|
subprocess.check_call('scp /tmp/releases.xml divok:/var/www/calibre.kovidgoyal.net/htdocs/downloads'.split())
|
||||||
|
|
||||||
|
@ -221,9 +221,13 @@ def get_proxies():
|
|||||||
proxies['ftp'] = server
|
proxies['ftp'] = server
|
||||||
settings.Close()
|
settings.Close()
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
print('Unable to detect proxy settings: %s' % str(e))
|
prints('Unable to detect proxy settings: %s' % str(e))
|
||||||
|
for x in list(proxies):
|
||||||
|
if len(proxies[x]) < 5:
|
||||||
|
prints('Removing invalid', x, 'proxy:', proxies[x])
|
||||||
|
del proxies[x]
|
||||||
if proxies:
|
if proxies:
|
||||||
print('Using proxies: %s' % proxies)
|
prints('Using proxies: %s' % proxies)
|
||||||
return proxies
|
return proxies
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
__appname__ = 'calibre'
|
__appname__ = 'calibre'
|
||||||
__version__ = '0.6.11'
|
__version__ = '0.6.12'
|
||||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
@ -367,6 +367,7 @@ from calibre.devices.prs500.driver import PRS500
|
|||||||
from calibre.devices.prs505.driver import PRS505
|
from calibre.devices.prs505.driver import PRS505
|
||||||
from calibre.devices.prs700.driver import PRS700
|
from calibre.devices.prs700.driver import PRS700
|
||||||
from calibre.devices.android.driver import ANDROID
|
from calibre.devices.android.driver import ANDROID
|
||||||
|
from calibre.devices.eslick.driver import ESLICK
|
||||||
|
|
||||||
plugins = [HTML2ZIP]
|
plugins = [HTML2ZIP]
|
||||||
plugins += [
|
plugins += [
|
||||||
@ -416,7 +417,8 @@ plugins += [
|
|||||||
PRS700,
|
PRS700,
|
||||||
ANDROID,
|
ANDROID,
|
||||||
CYBOOK_OPUS,
|
CYBOOK_OPUS,
|
||||||
COOL_ER
|
COOL_ER,
|
||||||
|
ESLICK
|
||||||
]
|
]
|
||||||
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \
|
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \
|
||||||
x.__name__.endswith('MetadataReader')]
|
x.__name__.endswith('MetadataReader')]
|
||||||
|
@ -155,6 +155,9 @@ def debug_device_driver():
|
|||||||
print
|
print
|
||||||
print "Don't forget to send the file /tmp/ioreg.txt as well"
|
print "Don't forget to send the file /tmp/ioreg.txt as well"
|
||||||
|
|
||||||
|
if iswindows:
|
||||||
|
raw_input('Press Enter to continue...')
|
||||||
|
|
||||||
|
|
||||||
def add_simple_plugin(path_to_plugin):
|
def add_simple_plugin(path_to_plugin):
|
||||||
import tempfile, zipfile, shutil
|
import tempfile, zipfile, shutil
|
||||||
|
10
src/calibre/devices/eslick/__init__.py
Normal file
10
src/calibre/devices/eslick/__init__.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
|
||||||
|
|
39
src/calibre/devices/eslick/driver.py
Normal file
39
src/calibre/devices/eslick/driver.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
|
||||||
|
from calibre.devices.usbms.driver import USBMS
|
||||||
|
|
||||||
|
class ESLICK(USBMS):
|
||||||
|
|
||||||
|
name = 'ESlick Device Interface'
|
||||||
|
gui_name = 'Foxit ESlick'
|
||||||
|
description = _('Communicate with the ESlick eBook reader.')
|
||||||
|
author = _('Kovid Goyal')
|
||||||
|
supported_platforms = ['windows', 'osx', 'linux']
|
||||||
|
|
||||||
|
# Ordered list of supported formats
|
||||||
|
FORMATS = ['pdf', 'txt']
|
||||||
|
|
||||||
|
VENDOR_ID = [0x04cc]
|
||||||
|
PRODUCT_ID = [0x1a64]
|
||||||
|
BCD = [0x0110]
|
||||||
|
|
||||||
|
VENDOR_NAME = 'FOXIT'
|
||||||
|
WINDOWS_MAIN_MEM = 'ESLICK_USB_DEVICE'
|
||||||
|
WINDOWS_CARD_A_MEM = 'ESLICK_USB_DEVICE'
|
||||||
|
|
||||||
|
#OSX_MAIN_MEM = 'Kindle Internal Storage Media'
|
||||||
|
#OSX_CARD_A_MEM = 'Kindle Card Storage Media'
|
||||||
|
|
||||||
|
MAIN_MEMORY_VOLUME_LABEL = 'ESlick Main Memory'
|
||||||
|
STORAGE_CARD_VOLUME_LABEL = 'ESlick Storage Card'
|
||||||
|
|
||||||
|
SUPPORTS_SUB_DIRS = True
|
||||||
|
|
||||||
|
|
@ -238,7 +238,7 @@ class HTMLPreProcessor(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
end_rules = []
|
end_rules = []
|
||||||
if getattr(self.extra_opts, 'unwrap_factor', None):
|
if getattr(self.extra_opts, 'unwrap_factor', 0.0) > 0.01:
|
||||||
length = line_length(html, getattr(self.extra_opts, 'unwrap_factor'))
|
length = line_length(html, getattr(self.extra_opts, 'unwrap_factor'))
|
||||||
if length:
|
if length:
|
||||||
end_rules.append(
|
end_rules.append(
|
||||||
|
@ -262,9 +262,10 @@ class HTMLInput(InputFormatPlugin):
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
OptionRecommendation(name='unwrap_factor', recommended_value=0.5,
|
OptionRecommendation(name='unwrap_factor', recommended_value=0.0,
|
||||||
help=_('Average line length for line breaking if the HTML is from a '
|
help=_('Average line length for line breaking if the HTML is from a '
|
||||||
'previous partial conversion of a PDF file.')),
|
'previous partial conversion of a PDF file. Default is %default '
|
||||||
|
'which disables this.')),
|
||||||
|
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ class ResultList(list):
|
|||||||
d = date(entry)
|
d = date(entry)
|
||||||
if d:
|
if d:
|
||||||
default = datetime.utcnow()
|
default = datetime.utcnow()
|
||||||
default = datetime(default.year, default.month, 1)
|
default = datetime(default.year, default.month, 15)
|
||||||
d = parser.parse(d[0].text, default=default)
|
d = parser.parse(d[0].text, default=default)
|
||||||
else:
|
else:
|
||||||
d = None
|
d = None
|
||||||
|
@ -309,7 +309,8 @@ class MobiReader(object):
|
|||||||
try:
|
try:
|
||||||
root = html.fromstring(self.processed_html)
|
root = html.fromstring(self.processed_html)
|
||||||
if len(root.xpath('//html')) > 5:
|
if len(root.xpath('//html')) > 5:
|
||||||
root = html.fromstring(self.processed_html.replace('\x0c', ''))
|
root = html.fromstring(self.processed_html.replace('\x0c',
|
||||||
|
'').replace('\x14', ''))
|
||||||
except:
|
except:
|
||||||
self.log.warning('MOBI markup appears to contain random bytes. Stripping.')
|
self.log.warning('MOBI markup appears to contain random bytes. Stripping.')
|
||||||
self.processed_html = self.remove_random_bytes(self.processed_html)
|
self.processed_html = self.remove_random_bytes(self.processed_html)
|
||||||
|
@ -773,6 +773,7 @@ class Manifest(object):
|
|||||||
data = self.oeb.decode(data)
|
data = self.oeb.decode(data)
|
||||||
data = self.oeb.html_preprocessor(data)
|
data = self.oeb.html_preprocessor(data)
|
||||||
|
|
||||||
|
|
||||||
# Remove DOCTYPE declaration as it messes up parsing
|
# Remove DOCTYPE declaration as it messes up parsing
|
||||||
# Inparticular it causes tostring to insert xmlns
|
# Inparticular it causes tostring to insert xmlns
|
||||||
# declarations, which messes up the coercing logic
|
# declarations, which messes up the coercing logic
|
||||||
|
@ -8,6 +8,7 @@ __docformat__ = 'restructuredtext en'
|
|||||||
|
|
||||||
import textwrap
|
import textwrap
|
||||||
from xml.sax.saxutils import escape
|
from xml.sax.saxutils import escape
|
||||||
|
from itertools import repeat
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
@ -31,6 +32,7 @@ class Jacket(object):
|
|||||||
<h1 class="calibre_rescale_180">%(title)s</h1>
|
<h1 class="calibre_rescale_180">%(title)s</h1>
|
||||||
<h2 class="calibre_rescale_140">%(jacket)s</h2>
|
<h2 class="calibre_rescale_140">%(jacket)s</h2>
|
||||||
<div class="calibre_rescale_100">%(series)s</div>
|
<div class="calibre_rescale_100">%(series)s</div>
|
||||||
|
<div class="calibre_rescale_100">%(rating)s</div>
|
||||||
<div class="calibre_rescale_100">%(tags)s</div>
|
<div class="calibre_rescale_100">%(tags)s</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-top:2em" class="calibre_rescale_100">
|
<div style="margin-top:2em" class="calibre_rescale_100">
|
||||||
@ -54,6 +56,23 @@ class Jacket(object):
|
|||||||
img.getparent().remove(img)
|
img.getparent().remove(img)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def get_rating(self, rating):
|
||||||
|
ans = ''
|
||||||
|
if rating is None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
num = float(rating)/2
|
||||||
|
except:
|
||||||
|
return ans
|
||||||
|
num = max(0, num)
|
||||||
|
num = min(num, 5)
|
||||||
|
if num < 1:
|
||||||
|
return ans
|
||||||
|
id, href = self.oeb.manifest.generate('star', 'star.png')
|
||||||
|
self.oeb.manifest.add(id, href, 'image/png', data=I('star.png', data=True))
|
||||||
|
ans = 'Rating: ' + ''.join(repeat('<img style="vertical-align:text-top" alt="star" src="%s" />'%href, num))
|
||||||
|
return ans
|
||||||
|
|
||||||
def insert_metadata(self, mi):
|
def insert_metadata(self, mi):
|
||||||
self.log('Inserting metadata into book...')
|
self.log('Inserting metadata into book...')
|
||||||
comments = mi.comments
|
comments = mi.comments
|
||||||
@ -87,7 +106,7 @@ class Jacket(object):
|
|||||||
html = self.JACKET_TEMPLATE%dict(xmlns=XPNSMAP['h'],
|
html = self.JACKET_TEMPLATE%dict(xmlns=XPNSMAP['h'],
|
||||||
title=escape(title), comments=escape(comments),
|
title=escape(title), comments=escape(comments),
|
||||||
jacket=escape(_('Book Jacket')), series=series,
|
jacket=escape(_('Book Jacket')), series=series,
|
||||||
tags=tags)
|
tags=tags, rating=self.get_rating(mi.rating))
|
||||||
id, href = self.oeb.manifest.generate('jacket', 'jacket.xhtml')
|
id, href = self.oeb.manifest.generate('jacket', 'jacket.xhtml')
|
||||||
root = etree.fromstring(html)
|
root = etree.fromstring(html)
|
||||||
item = self.oeb.manifest.add(id, href, guess_type(href)[0], data=root)
|
item = self.oeb.manifest.add(id, href, guess_type(href)[0], data=root)
|
||||||
|
@ -3,7 +3,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
""" The GUI """
|
""" The GUI """
|
||||||
import os
|
import os
|
||||||
from PyQt4.QtCore import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, QSize, \
|
from PyQt4.QtCore import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, QSize, \
|
||||||
QByteArray, QUrl, QTranslator, QCoreApplication, QThread
|
QByteArray, QTranslator, QCoreApplication, QThread
|
||||||
from PyQt4.QtGui import QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \
|
from PyQt4.QtGui import QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \
|
||||||
QIcon, QTableView, QApplication, QDialog, QPushButton
|
QIcon, QTableView, QApplication, QDialog, QPushButton
|
||||||
|
|
||||||
@ -23,8 +23,6 @@ ALL_COLUMNS = ['title', 'authors', 'size', 'timestamp', 'rating', 'publisher',
|
|||||||
|
|
||||||
def _config():
|
def _config():
|
||||||
c = Config('gui', 'preferences for the calibre GUI')
|
c = Config('gui', 'preferences for the calibre GUI')
|
||||||
c.add_opt('frequently_used_directories', default=[],
|
|
||||||
help=_('Frequently used directories'))
|
|
||||||
c.add_opt('send_to_storage_card_by_default', default=False,
|
c.add_opt('send_to_storage_card_by_default', default=False,
|
||||||
help=_('Send file to storage card instead of main memory by default'))
|
help=_('Send file to storage card instead of main memory by default'))
|
||||||
c.add_opt('confirm_delete', default=False,
|
c.add_opt('confirm_delete', default=False,
|
||||||
@ -83,6 +81,8 @@ def _config():
|
|||||||
help='Search history for the LRF viewer')
|
help='Search history for the LRF viewer')
|
||||||
c.add_opt('scheduler_search_history', default=[],
|
c.add_opt('scheduler_search_history', default=[],
|
||||||
help='Search history for the recipe scheduler')
|
help='Search history for the recipe scheduler')
|
||||||
|
c.add_opt('worker_limit', default=6,
|
||||||
|
help=_('Maximum number of waiting worker processes'))
|
||||||
|
|
||||||
return ConfigProxy(c)
|
return ConfigProxy(c)
|
||||||
|
|
||||||
@ -377,15 +377,8 @@ def file_icon_provider():
|
|||||||
global _file_icon_provider
|
global _file_icon_provider
|
||||||
return _file_icon_provider
|
return _file_icon_provider
|
||||||
|
|
||||||
_sidebar_directories = []
|
|
||||||
def set_sidebar_directories(dirs):
|
|
||||||
global _sidebar_directories
|
|
||||||
if dirs is None:
|
|
||||||
dirs = config['frequently_used_directories']
|
|
||||||
_sidebar_directories = [QUrl.fromLocalFile(i) for i in dirs]
|
|
||||||
|
|
||||||
class FileDialog(QObject):
|
class FileDialog(QObject):
|
||||||
def __init__(self, title='Choose Files',
|
def __init__(self, title=_('Choose Files'),
|
||||||
filters=[],
|
filters=[],
|
||||||
add_all_files_filter=True,
|
add_all_files_filter=True,
|
||||||
parent=None,
|
parent=None,
|
||||||
@ -394,7 +387,6 @@ class FileDialog(QObject):
|
|||||||
mode = QFileDialog.ExistingFiles,
|
mode = QFileDialog.ExistingFiles,
|
||||||
):
|
):
|
||||||
QObject.__init__(self)
|
QObject.__init__(self)
|
||||||
initialize_file_icon_provider()
|
|
||||||
ftext = ''
|
ftext = ''
|
||||||
if filters:
|
if filters:
|
||||||
for filter in filters:
|
for filter in filters:
|
||||||
@ -410,42 +402,27 @@ class FileDialog(QObject):
|
|||||||
self.selected_files = None
|
self.selected_files = None
|
||||||
self.fd = None
|
self.fd = None
|
||||||
|
|
||||||
if islinux:
|
initial_dir = dynamic.get(self.dialog_name, os.path.expanduser('~'))
|
||||||
self.fd = QFileDialog(parent)
|
if not isinstance(initial_dir, basestring):
|
||||||
self.fd.setFileMode(mode)
|
initial_dir = os.path.expanduser('~')
|
||||||
self.fd.setIconProvider(_file_icon_provider)
|
|
||||||
self.fd.setModal(modal)
|
|
||||||
self.fd.setNameFilter(ftext)
|
|
||||||
self.fd.setWindowTitle(title)
|
|
||||||
state = dynamic[self.dialog_name]
|
|
||||||
if not state or not self.fd.restoreState(state):
|
|
||||||
self.fd.setDirectory(os.path.expanduser('~'))
|
|
||||||
osu = [i for i in self.fd.sidebarUrls()]
|
|
||||||
self.fd.setSidebarUrls(osu + _sidebar_directories)
|
|
||||||
QObject.connect(self.fd, SIGNAL('accepted()'), self.save_dir)
|
|
||||||
self.accepted = self.fd.exec_() == QFileDialog.Accepted
|
|
||||||
else:
|
|
||||||
dir = dynamic.get(self.dialog_name, os.path.expanduser('~'))
|
|
||||||
self.selected_files = []
|
self.selected_files = []
|
||||||
if mode == QFileDialog.AnyFile:
|
if mode == QFileDialog.AnyFile:
|
||||||
f = qstring_to_unicode(
|
f = unicode(QFileDialog.getSaveFileName(parent, title, initial_dir, ftext, ""))
|
||||||
QFileDialog.getSaveFileName(parent, title, dir, ftext, ""))
|
if f and os.path.exists(f):
|
||||||
if os.path.exists(f):
|
|
||||||
self.selected_files.append(f)
|
self.selected_files.append(f)
|
||||||
elif mode == QFileDialog.ExistingFile:
|
elif mode == QFileDialog.ExistingFile:
|
||||||
f = qstring_to_unicode(
|
f = unicode(QFileDialog.getOpenFileName(parent, title, initial_dir, ftext, ""))
|
||||||
QFileDialog.getOpenFileName(parent, title, dir, ftext, ""))
|
if f and os.path.exists(f):
|
||||||
if os.path.exists(f):
|
|
||||||
self.selected_files.append(f)
|
self.selected_files.append(f)
|
||||||
elif mode == QFileDialog.ExistingFiles:
|
elif mode == QFileDialog.ExistingFiles:
|
||||||
fs = QFileDialog.getOpenFileNames(parent, title, dir, ftext, "")
|
fs = QFileDialog.getOpenFileNames(parent, title, initial_dir, ftext, "")
|
||||||
for f in fs:
|
for f in fs:
|
||||||
if os.path.exists(qstring_to_unicode(f)):
|
f = unicode(f)
|
||||||
|
if f and os.path.exists(f):
|
||||||
self.selected_files.append(f)
|
self.selected_files.append(f)
|
||||||
else:
|
else:
|
||||||
opts = QFileDialog.ShowDirsOnly if mode == QFileDialog.DirectoryOnly else QFileDialog.Option()
|
opts = QFileDialog.ShowDirsOnly if mode == QFileDialog.DirectoryOnly else QFileDialog.Option()
|
||||||
f = qstring_to_unicode(
|
f = unicode(QFileDialog.getExistingDirectory(parent, title, initial_dir, opts))
|
||||||
QFileDialog.getExistingDirectory(parent, title, dir, opts))
|
|
||||||
if os.path.exists(f):
|
if os.path.exists(f):
|
||||||
self.selected_files.append(f)
|
self.selected_files.append(f)
|
||||||
if self.selected_files:
|
if self.selected_files:
|
||||||
@ -457,16 +434,10 @@ class FileDialog(QObject):
|
|||||||
self.accepted = bool(self.selected_files)
|
self.accepted = bool(self.selected_files)
|
||||||
|
|
||||||
def get_files(self):
|
def get_files(self):
|
||||||
if islinux and self.fd.result() != self.fd.Accepted:
|
|
||||||
return tuple()
|
|
||||||
if self.selected_files is None:
|
if self.selected_files is None:
|
||||||
return tuple(os.path.abspath(qstring_to_unicode(i)) for i in self.fd.selectedFiles())
|
return tuple(os.path.abspath(qstring_to_unicode(i)) for i in self.fd.selectedFiles())
|
||||||
return tuple(self.selected_files)
|
return tuple(self.selected_files)
|
||||||
|
|
||||||
def save_dir(self):
|
|
||||||
if self.fd:
|
|
||||||
dynamic[self.dialog_name] = self.fd.saveState()
|
|
||||||
|
|
||||||
|
|
||||||
def choose_dir(window, name, title):
|
def choose_dir(window, name, title):
|
||||||
fd = FileDialog(title, [], False, window, name=name,
|
fd = FileDialog(title, [], False, window, name=name,
|
||||||
|
@ -18,6 +18,24 @@ from calibre.ebooks.metadata.opf2 import OPFCreator
|
|||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.gui2.convert import Widget
|
from calibre.gui2.convert import Widget
|
||||||
|
|
||||||
|
def create_opf_file(db, book_id):
|
||||||
|
mi = db.get_metadata(book_id, index_is_id=True)
|
||||||
|
mi.application_id = uuid.uuid4()
|
||||||
|
opf = OPFCreator(os.getcwdu(), mi)
|
||||||
|
opf_file = PersistentTemporaryFile('.opf')
|
||||||
|
opf.render(opf_file)
|
||||||
|
opf_file.close()
|
||||||
|
return mi, opf_file
|
||||||
|
|
||||||
|
def create_cover_file(db, book_id):
|
||||||
|
cover = db.cover(book_id, index_is_id=True)
|
||||||
|
cf = None
|
||||||
|
if cover:
|
||||||
|
cf = PersistentTemporaryFile('.jpeg')
|
||||||
|
cf.write(cover)
|
||||||
|
cf.close()
|
||||||
|
return cf
|
||||||
|
|
||||||
class MetadataWidget(Widget, Ui_Form):
|
class MetadataWidget(Widget, Ui_Form):
|
||||||
|
|
||||||
TITLE = _('Metadata')
|
TITLE = _('Metadata')
|
||||||
@ -181,12 +199,7 @@ class MetadataWidget(Widget, Ui_Form):
|
|||||||
self.cover_file = self.opf_file = None
|
self.cover_file = self.opf_file = None
|
||||||
if self.db is not None:
|
if self.db is not None:
|
||||||
self.db.set_metadata(self.book_id, self.user_mi)
|
self.db.set_metadata(self.book_id, self.user_mi)
|
||||||
self.mi = self.db.get_metadata(self.book_id, index_is_id=True)
|
self.mi, self.opf_file = create_opf_file(self.db, self.book_id)
|
||||||
self.mi.application_id = uuid.uuid4()
|
|
||||||
opf = OPFCreator(os.getcwdu(), self.mi)
|
|
||||||
self.opf_file = PersistentTemporaryFile('.opf')
|
|
||||||
opf.render(self.opf_file)
|
|
||||||
self.opf_file.close()
|
|
||||||
if self.cover_changed and self.cover_data is not None:
|
if self.cover_changed and self.cover_data is not None:
|
||||||
self.db.set_cover(self.book_id, self.cover_data)
|
self.db.set_cover(self.book_id, self.cover_data)
|
||||||
cover = self.db.cover(self.book_id, index_is_id=True)
|
cover = self.db.cover(self.book_id, index_is_id=True)
|
||||||
|
@ -5,7 +5,7 @@ __copyright__ = '2009, John Schember <john@nachtimwald.com>'
|
|||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
from calibre.gui2.convert.pdf_input_ui import Ui_Form
|
from calibre.gui2.convert.pdf_input_ui import Ui_Form
|
||||||
from calibre.gui2.convert import Widget
|
from calibre.gui2.convert import Widget, QDoubleSpinBox
|
||||||
|
|
||||||
class PluginWidget(Widget, Ui_Form):
|
class PluginWidget(Widget, Ui_Form):
|
||||||
|
|
||||||
@ -17,3 +17,8 @@ class PluginWidget(Widget, Ui_Form):
|
|||||||
['no_images', 'unwrap_factor'])
|
['no_images', 'unwrap_factor'])
|
||||||
self.db, self.book_id = db, book_id
|
self.db, self.book_id = db, book_id
|
||||||
self.initialize_options(get_option, get_help, db, book_id)
|
self.initialize_options(get_option, get_help, db, book_id)
|
||||||
|
|
||||||
|
def set_value_handler(self, g, val):
|
||||||
|
if val is None and isinstance(g, QDoubleSpinBox):
|
||||||
|
g.setValue(0.0)
|
||||||
|
return True
|
||||||
|
@ -68,6 +68,36 @@ class GroupModel(QAbstractListModel):
|
|||||||
return QVariant(f)
|
return QVariant(f)
|
||||||
return NONE
|
return NONE
|
||||||
|
|
||||||
|
def get_preferred_input_format_for_book(db, book_id):
|
||||||
|
recs = load_specifics(db, book_id)
|
||||||
|
if recs:
|
||||||
|
return recs.get('gui_preferred_input_format', None)
|
||||||
|
|
||||||
|
def get_available_formats_for_book(db, book_id):
|
||||||
|
available_formats = db.formats(book_id, index_is_id=True)
|
||||||
|
if not available_formats:
|
||||||
|
available_formats = ''
|
||||||
|
return set([x.lower() for x in
|
||||||
|
available_formats.split(',')])
|
||||||
|
|
||||||
|
def get_supported_input_formats_for_book(db, book_id):
|
||||||
|
available_formats = get_available_formats_for_book(db, book_id)
|
||||||
|
input_formats = set([x.lower() for x in supported_input_formats()])
|
||||||
|
input_formats = sorted(available_formats.intersection(input_formats))
|
||||||
|
if not input_formats:
|
||||||
|
raise NoSupportedInputFormats
|
||||||
|
return input_formats
|
||||||
|
|
||||||
|
|
||||||
|
def get_input_format_for_book(db, book_id, pref):
|
||||||
|
if pref is None:
|
||||||
|
pref = get_preferred_input_format_for_book(db, book_id)
|
||||||
|
input_formats = get_supported_input_formats_for_book(db, book_id)
|
||||||
|
input_format = pref if pref in input_formats else \
|
||||||
|
sort_formats_by_preference(input_formats, prefs['input_format_order'])[0]
|
||||||
|
return input_format, input_formats
|
||||||
|
|
||||||
|
|
||||||
class Config(ResizableDialog, Ui_Dialog):
|
class Config(ResizableDialog, Ui_Dialog):
|
||||||
'''
|
'''
|
||||||
Configuration dialog for single book conversion. If accepted, has the
|
Configuration dialog for single book conversion. If accepted, has the
|
||||||
@ -86,12 +116,6 @@ class Config(ResizableDialog, Ui_Dialog):
|
|||||||
preferred_input_format=None, preferred_output_format=None):
|
preferred_input_format=None, preferred_output_format=None):
|
||||||
ResizableDialog.__init__(self, parent)
|
ResizableDialog.__init__(self, parent)
|
||||||
|
|
||||||
if preferred_input_format is None and db is not None:
|
|
||||||
recs = load_specifics(db, book_id)
|
|
||||||
if recs:
|
|
||||||
preferred_input_format = recs.get('gui_preferred_input_format',
|
|
||||||
None)
|
|
||||||
|
|
||||||
self.setup_input_output_formats(db, book_id, preferred_input_format,
|
self.setup_input_output_formats(db, book_id, preferred_input_format,
|
||||||
preferred_output_format)
|
preferred_output_format)
|
||||||
self.db, self.book_id = db, book_id
|
self.db, self.book_id = db, book_id
|
||||||
@ -194,22 +218,10 @@ class Config(ResizableDialog, Ui_Dialog):
|
|||||||
preferred_output_format):
|
preferred_output_format):
|
||||||
if preferred_output_format:
|
if preferred_output_format:
|
||||||
preferred_output_format = preferred_output_format.lower()
|
preferred_output_format = preferred_output_format.lower()
|
||||||
available_formats = db.formats(book_id, index_is_id=True)
|
|
||||||
if not available_formats:
|
|
||||||
available_formats = ''
|
|
||||||
available_formats = set([x.lower() for x in
|
|
||||||
available_formats.split(',')])
|
|
||||||
input_formats = set([x.lower() for x in supported_input_formats()])
|
|
||||||
input_formats = \
|
|
||||||
sorted(available_formats.intersection(input_formats))
|
|
||||||
if not input_formats:
|
|
||||||
raise NoSupportedInputFormats
|
|
||||||
output_formats = sorted(available_output_formats())
|
output_formats = sorted(available_output_formats())
|
||||||
output_formats.remove('oeb')
|
output_formats.remove('oeb')
|
||||||
preferred_input_format = preferred_input_format if \
|
input_format, input_formats = get_input_format_for_book(db, book_id,
|
||||||
preferred_input_format in input_formats else \
|
preferred_input_format)
|
||||||
sort_formats_by_preference(input_formats,
|
|
||||||
prefs['input_format_order'])[0]
|
|
||||||
preferred_output_format = preferred_output_format if \
|
preferred_output_format = preferred_output_format if \
|
||||||
preferred_output_format in output_formats else \
|
preferred_output_format in output_formats else \
|
||||||
sort_formats_by_preference(output_formats,
|
sort_formats_by_preference(output_formats,
|
||||||
@ -218,7 +230,7 @@ class Config(ResizableDialog, Ui_Dialog):
|
|||||||
input_formats])))
|
input_formats])))
|
||||||
self.output_formats.addItems(list(map(QString, [x.upper() for x in
|
self.output_formats.addItems(list(map(QString, [x.upper() for x in
|
||||||
output_formats])))
|
output_formats])))
|
||||||
self.input_formats.setCurrentIndex(input_formats.index(preferred_input_format))
|
self.input_formats.setCurrentIndex(input_formats.index(input_format))
|
||||||
self.output_formats.setCurrentIndex(output_formats.index(preferred_output_format))
|
self.output_formats.setCurrentIndex(output_formats.index(preferred_output_format))
|
||||||
|
|
||||||
def show_pane(self, index):
|
def show_pane(self, index):
|
||||||
|
@ -10,7 +10,7 @@ from PyQt4.Qt import QDialog, QListWidgetItem, QIcon, \
|
|||||||
QDialogButtonBox, QTabWidget, QBrush, QLineEdit, \
|
QDialogButtonBox, QTabWidget, QBrush, QLineEdit, \
|
||||||
QProgressDialog
|
QProgressDialog
|
||||||
|
|
||||||
from calibre.constants import islinux, iswindows, isosx
|
from calibre.constants import iswindows, isosx
|
||||||
from calibre.gui2.dialogs.config.config_ui import Ui_Dialog
|
from calibre.gui2.dialogs.config.config_ui import Ui_Dialog
|
||||||
from calibre.gui2 import qstring_to_unicode, choose_dir, error_dialog, config, \
|
from calibre.gui2 import qstring_to_unicode, choose_dir, error_dialog, config, \
|
||||||
ALL_COLUMNS, NONE, info_dialog, choose_files, \
|
ALL_COLUMNS, NONE, info_dialog, choose_files, \
|
||||||
@ -354,16 +354,10 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
self.connect(self.input_up, SIGNAL('clicked()'), self.up_input)
|
self.connect(self.input_up, SIGNAL('clicked()'), self.up_input)
|
||||||
self.connect(self.input_down, SIGNAL('clicked()'), self.down_input)
|
self.connect(self.input_down, SIGNAL('clicked()'), self.down_input)
|
||||||
|
|
||||||
dirs = config['frequently_used_directories']
|
|
||||||
rn = config['use_roman_numerals_for_series_number']
|
rn = config['use_roman_numerals_for_series_number']
|
||||||
self.timeout.setValue(prefs['network_timeout'])
|
self.timeout.setValue(prefs['network_timeout'])
|
||||||
self.roman_numerals.setChecked(rn)
|
self.roman_numerals.setChecked(rn)
|
||||||
self.new_version_notification.setChecked(config['new_version_notification'])
|
self.new_version_notification.setChecked(config['new_version_notification'])
|
||||||
self.directory_list.addItems(dirs)
|
|
||||||
self.connect(self.add_button, SIGNAL('clicked(bool)'), self.add_dir)
|
|
||||||
self.connect(self.remove_button, SIGNAL('clicked(bool)'), self.remove_dir)
|
|
||||||
if not islinux:
|
|
||||||
self.dirs_box.setVisible(False)
|
|
||||||
|
|
||||||
column_map = config['column_map']
|
column_map = config['column_map']
|
||||||
for col in column_map + [i for i in ALL_COLUMNS if i not in column_map]:
|
for col in column_map + [i for i in ALL_COLUMNS if i not in column_map]:
|
||||||
@ -432,6 +426,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
self.port.setValue(opts.port)
|
self.port.setValue(opts.port)
|
||||||
self.username.setText(opts.username)
|
self.username.setText(opts.username)
|
||||||
self.password.setText(opts.password if opts.password else '')
|
self.password.setText(opts.password if opts.password else '')
|
||||||
|
self.opt_max_opds_items.setValue(opts.max_opds_items)
|
||||||
self.auto_launch.setChecked(config['autolaunch_server'])
|
self.auto_launch.setChecked(config['autolaunch_server'])
|
||||||
self.systray_icon.setChecked(config['systray_icon'])
|
self.systray_icon.setChecked(config['systray_icon'])
|
||||||
self.sync_news.setChecked(config['upload_news_to_device'])
|
self.sync_news.setChecked(config['upload_news_to_device'])
|
||||||
@ -457,6 +452,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
self.connect(self.sync_news, SIGNAL('toggled(bool)'),
|
self.connect(self.sync_news, SIGNAL('toggled(bool)'),
|
||||||
self.delete_news.setEnabled)
|
self.delete_news.setEnabled)
|
||||||
self.setup_conversion_options()
|
self.setup_conversion_options()
|
||||||
|
self.opt_worker_limit.setValue(config['worker_limit'])
|
||||||
|
|
||||||
def create_symlinks(self):
|
def create_symlinks(self):
|
||||||
from calibre.utils.osx_symlinks import create_symlinks
|
from calibre.utils.osx_symlinks import create_symlinks
|
||||||
@ -674,15 +670,6 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
if dir:
|
if dir:
|
||||||
self.location.setText(dir)
|
self.location.setText(dir)
|
||||||
|
|
||||||
def add_dir(self):
|
|
||||||
dir = choose_dir(self, 'Add freq dir dialog', 'select directory')
|
|
||||||
if dir:
|
|
||||||
self.directory_list.addItem(dir)
|
|
||||||
|
|
||||||
def remove_dir(self):
|
|
||||||
idx = self.directory_list.currentRow()
|
|
||||||
if idx >= 0:
|
|
||||||
self.directory_list.takeItem(idx)
|
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
mcs = unicode(self.max_cover_size.text()).strip()
|
mcs = unicode(self.max_cover_size.text()).strip()
|
||||||
@ -696,6 +683,12 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
return
|
return
|
||||||
if not self.add_save.save_settings():
|
if not self.add_save.save_settings():
|
||||||
return
|
return
|
||||||
|
wl = self.opt_worker_limit.value()
|
||||||
|
if wl%2 != 0:
|
||||||
|
wl += 1
|
||||||
|
config['worker_limit'] = wl
|
||||||
|
|
||||||
|
|
||||||
config['use_roman_numerals_for_series_number'] = bool(self.roman_numerals.isChecked())
|
config['use_roman_numerals_for_series_number'] = bool(self.roman_numerals.isChecked())
|
||||||
config['new_version_notification'] = bool(self.new_version_notification.isChecked())
|
config['new_version_notification'] = bool(self.new_version_notification.isChecked())
|
||||||
prefs['network_timeout'] = int(self.timeout.value())
|
prefs['network_timeout'] = int(self.timeout.value())
|
||||||
@ -722,6 +715,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
sc.set('password', unicode(self.password.text()).strip())
|
sc.set('password', unicode(self.password.text()).strip())
|
||||||
sc.set('port', self.port.value())
|
sc.set('port', self.port.value())
|
||||||
sc.set('max_cover', mcs)
|
sc.set('max_cover', mcs)
|
||||||
|
sc.set('max_opds_items', self.opt_max_opds_items.value())
|
||||||
config['delete_news_from_library_on_upload'] = self.delete_news.isChecked()
|
config['delete_news_from_library_on_upload'] = self.delete_news.isChecked()
|
||||||
config['upload_news_to_device'] = self.sync_news.isChecked()
|
config['upload_news_to_device'] = self.sync_news.isChecked()
|
||||||
config['search_as_you_type'] = self.search_as_you_type.isChecked()
|
config['search_as_you_type'] = self.search_as_you_type.isChecked()
|
||||||
@ -742,10 +736,6 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
d.exec_()
|
d.exec_()
|
||||||
else:
|
else:
|
||||||
self.database_location = os.path.abspath(path)
|
self.database_location = os.path.abspath(path)
|
||||||
self.directories = [
|
|
||||||
qstring_to_unicode(self.directory_list.item(i).text()) for i in \
|
|
||||||
range(self.directory_list.count())]
|
|
||||||
config['frequently_used_directories'] = self.directories
|
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
|
||||||
class VacThread(QThread):
|
class VacThread(QThread):
|
||||||
|
@ -281,103 +281,6 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<widget class="QGroupBox" name="dirs_box">
|
|
||||||
<property name="title">
|
|
||||||
<string>Frequently used directories</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QGridLayout" name="_5">
|
|
||||||
<item row="0" column="0">
|
|
||||||
<layout class="QHBoxLayout" name="_6">
|
|
||||||
<item>
|
|
||||||
<widget class="QListWidget" name="directory_list">
|
|
||||||
<property name="alternatingRowColors">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="iconSize">
|
|
||||||
<size>
|
|
||||||
<width>22</width>
|
|
||||||
<height>22</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QVBoxLayout" name="_7">
|
|
||||||
<item>
|
|
||||||
<spacer>
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Vertical</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>20</width>
|
|
||||||
<height>40</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QToolButton" name="add_button">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Add a directory to the frequently used directories list</string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>...</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../../../../resources/images.qrc">
|
|
||||||
<normaloff>:/images/plus.svg</normaloff>:/images/plus.svg</iconset>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<spacer>
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Vertical</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>20</width>
|
|
||||||
<height>40</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QToolButton" name="remove_button">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Remove a directory from the frequently used directories list</string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>...</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../../../../resources/images.qrc">
|
|
||||||
<normaloff>:/images/list_remove.svg</normaloff>:/images/list_remove.svg</iconset>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<spacer>
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Vertical</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>20</width>
|
|
||||||
<height>40</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="page">
|
<widget class="QWidget" name="page">
|
||||||
@ -682,30 +585,38 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="page_2">
|
<widget class="QWidget" name="page_2">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_9">
|
<layout class="QGridLayout" name="gridLayout_3">
|
||||||
<item>
|
<item row="0" column="0">
|
||||||
<spacer>
|
<widget class="QLabel" name="label_5">
|
||||||
<property name="orientation">
|
<property name="text">
|
||||||
<enum>Qt::Horizontal</enum>
|
<string>&Maximum number of waiting worker processes (needs restart):</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="buddy">
|
||||||
<size>
|
<cstring>opt_worker_limit</cstring>
|
||||||
<width>154</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
</property>
|
||||||
</spacer>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="0" column="1">
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_12">
|
<widget class="QSpinBox" name="opt_worker_limit">
|
||||||
<item>
|
<property name="minimum">
|
||||||
|
<number>2</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>10000</number>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<number>2</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0" colspan="2">
|
||||||
<widget class="QPushButton" name="compact_button">
|
<widget class="QPushButton" name="compact_button">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Check database integrity</string>
|
<string>&Check database integrity</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="2" column="0" colspan="2">
|
||||||
<widget class="QPushButton" name="button_osx_symlinks">
|
<widget class="QPushButton" name="button_osx_symlinks">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Install command line tools</string>
|
<string>&Install command line tools</string>
|
||||||
@ -713,21 +624,6 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<spacer>
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>153</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="page_4">
|
<widget class="QWidget" name="page_4">
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
@ -797,13 +693,6 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="1">
|
<item row="4" column="1">
|
||||||
<widget class="QCheckBox" name="show_server_password">
|
|
||||||
<property name="text">
|
|
||||||
<string>&Show password</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="1">
|
|
||||||
<widget class="QLineEdit" name="max_cover_size">
|
<widget class="QLineEdit" name="max_cover_size">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>The maximum size (widthxheight) for displayed covers. Larger covers are resized. </string>
|
<string>The maximum size (widthxheight) for displayed covers. Larger covers are resized. </string>
|
||||||
@ -813,7 +702,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0">
|
<item row="4" column="0">
|
||||||
<widget class="QLabel" name="label_3">
|
<widget class="QLabel" name="label_3">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Max. &cover size:</string>
|
<string>Max. &cover size:</string>
|
||||||
@ -823,6 +712,33 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="QCheckBox" name="show_server_password">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Show password</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QLabel" name="label_15">
|
||||||
|
<property name="text">
|
||||||
|
<string>Max. &OPDS items per query:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>opt_max_opds_items</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
|
<widget class="QSpinBox" name="opt_max_opds_items">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>10000</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<string>Active Jobs</string>
|
<string>Active Jobs</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowIcon">
|
<property name="windowIcon">
|
||||||
<iconset resource="../../../../resources/images.qrc">
|
<iconset resource="../../../work/calibre/resources/images.qrc">
|
||||||
<normaloff>:/images/jobs.svg</normaloff>:/images/jobs.svg</iconset>
|
<normaloff>:/images/jobs.svg</normaloff>:/images/jobs.svg</iconset>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout">
|
<layout class="QVBoxLayout">
|
||||||
@ -57,6 +57,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="stop_all_jobs_button">
|
||||||
|
<property name="text">
|
||||||
|
<string>Stop &all jobs</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
@ -67,7 +74,7 @@
|
|||||||
</customwidget>
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="../../../../resources/images.qrc"/>
|
<include location="../../../work/calibre/resources/images.qrc"/>
|
||||||
</resources>
|
</resources>
|
||||||
<connections/>
|
<connections/>
|
||||||
</ui>
|
</ui>
|
||||||
|
@ -252,9 +252,19 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
if ext not in extensions:
|
if ext not in extensions:
|
||||||
self.db.remove_format(self.row, ext, notify=False)
|
self.db.remove_format(self.row, ext, notify=False)
|
||||||
|
|
||||||
def __init__(self, window, row, db, accepted_callback=None):
|
def do_cancel_all(self):
|
||||||
|
self.cancel_all = True
|
||||||
|
self.reject()
|
||||||
|
|
||||||
|
def __init__(self, window, row, db, accepted_callback=None, cancel_all=False):
|
||||||
ResizableDialog.__init__(self, window)
|
ResizableDialog.__init__(self, window)
|
||||||
self.bc_box.layout().setAlignment(self.cover, Qt.AlignCenter|Qt.AlignHCenter)
|
self.bc_box.layout().setAlignment(self.cover, Qt.AlignCenter|Qt.AlignHCenter)
|
||||||
|
self.cancel_all = False
|
||||||
|
if cancel_all:
|
||||||
|
self.__abort_button = self.button_box.addButton(self.button_box.Abort)
|
||||||
|
self.__abort_button.setToolTip(_('Abort the editing of all remaining books'))
|
||||||
|
self.connect(self.__abort_button, SIGNAL('clicked()'),
|
||||||
|
self.do_cancel_all)
|
||||||
self.splitter.setStretchFactor(100, 1)
|
self.splitter.setStretchFactor(100, 1)
|
||||||
self.db = db
|
self.db = db
|
||||||
self.pi = ProgressIndicator(self)
|
self.pi = ProgressIndicator(self)
|
||||||
|
@ -15,7 +15,7 @@ from PyQt4.Qt import QAbstractTableModel, QVariant, QModelIndex, Qt, \
|
|||||||
|
|
||||||
from calibre.utils.ipc.server import Server
|
from calibre.utils.ipc.server import Server
|
||||||
from calibre.utils.ipc.job import ParallelJob
|
from calibre.utils.ipc.job import ParallelJob
|
||||||
from calibre.gui2 import Dispatcher, error_dialog, NONE
|
from calibre.gui2 import Dispatcher, error_dialog, NONE, config
|
||||||
from calibre.gui2.device import DeviceJob
|
from calibre.gui2.device import DeviceJob
|
||||||
from calibre.gui2.dialogs.jobs_ui import Ui_JobsDialog
|
from calibre.gui2.dialogs.jobs_ui import Ui_JobsDialog
|
||||||
from calibre import __appname__
|
from calibre import __appname__
|
||||||
@ -31,7 +31,7 @@ class JobManager(QAbstractTableModel):
|
|||||||
|
|
||||||
self.jobs = []
|
self.jobs = []
|
||||||
self.add_job = Dispatcher(self._add_job)
|
self.add_job = Dispatcher(self._add_job)
|
||||||
self.server = Server()
|
self.server = Server(limit=int(config['worker_limit']/2.0))
|
||||||
self.changed_queue = Queue()
|
self.changed_queue = Queue()
|
||||||
|
|
||||||
self.timer = QTimer(self)
|
self.timer = QTimer(self)
|
||||||
@ -193,6 +193,12 @@ class JobManager(QAbstractTableModel):
|
|||||||
_('Job has already run')).exec_()
|
_('Job has already run')).exec_()
|
||||||
self.server.kill_job(job)
|
self.server.kill_job(job)
|
||||||
|
|
||||||
|
def kill_all_jobs(self):
|
||||||
|
for job in self.jobs:
|
||||||
|
if isinstance(job, DeviceJob) or job.duration is not None:
|
||||||
|
continue
|
||||||
|
self.server.kill_job(job)
|
||||||
|
|
||||||
def terminate_all_jobs(self):
|
def terminate_all_jobs(self):
|
||||||
self.server.killall()
|
self.server.killall()
|
||||||
|
|
||||||
@ -230,6 +236,8 @@ class JobsDialog(QDialog, Ui_JobsDialog):
|
|||||||
self.kill_job)
|
self.kill_job)
|
||||||
self.connect(self.details_button, SIGNAL('clicked()'),
|
self.connect(self.details_button, SIGNAL('clicked()'),
|
||||||
self.show_details)
|
self.show_details)
|
||||||
|
self.connect(self.stop_all_jobs_button, SIGNAL('clicked()'),
|
||||||
|
self.kill_all_jobs)
|
||||||
self.connect(self, SIGNAL('kill_job(int, PyQt_PyObject)'),
|
self.connect(self, SIGNAL('kill_job(int, PyQt_PyObject)'),
|
||||||
self.jobs_view.model().kill_job)
|
self.jobs_view.model().kill_job)
|
||||||
self.pb_delegate = ProgressBarDelegate(self)
|
self.pb_delegate = ProgressBarDelegate(self)
|
||||||
@ -247,7 +255,8 @@ class JobsDialog(QDialog, Ui_JobsDialog):
|
|||||||
self.jobs_view.show_details(index)
|
self.jobs_view.show_details(index)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def kill_all_jobs(self):
|
||||||
|
self.model.kill_all_jobs()
|
||||||
|
|
||||||
def closeEvent(self, e):
|
def closeEvent(self, e):
|
||||||
self.jobs_view.write_settings()
|
self.jobs_view.write_settings()
|
||||||
|
@ -20,11 +20,12 @@ from calibre.constants import __version__, __appname__, \
|
|||||||
from calibre.utils.filenames import ascii_filename
|
from calibre.utils.filenames import ascii_filename
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.utils.config import prefs, dynamic
|
from calibre.utils.config import prefs, dynamic
|
||||||
|
from calibre.utils.ipc import ADDRESS, RC
|
||||||
from calibre.utils.ipc.server import Server
|
from calibre.utils.ipc.server import Server
|
||||||
from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \
|
from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \
|
||||||
initialize_file_icon_provider, question_dialog,\
|
initialize_file_icon_provider, question_dialog,\
|
||||||
pixmap_to_data, choose_dir, ORG_NAME, \
|
pixmap_to_data, choose_dir, ORG_NAME, \
|
||||||
set_sidebar_directories, Dispatcher, \
|
Dispatcher, \
|
||||||
Application, available_height, \
|
Application, available_height, \
|
||||||
max_available_height, config, info_dialog, \
|
max_available_height, config, info_dialog, \
|
||||||
available_width, GetMetadata
|
available_width, GetMetadata
|
||||||
@ -50,9 +51,6 @@ from calibre.ebooks import BOOK_EXTENSIONS
|
|||||||
from calibre.library.database2 import LibraryDatabase2, CoverCache
|
from calibre.library.database2 import LibraryDatabase2, CoverCache
|
||||||
from calibre.gui2.dialogs.confirm_delete import confirm
|
from calibre.gui2.dialogs.confirm_delete import confirm
|
||||||
|
|
||||||
ADDRESS = r'\\.\pipe\CalibreGUI' if iswindows else \
|
|
||||||
os.path.expanduser('~/.calibre-gui.socket')
|
|
||||||
|
|
||||||
class SaveMenu(QMenu):
|
class SaveMenu(QMenu):
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
@ -208,6 +206,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
self.latest_version = ' '
|
self.latest_version = ' '
|
||||||
self.vanity.setText(self.vanity_template%dict(version=' ', device=' '))
|
self.vanity.setText(self.vanity_template%dict(version=' ', device=' '))
|
||||||
self.device_info = ' '
|
self.device_info = ' '
|
||||||
|
if not opts.no_update_check:
|
||||||
self.update_checker = CheckForUpdates()
|
self.update_checker = CheckForUpdates()
|
||||||
QObject.connect(self.update_checker,
|
QObject.connect(self.update_checker,
|
||||||
SIGNAL('update_found(PyQt_PyObject)'), self.update_found)
|
SIGNAL('update_found(PyQt_PyObject)'), self.update_found)
|
||||||
@ -531,7 +530,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
self._sync_menu.trigger_default)
|
self._sync_menu.trigger_default)
|
||||||
|
|
||||||
def add_spare_server(self, *args):
|
def add_spare_server(self, *args):
|
||||||
self.spare_servers.append(Server())
|
self.spare_servers.append(Server(limit=int(config['worker_limit']/2.0)))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def spare_server(self):
|
def spare_server(self):
|
||||||
@ -1027,10 +1026,13 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
self._metadata_view_id = self.library_view.model().db.id(row.row())
|
self._metadata_view_id = self.library_view.model().db.id(row.row())
|
||||||
d = MetadataSingleDialog(self, row.row(),
|
d = MetadataSingleDialog(self, row.row(),
|
||||||
self.library_view.model().db,
|
self.library_view.model().db,
|
||||||
accepted_callback=accepted)
|
accepted_callback=accepted,
|
||||||
|
cancel_all=rows.index(row) < len(rows)-1)
|
||||||
self.connect(d, SIGNAL('view_format(PyQt_PyObject)'),
|
self.connect(d, SIGNAL('view_format(PyQt_PyObject)'),
|
||||||
self.metadata_view_format)
|
self.metadata_view_format)
|
||||||
d.exec_()
|
d.exec_()
|
||||||
|
if d.cancel_all:
|
||||||
|
break
|
||||||
if rows:
|
if rows:
|
||||||
current = self.library_view.currentIndex()
|
current = self.library_view.currentIndex()
|
||||||
self.library_view.model().current_changed(current, previous)
|
self.library_view.model().current_changed(current, previous)
|
||||||
@ -1484,8 +1486,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
self.save_menu.actions()[2].setText(
|
self.save_menu.actions()[2].setText(
|
||||||
_('Save only %s format to disk')%
|
_('Save only %s format to disk')%
|
||||||
prefs['output_format'].upper())
|
prefs['output_format'].upper())
|
||||||
if hasattr(d, 'directories'):
|
|
||||||
set_sidebar_directories(d.directories)
|
|
||||||
self.library_view.model().read_config()
|
self.library_view.model().read_config()
|
||||||
self.create_device_menu()
|
self.create_device_menu()
|
||||||
|
|
||||||
@ -1546,12 +1546,16 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
self.view_menu.actions()[1].setEnabled(True)
|
self.view_menu.actions()[1].setEnabled(True)
|
||||||
self.action_open_containing_folder.setEnabled(True)
|
self.action_open_containing_folder.setEnabled(True)
|
||||||
self.action_sync.setEnabled(True)
|
self.action_sync.setEnabled(True)
|
||||||
|
self.status_bar.tag_view_button.setEnabled(True)
|
||||||
|
self.status_bar.cover_flow_button.setEnabled(True)
|
||||||
else:
|
else:
|
||||||
self.action_edit.setEnabled(False)
|
self.action_edit.setEnabled(False)
|
||||||
self.action_convert.setEnabled(False)
|
self.action_convert.setEnabled(False)
|
||||||
self.view_menu.actions()[1].setEnabled(False)
|
self.view_menu.actions()[1].setEnabled(False)
|
||||||
self.action_open_containing_folder.setEnabled(False)
|
self.action_open_containing_folder.setEnabled(False)
|
||||||
self.action_sync.setEnabled(False)
|
self.action_sync.setEnabled(False)
|
||||||
|
self.status_bar.tag_view_button.setEnabled(False)
|
||||||
|
self.status_bar.cover_flow_button.setEnabled(False)
|
||||||
|
|
||||||
|
|
||||||
def device_job_exception(self, job):
|
def device_job_exception(self, job):
|
||||||
@ -1644,7 +1648,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
geometry = config['main_window_geometry']
|
geometry = config['main_window_geometry']
|
||||||
if geometry is not None:
|
if geometry is not None:
|
||||||
self.restoreGeometry(geometry)
|
self.restoreGeometry(geometry)
|
||||||
set_sidebar_directories(None)
|
|
||||||
self.tool_bar.setIconSize(config['toolbar_icon_size'])
|
self.tool_bar.setIconSize(config['toolbar_icon_size'])
|
||||||
self.tool_bar.setToolButtonStyle(
|
self.tool_bar.setToolButtonStyle(
|
||||||
Qt.ToolButtonTextUnderIcon if \
|
Qt.ToolButtonTextUnderIcon if \
|
||||||
@ -1814,6 +1817,8 @@ path_to_ebook to the database.
|
|||||||
help=_('Start minimized to system tray.'))
|
help=_('Start minimized to system tray.'))
|
||||||
parser.add_option('-v', '--verbose', default=0, action='count',
|
parser.add_option('-v', '--verbose', default=0, action='count',
|
||||||
help=_('Log debugging information to console'))
|
help=_('Log debugging information to console'))
|
||||||
|
parser.add_option('--no-update-check', default=False, action='store_true',
|
||||||
|
help=_('Do not check for updates'))
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def init_qt(args):
|
def init_qt(args):
|
||||||
@ -1882,14 +1887,6 @@ def cant_start(msg=_('If you are sure it is not running')+', ',
|
|||||||
d.exec_()
|
d.exec_()
|
||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
|
|
||||||
class RC(Thread):
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
from multiprocessing.connection import Client
|
|
||||||
self.done = False
|
|
||||||
self.conn = Client(ADDRESS)
|
|
||||||
self.done = True
|
|
||||||
|
|
||||||
def communicate(args):
|
def communicate(args):
|
||||||
t = RC()
|
t = RC()
|
||||||
t.start()
|
t.start()
|
||||||
|
@ -36,6 +36,7 @@ class SearchBox2(QComboBox):
|
|||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
QComboBox.__init__(self, parent)
|
QComboBox.__init__(self, parent)
|
||||||
|
self.normal_background = 'rgb(255, 255, 255, 0%)'
|
||||||
self.line_edit = SearchLineEdit(self)
|
self.line_edit = SearchLineEdit(self)
|
||||||
self.setLineEdit(self.line_edit)
|
self.setLineEdit(self.line_edit)
|
||||||
self.connect(self.line_edit, SIGNAL('key_pressed(PyQt_PyObject)'),
|
self.connect(self.line_edit, SIGNAL('key_pressed(PyQt_PyObject)'),
|
||||||
@ -62,14 +63,18 @@ class SearchBox2(QComboBox):
|
|||||||
|
|
||||||
def normalize_state(self):
|
def normalize_state(self):
|
||||||
self.setEditText('')
|
self.setEditText('')
|
||||||
self.line_edit.setStyleSheet('QLineEdit { color: black; background-color: white; }')
|
self.line_edit.setStyleSheet(
|
||||||
|
'QLineEdit { color: black; background-color: %s; }' %
|
||||||
|
self.normal_background)
|
||||||
self.help_state = False
|
self.help_state = False
|
||||||
|
|
||||||
def clear_to_help(self):
|
def clear_to_help(self):
|
||||||
self.setEditText(self.help_text)
|
self.setEditText(self.help_text)
|
||||||
self.line_edit.home(False)
|
self.line_edit.home(False)
|
||||||
self.help_state = True
|
self.help_state = True
|
||||||
self.line_edit.setStyleSheet('QLineEdit { color: gray; background-color: white; }')
|
self.line_edit.setStyleSheet(
|
||||||
|
'QLineEdit { color: gray; background-color: %s; }' %
|
||||||
|
self.normal_background)
|
||||||
self.emit(SIGNAL('cleared()'))
|
self.emit(SIGNAL('cleared()'))
|
||||||
|
|
||||||
def text(self):
|
def text(self):
|
||||||
@ -84,7 +89,7 @@ class SearchBox2(QComboBox):
|
|||||||
return self.clear_to_help()
|
return self.clear_to_help()
|
||||||
col = 'rgba(0,255,0,20%)' if ok else 'rgb(255,0,0,20%)'
|
col = 'rgba(0,255,0,20%)' if ok else 'rgb(255,0,0,20%)'
|
||||||
if not self.colorize:
|
if not self.colorize:
|
||||||
col = 'white'
|
col = self.normal_background
|
||||||
self.line_edit.setStyleSheet('QLineEdit { color: black; background-color: %s; }' % col)
|
self.line_edit.setStyleSheet('QLineEdit { color: black; background-color: %s; }' % col)
|
||||||
|
|
||||||
def key_pressed(self, event):
|
def key_pressed(self, event):
|
||||||
|
@ -14,8 +14,10 @@ from PyQt4.Qt import QDialog
|
|||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.gui2 import warning_dialog, question_dialog
|
from calibre.gui2 import warning_dialog, question_dialog
|
||||||
from calibre.gui2.convert.single import NoSupportedInputFormats
|
from calibre.gui2.convert.single import NoSupportedInputFormats
|
||||||
from calibre.gui2.convert.single import Config as SingleConfig
|
from calibre.gui2.convert.single import Config as SingleConfig, \
|
||||||
|
get_input_format_for_book
|
||||||
from calibre.gui2.convert.bulk import BulkConfig
|
from calibre.gui2.convert.bulk import BulkConfig
|
||||||
|
from calibre.gui2.convert.metadata import create_opf_file, create_cover_file
|
||||||
from calibre.customize.conversion import OptionRecommendation
|
from calibre.customize.conversion import OptionRecommendation
|
||||||
from calibre.utils.config import prefs
|
from calibre.utils.config import prefs
|
||||||
from calibre.ebooks.conversion.config import GuiRecommendations, \
|
from calibre.ebooks.conversion.config import GuiRecommendations, \
|
||||||
@ -55,7 +57,11 @@ def convert_single_ebook(parent, db, book_ids, auto_conversion=False, out_format
|
|||||||
out_file.close()
|
out_file.close()
|
||||||
temp_files = []
|
temp_files = []
|
||||||
|
|
||||||
desc = _('Convert book %d of %d (%s)') % (i + 1, total, repr(mi.title))
|
try:
|
||||||
|
dtitle = unicode(mi.title)
|
||||||
|
except:
|
||||||
|
dtitle = repr(mi.title)
|
||||||
|
desc = _('Convert book %d of %d (%s)') % (i + 1, total, dtitle)
|
||||||
|
|
||||||
recs = cPickle.loads(d.recommendations)
|
recs = cPickle.loads(d.recommendations)
|
||||||
if d.opf_file is not None:
|
if d.opf_file is not None:
|
||||||
@ -108,13 +114,11 @@ def convert_bulk_ebook(parent, db, book_ids, out_format=None):
|
|||||||
book_ids = convert_existing(parent, db, book_ids, output_format)
|
book_ids = convert_existing(parent, db, book_ids, output_format)
|
||||||
for i, book_id in enumerate(book_ids):
|
for i, book_id in enumerate(book_ids):
|
||||||
temp_files = []
|
temp_files = []
|
||||||
|
input_format = get_input_format_for_book(db, book_id, None)[0]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
d = SingleConfig(parent, db, book_id, None, output_format)
|
mi, opf_file = create_opf_file(db, book_id)
|
||||||
d.accept()
|
in_file = db.format_abspath(book_id, input_format, True)
|
||||||
|
|
||||||
mi = db.get_metadata(book_id, True)
|
|
||||||
in_file = db.format_abspath(book_id, d.input_format, True)
|
|
||||||
|
|
||||||
out_file = PersistentTemporaryFile('.' + output_format)
|
out_file = PersistentTemporaryFile('.' + output_format)
|
||||||
out_file.write(output_format)
|
out_file.write(output_format)
|
||||||
@ -122,7 +126,7 @@ def convert_bulk_ebook(parent, db, book_ids, out_format=None):
|
|||||||
temp_files = []
|
temp_files = []
|
||||||
|
|
||||||
combined_recs = GuiRecommendations()
|
combined_recs = GuiRecommendations()
|
||||||
default_recs = load_defaults('%s_input' % d.input_format)
|
default_recs = load_defaults('%s_input' % input_format)
|
||||||
specific_recs = load_specifics(db, book_id)
|
specific_recs = load_specifics(db, book_id)
|
||||||
for key in default_recs:
|
for key in default_recs:
|
||||||
combined_recs[key] = default_recs[key]
|
combined_recs[key] = default_recs[key]
|
||||||
@ -133,24 +137,30 @@ def convert_bulk_ebook(parent, db, book_ids, out_format=None):
|
|||||||
save_specifics(db, book_id, combined_recs)
|
save_specifics(db, book_id, combined_recs)
|
||||||
lrecs = list(combined_recs.to_recommendations())
|
lrecs = list(combined_recs.to_recommendations())
|
||||||
|
|
||||||
if d.opf_file is not None:
|
cover_file = create_cover_file(db, book_id)
|
||||||
lrecs.append(('read_metadata_from_opf', d.opf_file.name,
|
|
||||||
|
if opf_file is not None:
|
||||||
|
lrecs.append(('read_metadata_from_opf', opf_file.name,
|
||||||
OptionRecommendation.HIGH))
|
OptionRecommendation.HIGH))
|
||||||
temp_files.append(d.opf_file)
|
temp_files.append(opf_file)
|
||||||
if d.cover_file is not None:
|
if cover_file is not None:
|
||||||
lrecs.append(('cover', d.cover_file.name,
|
lrecs.append(('cover', cover_file.name,
|
||||||
OptionRecommendation.HIGH))
|
OptionRecommendation.HIGH))
|
||||||
temp_files.append(d.cover_file)
|
temp_files.append(cover_file)
|
||||||
|
|
||||||
for x in list(lrecs):
|
for x in list(lrecs):
|
||||||
if x[0] == 'debug_pipeline':
|
if x[0] == 'debug_pipeline':
|
||||||
lrecs.remove(x)
|
lrecs.remove(x)
|
||||||
|
|
||||||
desc = _('Convert book %d of %d (%s)') % (i + 1, total, repr(mi.title))
|
try:
|
||||||
|
dtitle = unicode(mi.title)
|
||||||
|
except:
|
||||||
|
dtitle = repr(mi.title)
|
||||||
|
desc = _('Convert book %d of %d (%s)') % (i + 1, total, dtitle)
|
||||||
|
|
||||||
args = [in_file, out_file.name, lrecs]
|
args = [in_file, out_file.name, lrecs]
|
||||||
temp_files.append(out_file)
|
temp_files.append(out_file)
|
||||||
jobs.append(('gui_convert', args, desc, d.output_format.upper(), book_id, temp_files))
|
jobs.append(('gui_convert', args, desc, output_format.upper(), book_id, temp_files))
|
||||||
|
|
||||||
changed = True
|
changed = True
|
||||||
except NoSupportedInputFormats:
|
except NoSupportedInputFormats:
|
||||||
@ -168,6 +178,7 @@ def convert_bulk_ebook(parent, db, book_ids, out_format=None):
|
|||||||
'source format was found.') % (len(res), total),
|
'source format was found.') % (len(res), total),
|
||||||
msg).exec_()
|
msg).exec_()
|
||||||
|
|
||||||
|
jobs.reverse()
|
||||||
return jobs, changed, bad
|
return jobs, changed, bad
|
||||||
|
|
||||||
def fetch_scheduled_recipe(recipe, script):
|
def fetch_scheduled_recipe(recipe, script):
|
||||||
|
@ -22,4 +22,7 @@ def server_config(defaults=None):
|
|||||||
help='Development mode. Server automatically restarts on file changes and serves code files (html, css, js) from the file system instead of calibre\'s resource system.')
|
help='Development mode. Server automatically restarts on file changes and serves code files (html, css, js) from the file system instead of calibre\'s resource system.')
|
||||||
c.add_opt('max_cover', ['--max-cover'], default='600x800',
|
c.add_opt('max_cover', ['--max-cover'], default='600x800',
|
||||||
help=_('The maximum size for displayed covers. Default is %default.'))
|
help=_('The maximum size for displayed covers. Default is %default.'))
|
||||||
|
c.add_opt('max_opds_items', ['--max-opds-items'], default=30,
|
||||||
|
help=_('The maximum number of matches to return per OPDS query. '
|
||||||
|
'This affects Stanza, WordPlayer, etc. integration.'))
|
||||||
return c
|
return c
|
||||||
|
@ -13,11 +13,6 @@ from urllib import quote
|
|||||||
|
|
||||||
from calibre import terminal_controller, preferred_encoding, prints
|
from calibre import terminal_controller, preferred_encoding, prints
|
||||||
from calibre.utils.config import OptionParser, prefs
|
from calibre.utils.config import OptionParser, prefs
|
||||||
try:
|
|
||||||
from calibre.utils.single_qt_application import send_message
|
|
||||||
send_message
|
|
||||||
except:
|
|
||||||
send_message = None
|
|
||||||
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.ebooks.metadata.opf2 import OPFCreator, OPF
|
from calibre.ebooks.metadata.opf2 import OPFCreator, OPF
|
||||||
@ -102,6 +97,20 @@ STANZA_TEMPLATE='''\
|
|||||||
</feed>
|
</feed>
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
def send_message(msg=''):
|
||||||
|
prints('Notifying calibre of the change')
|
||||||
|
from calibre.utils.ipc import RC
|
||||||
|
import time
|
||||||
|
t = RC(print_error=False)
|
||||||
|
t.start()
|
||||||
|
time.sleep(3)
|
||||||
|
if t.done:
|
||||||
|
t.conn.send('refreshdb:'+msg)
|
||||||
|
t.conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_parser(usage):
|
def get_parser(usage):
|
||||||
parser = OptionParser(usage)
|
parser = OptionParser(usage)
|
||||||
go = parser.add_option_group('GLOBAL OPTIONS')
|
go = parser.add_option_group('GLOBAL OPTIONS')
|
||||||
@ -314,8 +323,7 @@ def do_add(db, paths, one_book_per_directory, recurse, add_duplicates):
|
|||||||
print >>sys.stderr, '\t', title+':'
|
print >>sys.stderr, '\t', title+':'
|
||||||
print >>sys.stderr, '\t\t ', path
|
print >>sys.stderr, '\t\t ', path
|
||||||
|
|
||||||
if send_message is not None:
|
send_message()
|
||||||
send_message('refreshdb:', 'calibre GUI')
|
|
||||||
finally:
|
finally:
|
||||||
sys.stdout = orig
|
sys.stdout = orig
|
||||||
|
|
||||||
@ -356,8 +364,7 @@ def do_remove(db, ids):
|
|||||||
for y in x:
|
for y in x:
|
||||||
db.delete_book(y)
|
db.delete_book(y)
|
||||||
|
|
||||||
if send_message is not None:
|
send_message()
|
||||||
send_message('refreshdb:', 'calibre GUI')
|
|
||||||
|
|
||||||
def remove_option_parser():
|
def remove_option_parser():
|
||||||
return get_parser(_(
|
return get_parser(_(
|
||||||
@ -386,7 +393,7 @@ def command_remove(args, dbpath):
|
|||||||
else:
|
else:
|
||||||
ids.append(int(y[0]))
|
ids.append(int(y[0]))
|
||||||
|
|
||||||
do_remove(get_db(dbpath, opts), ids)
|
do_remove(get_db(dbpath, opts), set(ids))
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@ -484,8 +491,7 @@ def do_set_metadata(db, id, stream):
|
|||||||
mi = OPF(stream)
|
mi = OPF(stream)
|
||||||
db.set_metadata(id, mi)
|
db.set_metadata(id, mi)
|
||||||
do_show_metadata(db, id, False)
|
do_show_metadata(db, id, False)
|
||||||
if send_message is not None:
|
send_message()
|
||||||
send_message('refreshdb:', 'calibre GUI')
|
|
||||||
|
|
||||||
def set_metadata_option_parser():
|
def set_metadata_option_parser():
|
||||||
return get_parser(_(
|
return get_parser(_(
|
||||||
|
@ -1047,6 +1047,18 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
|||||||
return [ (i[0], i[1]) for i in \
|
return [ (i[0], i[1]) for i in \
|
||||||
self.conn.get('SELECT id, name FROM series')]
|
self.conn.get('SELECT id, name FROM series')]
|
||||||
|
|
||||||
|
def series_name(self, series_id):
|
||||||
|
return self.conn.get('SELECT name FROM series WHERE id=%d'%series_id,
|
||||||
|
all=False)
|
||||||
|
|
||||||
|
def author_name(self, author_id):
|
||||||
|
return self.conn.get('SELECT name FROM authors WHERE id=%d'%author_id,
|
||||||
|
all=False)
|
||||||
|
|
||||||
|
def tag_name(self, tag_id):
|
||||||
|
return self.conn.get('SELECT name FROM tags WHERE id=%d'%tag_id,
|
||||||
|
all=False)
|
||||||
|
|
||||||
def all_authors(self):
|
def all_authors(self):
|
||||||
return [ (i[0], i[1]) for i in \
|
return [ (i[0], i[1]) for i in \
|
||||||
self.conn.get('SELECT id, name FROM authors')]
|
self.conn.get('SELECT id, name FROM authors')]
|
||||||
@ -1058,6 +1070,11 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
|||||||
def all_tags(self):
|
def all_tags(self):
|
||||||
return [i[0].strip() for i in self.conn.get('SELECT name FROM tags') if i[0].strip()]
|
return [i[0].strip() for i in self.conn.get('SELECT name FROM tags') if i[0].strip()]
|
||||||
|
|
||||||
|
def all_tags2(self):
|
||||||
|
return [ (i[0], i[1]) for i in \
|
||||||
|
self.conn.get('SELECT id, name FROM tags')]
|
||||||
|
|
||||||
|
|
||||||
def conversion_options(self, id, format):
|
def conversion_options(self, id, format):
|
||||||
data = self.conn.get('SELECT data FROM conversion_options WHERE book=? AND format=?', (id, format.upper()), all=False)
|
data = self.conn.get('SELECT data FROM conversion_options WHERE book=? AND format=?', (id, format.upper()), all=False)
|
||||||
if data:
|
if data:
|
||||||
|
@ -921,9 +921,12 @@ class LibraryDatabase2(LibraryDatabase):
|
|||||||
'''
|
'''
|
||||||
Removes book from the result cache and the underlying database.
|
Removes book from the result cache and the underlying database.
|
||||||
'''
|
'''
|
||||||
|
try:
|
||||||
path = os.path.join(self.library_path, self.path(id, index_is_id=True))
|
path = os.path.join(self.library_path, self.path(id, index_is_id=True))
|
||||||
|
except:
|
||||||
|
path = None
|
||||||
self.data.remove(id)
|
self.data.remove(id)
|
||||||
if os.path.exists(path):
|
if path and os.path.exists(path):
|
||||||
try:
|
try:
|
||||||
winshell.delete_file(path, no_confirm=True, silent=True)
|
winshell.delete_file(path, no_confirm=True, silent=True)
|
||||||
except:
|
except:
|
||||||
|
@ -101,30 +101,13 @@ class LibraryServer(object):
|
|||||||
</entry>
|
</entry>
|
||||||
'''))
|
'''))
|
||||||
|
|
||||||
STANZA_AUTHOR_ENTRY=MarkupTemplate(textwrap.dedent('''\
|
STANZA_SUBCATALOG_ENTRY=MarkupTemplate(textwrap.dedent('''\
|
||||||
<entry xmlns:py="http://genshi.edgewall.org/">
|
<entry xmlns:py="http://genshi.edgewall.org/">
|
||||||
<title>${authors}</title>
|
<title>${title}</title>
|
||||||
<id>urn:calibre:${record[FM['id']]}</id>
|
<id>urn:calibre:${id}</id>
|
||||||
<updated>${timestamp}</updated>
|
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
||||||
<link type="application/atom+xml" href="/stanza/?authorid=${record[FM['id']]}" />
|
<link type="application/atom+xml" href="/stanza/?${what}id=${id}" />
|
||||||
</entry>
|
<content type="text">${count} books</content>
|
||||||
'''))
|
|
||||||
|
|
||||||
STANZA_TAG_ENTRY=MarkupTemplate(textwrap.dedent('''\
|
|
||||||
<entry xmlns:py="http://genshi.edgewall.org/">
|
|
||||||
<title>${tags}</title>
|
|
||||||
<id>urn:calibre:${record[FM['id']]}</id>
|
|
||||||
<updated>${timestamp}</updated>
|
|
||||||
<link type="application/atom+xml" href="/stanza/?tagid=${record[FM['id']]}" />
|
|
||||||
</entry>
|
|
||||||
'''))
|
|
||||||
|
|
||||||
STANZA_SERIES_ENTRY=MarkupTemplate(textwrap.dedent('''\
|
|
||||||
<entry xmlns:py="http://genshi.edgewall.org/">
|
|
||||||
<title>${series}</title>
|
|
||||||
<id>urn:calibre:${record[FM['id']]}</id>
|
|
||||||
<updated>${timestamp}</updated>
|
|
||||||
<link type="application/atom+xml" href="/stanza/?seriesid=${record[FM['id']]}" />
|
|
||||||
</entry>
|
</entry>
|
||||||
'''))
|
'''))
|
||||||
|
|
||||||
@ -135,6 +118,7 @@ class LibraryServer(object):
|
|||||||
<id>$id</id>
|
<id>$id</id>
|
||||||
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
||||||
<link rel="search" title="Search" type="application/atom+xml" href="/stanza/?search={searchTerms}"/>
|
<link rel="search" title="Search" type="application/atom+xml" href="/stanza/?search={searchTerms}"/>
|
||||||
|
${Markup(next_link)}
|
||||||
<author>
|
<author>
|
||||||
<name>calibre</name>
|
<name>calibre</name>
|
||||||
<uri>http://calibre.kovidgoyal.net</uri>
|
<uri>http://calibre.kovidgoyal.net</uri>
|
||||||
@ -167,30 +151,35 @@ class LibraryServer(object):
|
|||||||
<id>urn:uuid:fc000fa0-8c23-11de-a31d-0002a5d5c51b</id>
|
<id>urn:uuid:fc000fa0-8c23-11de-a31d-0002a5d5c51b</id>
|
||||||
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
||||||
<link type="application/atom+xml" href="/stanza/?sortby=byauthor" />
|
<link type="application/atom+xml" href="/stanza/?sortby=byauthor" />
|
||||||
|
<content type="text">Books sorted by Author</content>
|
||||||
</entry>
|
</entry>
|
||||||
<entry>
|
<entry>
|
||||||
<title>By Title</title>
|
<title>By Title</title>
|
||||||
<id>urn:uuid:1df4fe40-8c24-11de-b4c6-0002a5d5c51b</id>
|
<id>urn:uuid:1df4fe40-8c24-11de-b4c6-0002a5d5c51b</id>
|
||||||
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
||||||
<link type="application/atom+xml" href="/stanza/?sortby=bytitle" />
|
<link type="application/atom+xml" href="/stanza/?sortby=bytitle" />
|
||||||
|
<content type="text">Books sorted by Title</content>
|
||||||
</entry>
|
</entry>
|
||||||
<entry>
|
<entry>
|
||||||
<title>By Newest</title>
|
<title>By Newest</title>
|
||||||
<id>urn:uuid:3c6d4940-8c24-11de-a4d7-0002a5d5c51b</id>
|
<id>urn:uuid:3c6d4940-8c24-11de-a4d7-0002a5d5c51b</id>
|
||||||
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
||||||
<link type="application/atom+xml" href="/stanza/?sortby=bynewest" />
|
<link type="application/atom+xml" href="/stanza/?sortby=bynewest" />
|
||||||
|
<content type="text">Books sorted by Date</content>
|
||||||
</entry>
|
</entry>
|
||||||
<entry>
|
<entry>
|
||||||
<title>By Tag</title>
|
<title>By Tag</title>
|
||||||
<id>urn:uuid:824921e8-db8a-4e61-7d38-f1ce41502853</id>
|
<id>urn:uuid:824921e8-db8a-4e61-7d38-f1ce41502853</id>
|
||||||
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
||||||
<link type="application/atom+xml" href="/stanza/?sortby=bytag" />
|
<link type="application/atom+xml" href="/stanza/?sortby=bytag" />
|
||||||
|
<content type="text">Books sorted by Tags</content>
|
||||||
</entry>
|
</entry>
|
||||||
<entry>
|
<entry>
|
||||||
<title>By Series</title>
|
<title>By Series</title>
|
||||||
<id>urn:uuid:512a5e50-a88f-f6b8-82aa-8f129c719f61</id>
|
<id>urn:uuid:512a5e50-a88f-f6b8-82aa-8f129c719f61</id>
|
||||||
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
||||||
<link type="application/atom+xml" href="/stanza/?sortby=byseries" />
|
<link type="application/atom+xml" href="/stanza/?sortby=byseries" />
|
||||||
|
<content type="text">Books sorted by Series</content>
|
||||||
</entry>
|
</entry>
|
||||||
</feed>
|
</feed>
|
||||||
'''))
|
'''))
|
||||||
@ -204,6 +193,7 @@ class LibraryServer(object):
|
|||||||
self.opts = opts
|
self.opts = opts
|
||||||
self.max_cover_width, self.max_cover_height = \
|
self.max_cover_width, self.max_cover_height = \
|
||||||
map(int, self.opts.max_cover.split('x'))
|
map(int, self.opts.max_cover.split('x'))
|
||||||
|
self.max_stanza_items = opts.max_opds_items
|
||||||
path = P('content_server')
|
path = P('content_server')
|
||||||
self.build_time = datetime.fromtimestamp(os.stat(path).st_mtime)
|
self.build_time = datetime.fromtimestamp(os.stat(path).st_mtime)
|
||||||
self.default_cover = open(P('content_server/default_cover.jpg'), 'rb').read()
|
self.default_cover = open(P('content_server/default_cover.jpg'), 'rb').read()
|
||||||
@ -281,6 +271,7 @@ class LibraryServer(object):
|
|||||||
if cover is None:
|
if cover is None:
|
||||||
cover = self.default_cover
|
cover = self.default_cover
|
||||||
cherrypy.response.headers['Content-Type'] = 'image/jpeg'
|
cherrypy.response.headers['Content-Type'] = 'image/jpeg'
|
||||||
|
cherrypy.response.timeout = 3600
|
||||||
path = getattr(cover, 'name', False)
|
path = getattr(cover, 'name', False)
|
||||||
updated = datetime.utcfromtimestamp(os.stat(path).st_mtime) if path and \
|
updated = datetime.utcfromtimestamp(os.stat(path).st_mtime) if path and \
|
||||||
os.access(path, os.R_OK) else self.build_time
|
os.access(path, os.R_OK) else self.build_time
|
||||||
@ -326,6 +317,7 @@ class LibraryServer(object):
|
|||||||
if mt is None:
|
if mt is None:
|
||||||
mt = 'application/octet-stream'
|
mt = 'application/octet-stream'
|
||||||
cherrypy.response.headers['Content-Type'] = mt
|
cherrypy.response.headers['Content-Type'] = mt
|
||||||
|
cherrypy.response.timeout = 3600
|
||||||
path = getattr(fmt, 'name', None)
|
path = getattr(fmt, 'name', None)
|
||||||
if path and os.path.exists(path):
|
if path and os.path.exists(path):
|
||||||
updated = datetime.utcfromtimestamp(os.stat(path).st_mtime)
|
updated = datetime.utcfromtimestamp(os.stat(path).st_mtime)
|
||||||
@ -367,58 +359,125 @@ class LibraryServer(object):
|
|||||||
8:'Aug', 9:'Sep', 10:'Oct', 11:'Nov', 12:'Dec'}
|
8:'Aug', 9:'Sep', 10:'Oct', 11:'Nov', 12:'Dec'}
|
||||||
return lm.replace('month', month[updated.month])
|
return lm.replace('month', month[updated.month])
|
||||||
|
|
||||||
|
def get_matches(self, location, query):
|
||||||
|
base = self.db.data.get_matches(location, query)
|
||||||
|
epub = self.db.data.get_matches('format', 'epub')
|
||||||
|
pdb = self.db.data.get_matches('format', 'pdb')
|
||||||
|
return base.intersection(epub.union(pdb))
|
||||||
|
|
||||||
|
def stanza_sortby_subcategory(self, updated, sortby, offset):
|
||||||
|
what, subtitle = sortby[2:], ''
|
||||||
|
if sortby == 'byseries':
|
||||||
|
data = self.db.all_series()
|
||||||
|
data = [(x[0], x[1], len(self.get_matches('series', x[1]))) for x in data]
|
||||||
|
subtitle = 'Books by series'
|
||||||
|
elif sortby == 'byauthor':
|
||||||
|
data = self.db.all_authors()
|
||||||
|
data = [(x[0], x[1], len(self.get_matches('authors', x[1]))) for x in data]
|
||||||
|
subtitle = 'Books by author'
|
||||||
|
elif sortby == 'bytag':
|
||||||
|
data = self.db.all_tags2()
|
||||||
|
data = [(x[0], x[1], len(self.get_matches('tags', x[1]))) for x in data]
|
||||||
|
subtitle = 'Books by tag'
|
||||||
|
data = [x for x in data if x[2] > 0]
|
||||||
|
data.sort(cmp=lambda x, y: cmp(x[1], y[1]))
|
||||||
|
next_offset = offset + self.max_stanza_items
|
||||||
|
rdata = data[offset:next_offset]
|
||||||
|
if next_offset >= len(data):
|
||||||
|
next_offset = -1
|
||||||
|
entries = [self.STANZA_SUBCATALOG_ENTRY.generate(title=title, id=id,
|
||||||
|
what=what, updated=updated, count=c).render('xml').decode('utf-8') for id,
|
||||||
|
title, c in rdata]
|
||||||
|
next_link = ''
|
||||||
|
if next_offset > -1:
|
||||||
|
next_link = ('<link rel="next" title="Next" '
|
||||||
|
'type="application/atom+xml" href="/stanza/?sortby=%s&offset=%d"/>\n'
|
||||||
|
) % (sortby, next_offset)
|
||||||
|
return self.STANZA.generate(subtitle=subtitle, data=entries, FM=FIELD_MAP,
|
||||||
|
updated=updated, id='urn:calibre:main', next_link=next_link).render('xml')
|
||||||
|
|
||||||
|
def stanza_main(self, updated):
|
||||||
|
return self.STANZA_MAIN.generate(subtitle='', data=[], FM=FIELD_MAP,
|
||||||
|
updated=updated, id='urn:calibre:main').render('xml')
|
||||||
|
|
||||||
@expose
|
@expose
|
||||||
def stanza(self, search=None, sortby=None, authorid=None, tagid=None, seriesid=None):
|
def stanza(self, search=None, sortby=None, authorid=None, tagid=None,
|
||||||
|
seriesid=None, offset=0):
|
||||||
'Feeds to read calibre books on a ipod with stanza.'
|
'Feeds to read calibre books on a ipod with stanza.'
|
||||||
books = []
|
books = []
|
||||||
updated = self.db.last_modified()
|
updated = self.db.last_modified()
|
||||||
|
offset = int(offset)
|
||||||
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
|
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
|
||||||
cherrypy.response.headers['Content-Type'] = 'text/xml'
|
cherrypy.response.headers['Content-Type'] = 'text/xml'
|
||||||
|
# Main feed
|
||||||
if not sortby and not search and not authorid and not tagid and not seriesid:
|
if not sortby and not search and not authorid and not tagid and not seriesid:
|
||||||
return self.STANZA_MAIN.generate(subtitle='', data=books, FM=FIELD_MAP,
|
return self.stanza_main(updated)
|
||||||
updated=updated, id='urn:calibre:main').render('xml')
|
if sortby in ('byseries', 'byauthor', 'bytag'):
|
||||||
|
return self.stanza_sortby_subcategory(updated, sortby, offset)
|
||||||
|
|
||||||
|
# Get matching ids
|
||||||
if authorid:
|
if authorid:
|
||||||
authorid=int(authorid)
|
authorid=int(authorid)
|
||||||
au = self.db.authors(authorid, index_is_id=True)
|
au = self.db.author_name(authorid)
|
||||||
ids = self.db.data.get_matches('authors', au)
|
ids = self.get_matches('authors', au)
|
||||||
elif tagid:
|
elif tagid:
|
||||||
tagid=int(tagid)
|
tagid=int(tagid)
|
||||||
ta = self.db.tags(tagid, index_is_id=True)
|
ta = self.db.tag_name(tagid)
|
||||||
ids = self.db.data.get_matches('tags', ta)
|
ids = self.get_matches('tags', ta)
|
||||||
elif seriesid:
|
elif seriesid:
|
||||||
seriesid=int(seriesid)
|
seriesid=int(seriesid)
|
||||||
se = self.db.series(seriesid, index_is_id=True)
|
se = self.db.series_name(seriesid)
|
||||||
ids = self.db.data.get_matches('series', se)
|
ids = self.get_matches('series', se)
|
||||||
else:
|
else:
|
||||||
ids = self.db.data.parse(search) if search and search.strip() else self.db.data.universal_set()
|
ids = self.db.data.parse(search) if search and search.strip() else self.db.data.universal_set()
|
||||||
record_list = list(iter(self.db))
|
record_list = list(iter(self.db))
|
||||||
if sortby == "byauthor":
|
|
||||||
record_list.sort(lambda x, y: cmp(x[FIELD_MAP['author_sort']], y[FIELD_MAP['author_sort']]))
|
# Sort the record list
|
||||||
elif sortby == "bytag":
|
if sortby == "bytitle" or authorid or tagid:
|
||||||
record_list.sort(lambda x, y: cmp(x[FIELD_MAP['tags']], y[FIELD_MAP['tags']]))
|
|
||||||
elif sortby == "byseries":
|
|
||||||
record_list.sort(lambda x, y: cmp(x[FIELD_MAP['series']], y[FIELD_MAP['series']]))
|
|
||||||
elif sortby == "bytitle" or authorid or tagid:
|
|
||||||
record_list.sort(lambda x, y: cmp(title_sort(x[FIELD_MAP['title']]),
|
record_list.sort(lambda x, y: cmp(title_sort(x[FIELD_MAP['title']]),
|
||||||
title_sort(y[FIELD_MAP['title']])))
|
title_sort(y[FIELD_MAP['title']])))
|
||||||
elif seriesid:
|
elif seriesid:
|
||||||
record_list.sort(lambda x, y: cmp(x[FIELD_MAP['series_index']], y[FIELD_MAP['series_index']]))
|
record_list.sort(lambda x, y: cmp(x[FIELD_MAP['series_index']], y[FIELD_MAP['series_index']]))
|
||||||
else:
|
else: # Sort by date
|
||||||
record_list = reversed(record_list)
|
record_list = reversed(record_list)
|
||||||
|
|
||||||
|
|
||||||
|
fmts = FIELD_MAP['formats']
|
||||||
|
pat = re.compile(r'EPUB|PDB', re.IGNORECASE)
|
||||||
|
record_list = [x for x in record_list if x[0] in ids and
|
||||||
|
pat.search(x[fmts] if x[fmts] else '') is not None]
|
||||||
|
next_offset = offset + self.max_stanza_items
|
||||||
|
nrecord_list = record_list[offset:next_offset]
|
||||||
|
if next_offset >= len(record_list):
|
||||||
|
next_offset = -1
|
||||||
|
|
||||||
|
next_link = ''
|
||||||
|
if next_offset > -1:
|
||||||
|
q = ['offset=%d'%next_offset]
|
||||||
|
for x in ('search', 'sortby', 'authorid', 'tagid', 'seriesid'):
|
||||||
|
val = locals()[x]
|
||||||
|
if val is not None:
|
||||||
|
val = prepare_string_for_xml(unicode(val), True)
|
||||||
|
q.append('%s=%s'%(x, val))
|
||||||
|
next_link = ('<link rel="next" title="Next" '
|
||||||
|
'type="application/atom+xml" href="/stanza/?%s"/>\n'
|
||||||
|
) % '&'.join(q)
|
||||||
|
|
||||||
author_list=[]
|
author_list=[]
|
||||||
tag_list=[]
|
tag_list=[]
|
||||||
series_list=[]
|
series_list=[]
|
||||||
for record in record_list:
|
|
||||||
if record[0] not in ids: continue
|
for record in nrecord_list:
|
||||||
r = record[FIELD_MAP['formats']]
|
r = record[FIELD_MAP['formats']]
|
||||||
r = r.upper() if r else ''
|
r = r.upper() if r else ''
|
||||||
if 'EPUB' in r or 'PDB' in r:
|
|
||||||
z = record[FIELD_MAP['authors']]
|
z = record[FIELD_MAP['authors']]
|
||||||
if not z:
|
if not z:
|
||||||
z = _('Unknown')
|
z = _('Unknown')
|
||||||
authors = ' & '.join([i.replace('|', ',') for i in
|
authors = ' & '.join([i.replace('|', ',') for i in
|
||||||
z.split(',')])
|
z.split(',')])
|
||||||
|
|
||||||
|
# Setup extra description
|
||||||
extra = []
|
extra = []
|
||||||
rating = record[FIELD_MAP['rating']]
|
rating = record[FIELD_MAP['rating']]
|
||||||
if rating > 0:
|
if rating > 0:
|
||||||
@ -433,57 +492,29 @@ class LibraryServer(object):
|
|||||||
extra.append('SERIES: %s [%s]<br />'%\
|
extra.append('SERIES: %s [%s]<br />'%\
|
||||||
(prepare_string_for_xml(series),
|
(prepare_string_for_xml(series),
|
||||||
fmt_sidx(float(record[FIELD_MAP['series_index']]))))
|
fmt_sidx(float(record[FIELD_MAP['series_index']]))))
|
||||||
|
|
||||||
fmt = 'epub' if 'EPUB' in r else 'pdb'
|
fmt = 'epub' if 'EPUB' in r else 'pdb'
|
||||||
mimetype = guess_type('dummy.'+fmt)[0]
|
mimetype = guess_type('dummy.'+fmt)[0]
|
||||||
if sortby == "byauthor":
|
|
||||||
if authors and authors not in author_list:
|
# Create the sub-catalog, which is either a list of
|
||||||
author_list.append(authors)
|
# authors/tags/series or a list of books
|
||||||
books.append(self.STANZA_AUTHOR_ENTRY.generate(
|
data = dict(
|
||||||
|
record=record,
|
||||||
|
updated=updated,
|
||||||
authors=authors,
|
authors=authors,
|
||||||
record=record, FM=FIELD_MAP,
|
|
||||||
port=self.opts.port,
|
|
||||||
extra=''.join(extra),
|
|
||||||
mimetype=mimetype,
|
|
||||||
fmt=fmt,
|
|
||||||
timestamp=strftime('%Y-%m-%dT%H:%M:%S+00:00', record[5]),
|
|
||||||
).render('xml').decode('utf8'))
|
|
||||||
elif sortby == "bytag":
|
|
||||||
if tags and tags not in tag_list:
|
|
||||||
tag_list.append(tags)
|
|
||||||
books.append(self.STANZA_TAG_ENTRY.generate(
|
|
||||||
tags=tags,
|
tags=tags,
|
||||||
record=record, FM=FIELD_MAP,
|
|
||||||
port=self.opts.port,
|
|
||||||
extra=''.join(extra),
|
|
||||||
mimetype=mimetype,
|
|
||||||
fmt=fmt,
|
|
||||||
timestamp=strftime('%Y-%m-%dT%H:%M:%S+00:00', record[5]),
|
|
||||||
).render('xml').decode('utf8'))
|
|
||||||
elif sortby == "byseries":
|
|
||||||
if series and series not in series_list:
|
|
||||||
series_list.append(series)
|
|
||||||
books.append(self.STANZA_SERIES_ENTRY.generate(
|
|
||||||
series=series,
|
series=series,
|
||||||
record=record, FM=FIELD_MAP,
|
FM=FIELD_MAP,
|
||||||
port=self.opts.port,
|
extra='\n'.join(extra),
|
||||||
extra=''.join(extra),
|
|
||||||
mimetype=mimetype,
|
mimetype=mimetype,
|
||||||
fmt=fmt,
|
fmt=fmt,
|
||||||
timestamp=strftime('%Y-%m-%dT%H:%M:%S+00:00', record[5]),
|
timestamp=strftime('%Y-%m-%dT%H:%M:%S+00:00', record[5])
|
||||||
).render('xml').decode('utf8'))
|
)
|
||||||
else:
|
books.append(self.STANZA_ENTRY.generate(**data)\
|
||||||
books.append(self.STANZA_ENTRY.generate(
|
.render('xml').decode('utf8'))
|
||||||
authors=authors,
|
|
||||||
record=record, FM=FIELD_MAP,
|
|
||||||
port=self.opts.port,
|
|
||||||
extra=''.join(extra),
|
|
||||||
mimetype=mimetype,
|
|
||||||
fmt=fmt,
|
|
||||||
timestamp=strftime('%Y-%m-%dT%H:%M:%S+00:00', record[5]),
|
|
||||||
).render('xml').decode('utf8'))
|
|
||||||
|
|
||||||
return self.STANZA.generate(subtitle='', data=books, FM=FIELD_MAP,
|
return self.STANZA.generate(subtitle='', data=books, FM=FIELD_MAP,
|
||||||
updated=updated, id='urn:calibre:main').render('xml')
|
next_link=next_link, updated=updated, id='urn:calibre:main').render('xml')
|
||||||
|
|
||||||
|
|
||||||
@expose
|
@expose
|
||||||
@ -537,7 +568,9 @@ class LibraryServer(object):
|
|||||||
cherrypy.request.headers.get('Want-OPDS-Catalog', 919) != 919 or \
|
cherrypy.request.headers.get('Want-OPDS-Catalog', 919) != 919 or \
|
||||||
ua.startswith('Stanza')
|
ua.startswith('Stanza')
|
||||||
return self.stanza(search=kwargs.get('search', None), sortby=kwargs.get('sortby',None), authorid=kwargs.get('authorid',None),
|
return self.stanza(search=kwargs.get('search', None), sortby=kwargs.get('sortby',None), authorid=kwargs.get('authorid',None),
|
||||||
tagid=kwargs.get('tagid',None), seriesid=kwargs.get('seriesid',None)) if want_opds else self.static('index.html')
|
tagid=kwargs.get('tagid',None),
|
||||||
|
seriesid=kwargs.get('seriesid',None),
|
||||||
|
offset=kwargs.get('offset', 0)) if want_opds else self.static('index.html')
|
||||||
|
|
||||||
|
|
||||||
@expose
|
@expose
|
||||||
|
@ -22,6 +22,22 @@ _run_once = False
|
|||||||
if not _run_once:
|
if not _run_once:
|
||||||
_run_once = True
|
_run_once = True
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Platform specific modules
|
||||||
|
winutil = winutilerror = None
|
||||||
|
if iswindows:
|
||||||
|
winutil, winutilerror = plugins['winutil']
|
||||||
|
if not winutil:
|
||||||
|
raise RuntimeError('Failed to load the winutil plugin: %s'%winutilerror)
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
sys.argv[1:] = winutil.argv()[1-len(sys.argv):]
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Convert command line arguments to unicode
|
||||||
|
for i in range(1, len(sys.argv)):
|
||||||
|
if not isinstance(sys.argv[i], unicode):
|
||||||
|
sys.argv[i] = sys.argv[i].decode(preferred_encoding, 'replace')
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Setup resources
|
# Setup resources
|
||||||
import calibre.utils.resources as resources
|
import calibre.utils.resources as resources
|
||||||
@ -89,18 +105,4 @@ if not _run_once:
|
|||||||
os.path.join = my_join
|
os.path.join = my_join
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# Platform specific modules
|
|
||||||
winutil = winutilerror = None
|
|
||||||
if iswindows:
|
|
||||||
winutil, winutilerror = plugins['winutil']
|
|
||||||
if not winutil:
|
|
||||||
raise RuntimeError('Failed to load the winutil plugin: %s'%winutilerror)
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
sys.argv[1:] = winutil.argv()[1-len(sys.argv):]
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# Convert command line arguments to unicode
|
|
||||||
for i in range(1, len(sys.argv)):
|
|
||||||
if not isinstance(sys.argv[i], unicode):
|
|
||||||
sys.argv[i] = sys.argv[i].decode(preferred_encoding, 'replace')
|
|
||||||
|
@ -148,6 +148,11 @@ else:
|
|||||||
path=MOBILEREAD+file, app=__appname__,
|
path=MOBILEREAD+file, app=__appname__,
|
||||||
note=Markup(\
|
note=Markup(\
|
||||||
'''
|
'''
|
||||||
|
<p>If you are updating from a version of calibre older than 0.6.12 on
|
||||||
|
<b>Windows XP</b>, first uninstall calibre, then delete the C:\Program
|
||||||
|
Files\calibre folder (the location may be different if you previously
|
||||||
|
installed calibre elsewhere) and only then install the new version of
|
||||||
|
calibre.</p><p><br /></p>
|
||||||
<p>If you are using the <b>SONY PRS-500</b> and %(appname)s does not detect your reader, read on:</p>
|
<p>If you are using the <b>SONY PRS-500</b> and %(appname)s does not detect your reader, read on:</p>
|
||||||
<blockquote>
|
<blockquote>
|
||||||
<p>
|
<p>
|
||||||
|
@ -108,8 +108,7 @@ sudo python -c "import urllib2; exec urllib2.urlopen('http://status.calibre-eboo
|
|||||||
<pre class="wiki">
|
<pre class="wiki">
|
||||||
wget -O- http://calibre.kovidgoyal.net/downloads/${app}-${version}.tar.gz | tar xvz
|
wget -O- http://calibre.kovidgoyal.net/downloads/${app}-${version}.tar.gz | tar xvz
|
||||||
cd calibre*
|
cd calibre*
|
||||||
python setup.py build_ext && python setup.py build && sudo python setup.py install
|
sudo python setup.py install
|
||||||
sudo calibre_postinstall
|
|
||||||
</pre>
|
</pre>
|
||||||
Note that if your distribution does not have a
|
Note that if your distribution does not have a
|
||||||
correctly compiled libunrar.so, ${app} will not
|
correctly compiled libunrar.so, ${app} will not
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -20,6 +20,8 @@ from collections import defaultdict
|
|||||||
if os.environ.has_key('CALIBRE_CONFIG_DIRECTORY'):
|
if os.environ.has_key('CALIBRE_CONFIG_DIRECTORY'):
|
||||||
config_dir = os.path.abspath(os.environ['CALIBRE_CONFIG_DIRECTORY'])
|
config_dir = os.path.abspath(os.environ['CALIBRE_CONFIG_DIRECTORY'])
|
||||||
elif iswindows:
|
elif iswindows:
|
||||||
|
if plugins['winutil'][0] is None:
|
||||||
|
raise Exception(plugins['winutil'][1])
|
||||||
config_dir = plugins['winutil'][0].special_folder_path(plugins['winutil'][0].CSIDL_APPDATA)
|
config_dir = plugins['winutil'][0].special_folder_path(plugins['winutil'][0].CSIDL_APPDATA)
|
||||||
if not os.access(config_dir, os.W_OK|os.X_OK):
|
if not os.access(config_dir, os.W_OK|os.X_OK):
|
||||||
config_dir = os.path.expanduser('~')
|
config_dir = os.path.expanduser('~')
|
||||||
|
@ -6,5 +6,30 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
import os
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
from calibre.constants import iswindows
|
||||||
|
|
||||||
|
ADDRESS = r'\\.\pipe\CalibreGUI' if iswindows else \
|
||||||
|
os.path.expanduser('~/.calibre-gui.socket')
|
||||||
|
|
||||||
|
class RC(Thread):
|
||||||
|
|
||||||
|
def __init__(self, print_error=True):
|
||||||
|
self.print_error = print_error
|
||||||
|
Thread.__init__(self)
|
||||||
|
self.conn = None
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
from multiprocessing.connection import Client
|
||||||
|
self.done = False
|
||||||
|
try:
|
||||||
|
self.conn = Client(ADDRESS)
|
||||||
|
self.done = True
|
||||||
|
except:
|
||||||
|
if self.print_error:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ class BaseJob(object):
|
|||||||
self.done2 = None
|
self.done2 = None
|
||||||
self.killed = False
|
self.killed = False
|
||||||
self.failed = False
|
self.failed = False
|
||||||
|
self.kill_on_start = False
|
||||||
self.start_time = None
|
self.start_time = None
|
||||||
self.result = None
|
self.result = None
|
||||||
self.duration = None
|
self.duration = None
|
||||||
|
@ -6,7 +6,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import os, cPickle, time, tempfile
|
import sys, os, cPickle, time, tempfile
|
||||||
from math import ceil
|
from math import ceil
|
||||||
from threading import Thread, RLock
|
from threading import Thread, RLock
|
||||||
from Queue import Queue, Empty
|
from Queue import Queue, Empty
|
||||||
@ -83,14 +83,16 @@ class CriticalError(Exception):
|
|||||||
|
|
||||||
class Server(Thread):
|
class Server(Thread):
|
||||||
|
|
||||||
def __init__(self, notify_on_job_done=lambda x: x, pool_size=None):
|
def __init__(self, notify_on_job_done=lambda x: x, pool_size=None,
|
||||||
|
limit=sys.maxint):
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
self.daemon = True
|
self.daemon = True
|
||||||
global _counter
|
global _counter
|
||||||
self.id = _counter+1
|
self.id = _counter+1
|
||||||
_counter += 1
|
_counter += 1
|
||||||
|
|
||||||
self.pool_size = cpu_count() if pool_size is None else pool_size
|
limit = min(limit, cpu_count())
|
||||||
|
self.pool_size = limit if pool_size is None else pool_size
|
||||||
self.notify_on_job_done = notify_on_job_done
|
self.notify_on_job_done = notify_on_job_done
|
||||||
self.auth_key = os.urandom(32)
|
self.auth_key = os.urandom(32)
|
||||||
self.address = arbitrary_address('AF_PIPE' if iswindows else 'AF_UNIX')
|
self.address = arbitrary_address('AF_PIPE' if iswindows else 'AF_UNIX')
|
||||||
@ -168,6 +170,7 @@ class Server(Thread):
|
|||||||
except Empty:
|
except Empty:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Get notifications from worker process
|
||||||
for worker in self.workers:
|
for worker in self.workers:
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
@ -177,6 +180,7 @@ class Server(Thread):
|
|||||||
except Empty:
|
except Empty:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Remove finished jobs
|
||||||
for worker in [w for w in self.workers if not w.is_alive]:
|
for worker in [w for w in self.workers if not w.is_alive]:
|
||||||
self.workers.remove(worker)
|
self.workers.remove(worker)
|
||||||
job = worker.job
|
job = worker.job
|
||||||
@ -189,16 +193,24 @@ class Server(Thread):
|
|||||||
job.duration = time.time() - job.start_time
|
job.duration = time.time() - job.start_time
|
||||||
self.changed_jobs_queue.put(job)
|
self.changed_jobs_queue.put(job)
|
||||||
|
|
||||||
|
# Start new workers
|
||||||
if len(self.pool) + len(self.workers) < self.pool_size:
|
if len(self.pool) + len(self.workers) < self.pool_size:
|
||||||
try:
|
try:
|
||||||
self.pool.append(self.launch_worker())
|
self.pool.append(self.launch_worker())
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Start waiting jobs
|
||||||
if len(self.pool) > 0 and len(self.waiting_jobs) > 0:
|
if len(self.pool) > 0 and len(self.waiting_jobs) > 0:
|
||||||
job = self.waiting_jobs.pop()
|
job = self.waiting_jobs.pop()
|
||||||
worker = self.pool.pop()
|
|
||||||
job.start_time = time.time()
|
job.start_time = time.time()
|
||||||
|
if job.kill_on_start:
|
||||||
|
job.duration = 0.0
|
||||||
|
job.returncode = 1
|
||||||
|
job.killed = job.failed = True
|
||||||
|
job.result = None
|
||||||
|
else:
|
||||||
|
worker = self.pool.pop()
|
||||||
worker.start_job(job)
|
worker.start_job(job)
|
||||||
self.workers.append(worker)
|
self.workers.append(worker)
|
||||||
job.log_path = worker.log_path
|
job.log_path = worker.log_path
|
||||||
@ -215,11 +227,13 @@ class Server(Thread):
|
|||||||
self.kill_queue.put(job)
|
self.kill_queue.put(job)
|
||||||
|
|
||||||
def killall(self):
|
def killall(self):
|
||||||
for job in self.workers:
|
for worker in self.workers:
|
||||||
self.kill_queue.put(job)
|
self.kill_queue.put(worker.job)
|
||||||
|
|
||||||
def _kill_job(self, job):
|
def _kill_job(self, job):
|
||||||
if job.start_time is None: return
|
if job.start_time is None:
|
||||||
|
job.kill_on_start = True
|
||||||
|
return
|
||||||
for worker in self.workers:
|
for worker in self.workers:
|
||||||
if job is worker.job:
|
if job is worker.job:
|
||||||
worker.kill()
|
worker.kill()
|
||||||
|
@ -57,7 +57,7 @@ recipe_modules = ['recipe_' + r for r in (
|
|||||||
'monitor', 'republika', 'beta', 'beta_en', 'glasjavnosti',
|
'monitor', 'republika', 'beta', 'beta_en', 'glasjavnosti',
|
||||||
'esquire', 'livemint', 'thedgesingapore', 'darknet', 'rga',
|
'esquire', 'livemint', 'thedgesingapore', 'darknet', 'rga',
|
||||||
'intelligencer', 'theoldfoodie', 'hln_be', 'honvedelem',
|
'intelligencer', 'theoldfoodie', 'hln_be', 'honvedelem',
|
||||||
'the_new_republic',
|
'the_new_republic', 'philly',
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ lemonde.fr
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from datetime import date
|
#from datetime import date
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
|
||||||
@ -20,7 +20,28 @@ class LeMonde(BasicNewsRecipe):
|
|||||||
|
|
||||||
max_articles_per_feed = 30
|
max_articles_per_feed = 30
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
cover_url='http://abonnes.lemonde.fr/titresdumonde/'+date.today().strftime("%y%m%d")+'/1.jpg'
|
remove_javascript = True
|
||||||
|
|
||||||
|
|
||||||
|
#cover_url='http://abonnes.lemonde.fr/titresdumonde/'+date.today().strftime("%y%m%d")+'/1.jpg'
|
||||||
|
|
||||||
|
|
||||||
|
extra_css = '''
|
||||||
|
.dateline{color:#666666;font-family:verdana,sans-serif;font-size:xx-small;}
|
||||||
|
.mainText{font-family:Georgia,serif;color:#222222;}
|
||||||
|
.LM_articleText{font-family:Georgia,serif;}
|
||||||
|
.mainContent{font-family:Georgia,serif;}
|
||||||
|
.mainTitle{font-family:Georgia,serif;}
|
||||||
|
.LM_content{font-family:Georgia,serif;}
|
||||||
|
.content{font-family:Georgia,serif;}
|
||||||
|
.LM_caption{font-family:Georgia,serif;font-size:xx-small;}
|
||||||
|
.LM_imageSource{font-family:Arial,Helvetica,sans-serif;font-size:xx-small;color:#666666;}
|
||||||
|
h1{font-family:Georgia,serif;font-size:medium;color:#000000;}
|
||||||
|
.entry{font-family:Georgia,Times New Roman,Times,serif;}
|
||||||
|
.mainTitle{font-family:Georgia,Times New Roman,Times,serif;}
|
||||||
|
h2{font-family:Georgia,Times New Roman,Times,serif;font-size:large;}
|
||||||
|
small{{font-family:Arial,Helvetica,sans-serif;font-size:xx-small;}
|
||||||
|
'''
|
||||||
|
|
||||||
feeds = [
|
feeds = [
|
||||||
('A la Une', 'http://www.lemonde.fr/rss/une.xml'),
|
('A la Une', 'http://www.lemonde.fr/rss/une.xml'),
|
||||||
@ -40,14 +61,20 @@ class LeMonde(BasicNewsRecipe):
|
|||||||
('Examens', 'http://www.lemonde.fr/rss/sequence/0,2-3404,1-0,0.xml'),
|
('Examens', 'http://www.lemonde.fr/rss/sequence/0,2-3404,1-0,0.xml'),
|
||||||
('Opinions', 'http://www.lemonde.fr/rss/sequence/0,2-3232,1-0,0.xml')
|
('Opinions', 'http://www.lemonde.fr/rss/sequence/0,2-3232,1-0,0.xml')
|
||||||
]
|
]
|
||||||
|
keep_only_tags = [dict(name='div', attrs={'id':["mainTitle","mainContent","LM_content","content"]}),
|
||||||
|
dict(name='div', attrs={'class':["post"]})
|
||||||
|
]
|
||||||
|
|
||||||
remove_tags = [dict(name='img', attrs={'src':'http://medias.lemonde.fr/mmpub/img/lgo/lemondefr_pet.gif'}),
|
remove_tags = [dict(name='img', attrs={'src':'http://medias.lemonde.fr/mmpub/img/lgo/lemondefr_pet.gif'}),
|
||||||
dict(name='div', attrs={'id':'xiti-logo-noscript'}),
|
dict(name='div', attrs={'id':'xiti-logo-noscript'}),
|
||||||
dict(name='br', attrs={}),
|
dict(name='br', attrs={}),
|
||||||
dict(name='iframe', attrs={}),
|
dict(name='iframe', attrs={}),
|
||||||
|
dict(name='table', attrs={'id':["toolBox"]}),
|
||||||
|
dict(name='table', attrs={'class':["bottomToolBox"]}),
|
||||||
|
dict(name='div', attrs={'class':["pageNavigation","fenetreBoxesContainer","breakingNews","LM_toolsBottom","LM_comments","LM_tools","pave_meme_sujet_hidden","boxMemeSujet"]}),
|
||||||
|
dict(name='div', attrs={'id':["miniUne","LM_sideBar"]}),
|
||||||
]
|
]
|
||||||
|
|
||||||
extra_css = '.ar-tit {font-size: x-large;} \n .dt {font-size: x-small;}'
|
|
||||||
|
|
||||||
preprocess_regexps = [ (re.compile(i[0], re.IGNORECASE|re.DOTALL), i[1]) for i in
|
preprocess_regexps = [ (re.compile(i[0], re.IGNORECASE|re.DOTALL), i[1]) for i in
|
||||||
[
|
[
|
||||||
@ -68,8 +95,8 @@ class LeMonde(BasicNewsRecipe):
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
def print_version(self, url):
|
# def print_version(self, url):
|
||||||
return re.sub('http://www\.lemonde\.fr/.*_([0-9]+)_[0-9]+\.html.*','http://www.lemonde.fr/web/imprimer_element/0,40-0,50-\\1,0.html' ,url)
|
# return re.sub('http://www\.lemonde\.fr/.*_([0-9]+)_[0-9]+\.html.*','http://www.lemonde.fr/web/imprimer_element/0,40-0,50-\\1,0.html' ,url)
|
||||||
|
|
||||||
# Used to filter duplicated articles
|
# Used to filter duplicated articles
|
||||||
articles_list = []
|
articles_list = []
|
||||||
@ -94,3 +121,5 @@ class LeMonde(BasicNewsRecipe):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
56
src/calibre/web/feeds/recipes/recipe_philly.py
Normal file
56
src/calibre/web/feeds/recipes/recipe_philly.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
'''
|
||||||
|
philly.com/inquirer/
|
||||||
|
'''
|
||||||
|
import re
|
||||||
|
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||||
|
|
||||||
|
class Philly(BasicNewsRecipe):
|
||||||
|
|
||||||
|
title = 'Philadelphia Inquirer'
|
||||||
|
__author__ = 'RadikalDissent'
|
||||||
|
language = 'en'
|
||||||
|
description = 'Daily news from the Philadelphia Inquirer'
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
oldest_article = 1
|
||||||
|
max_articles_per_feed = 25
|
||||||
|
extra_css = '''
|
||||||
|
.byline {font-size: small; color: grey; font-style:italic; }
|
||||||
|
.lastline {font-size: small; color: grey; font-style:italic;}
|
||||||
|
.contact {font-size: small; color: grey;}
|
||||||
|
.contact p {font-size: small; color: grey;}
|
||||||
|
'''
|
||||||
|
preprocess_regexps = [(re.compile(i[0], re.IGNORECASE | re.DOTALL), i[1]) for i in
|
||||||
|
[
|
||||||
|
(r'<body.*<h1>', lambda match: '<body><h1>'),
|
||||||
|
(r'<font size="2" face="Arial">', lambda match: '<div class="contact"><font class="contact">'),
|
||||||
|
(r'<font face="Arial" size="2">', lambda match: '<div class="contact"><font class="contact">')
|
||||||
|
]
|
||||||
|
]
|
||||||
|
keep_only_tags = [
|
||||||
|
dict(name='h1'),
|
||||||
|
dict(name='p', attrs={'class':['byline','lastline']}),
|
||||||
|
dict(name='div', attrs={'class':'body-content'}),
|
||||||
|
]
|
||||||
|
|
||||||
|
remove_tags = [
|
||||||
|
dict(name='hr'),
|
||||||
|
dict(name='p', attrs={'class':'buzzBadge'}),
|
||||||
|
]
|
||||||
|
def print_version(self, url):
|
||||||
|
return url + '?viewAll=y'
|
||||||
|
|
||||||
|
|
||||||
|
feeds = [
|
||||||
|
('Front Page', 'http://www.philly.com/inquirer_front_page.rss'),
|
||||||
|
('Business', 'http://www.philly.com/inq_business.rss'),
|
||||||
|
('News', 'http://www.philly.com/inquirer/news/index.rss'),
|
||||||
|
('Nation', 'http://www.philly.com/inq_news_world_us.rss'),
|
||||||
|
('Local', 'http://www.philly.com/inquirer_local.rss'),
|
||||||
|
('Health', 'http://www.philly.com/inquirer_health_science.rss'),
|
||||||
|
('Education', 'http://www.philly.com/inquirer_education.rss'),
|
||||||
|
('Editorial and opinion', 'http://www.philly.com/inq_news_editorial.rss'),
|
||||||
|
('Sports', 'http://www.philly.com/inquirer_sports.rss')
|
||||||
|
]
|
Loading…
x
Reference in New Issue
Block a user