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',
|
||||
help=('Build only the named extension. Available: '+
|
||||
', '.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):
|
||||
if opts.no_compile:
|
||||
self.info('--no-compile specified, skipping compilation')
|
||||
return
|
||||
self.obj_dir = os.path.join(os.path.dirname(SRC), 'build', 'objects')
|
||||
if not os.path.exists(self.obj_dir):
|
||||
os.makedirs(self.obj_dir)
|
||||
|
@ -43,7 +43,7 @@ class Develop(Command):
|
||||
sub_commands = ['build', 'resources', 'gui']
|
||||
|
||||
def add_options(self, parser):
|
||||
parser.add_option('--prefix',
|
||||
parser.add_option('--prefix', '--root',
|
||||
help='Binaries will be installed in <prefix>/bin')
|
||||
|
||||
def pre_sub_commands(self, opts):
|
||||
@ -91,7 +91,9 @@ class Develop(Command):
|
||||
pass
|
||||
|
||||
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):
|
||||
self.info('\nDevelopment environment successfully setup')
|
||||
@ -119,6 +121,8 @@ class Develop(Command):
|
||||
path=self.path, resources=self.resources,
|
||||
extensions=self.extensions)
|
||||
path = self.j(self.bindir, name)
|
||||
if not os.path.exists(self.bindir):
|
||||
os.makedirs(self.bindir)
|
||||
self.info('Installing binary:', path)
|
||||
open(path, 'wb').write(script)
|
||||
os.chmod(path, self.MODE)
|
||||
@ -134,10 +138,10 @@ class Install(Develop):
|
||||
The default <prefix> is the prefix of your python installation.
|
||||
''')
|
||||
|
||||
sub_commands = ['build']
|
||||
sub_commands = ['build', 'gui']
|
||||
|
||||
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('--bindir', help='Where to install calibre binaries')
|
||||
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))
|
||||
else:
|
||||
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...')
|
||||
subprocess.check_call(' '.join(['tar', '-czf', self.a(self.DEST), '*']),
|
||||
cwd=tdir, shell=True)
|
||||
|
@ -20,7 +20,7 @@ class VMInstaller(Command):
|
||||
VM_NAME = None
|
||||
FREEZE_COMMAND = None
|
||||
FREEZE_TEMPLATE = 'python setup.py {freeze_command}'
|
||||
SHUTDOWN_CMD = ['sudo', 'shutdown', '-h', 'now']
|
||||
SHUTDOWN_CMD = ['sudo', 'poweroff']
|
||||
IS_64_BIT = False
|
||||
|
||||
BUILD_CMD = 'ssh -t %s bash build-calibre'
|
||||
|
@ -46,13 +46,12 @@ class LinuxFreeze(Command):
|
||||
'/lib/libz.so.1',
|
||||
'/usr/lib/libtiff.so.3',
|
||||
'/lib/libbz2.so.1',
|
||||
'/usr/lib/libpoppler.so.4',
|
||||
'/usr/lib/libpoppler.so.5',
|
||||
'/usr/lib/libpoppler-qt4.so.3',
|
||||
'/usr/lib/libxml2.so.2',
|
||||
'/usr/lib/libopenjpeg.so.2',
|
||||
'/usr/lib/libxslt.so.1',
|
||||
'/usr/lib64/libjpeg.so.7'.replace('64', '64' if is64bit
|
||||
else ''),
|
||||
'/usr/lib/libjpeg.so.7',
|
||||
'/usr/lib/libxslt.so.1',
|
||||
'/usr/lib/libgthread-2.0.so.0',
|
||||
'/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/libexslt.so.0',
|
||||
'/usr/lib/libMagickWand.so',
|
||||
'/usr/lib/libMagickCore.so',
|
||||
'/usr/lib/libMagickCore.so.2',
|
||||
'/usr/lib/libgcrypt.so.11',
|
||||
'/usr/lib/libgpg-error.so.0',
|
||||
'/usr/lib/libphonon.so.4',
|
||||
|
@ -25,3 +25,4 @@ class OSX32(VMInstaller):
|
||||
VM = '/vmware/bin/%s'%VM_NAME
|
||||
FREEZE_COMMAND = 'osx32_freeze'
|
||||
BUILD_PREFIX = VMInstaller.BUILD_PREFIX + ['source ~/.profile']
|
||||
SHUTDOWN_CMD = ['sudo', 'halt']
|
||||
|
@ -26,6 +26,7 @@ class Win32(VMInstaller):
|
||||
VM_NAME = 'xp_build'
|
||||
VM = '/vmware/bin/%s'%VM_NAME
|
||||
FREEZE_COMMAND = 'win32_freeze'
|
||||
SHUTDOWN_CMD = ['shutdown', '-s']
|
||||
|
||||
def download_installer(self):
|
||||
installer = self.installer()
|
||||
|
@ -6,7 +6,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import sys, os, shutil, subprocess, re, time
|
||||
import sys, os, shutil, subprocess, re, time, glob
|
||||
from datetime import datetime
|
||||
|
||||
from setup import Command, __appname__, __version__
|
||||
@ -31,6 +31,11 @@ class Stage2(Command):
|
||||
description = 'Stage 2 of the publish process'
|
||||
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):
|
||||
|
||||
description = 'Stage 3 of the publish process'
|
||||
@ -88,7 +93,7 @@ if os.environ.get('CALIBRE_BUILDBOT', None) == '1':
|
||||
else:
|
||||
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
|
||||
|
||||
@ -150,7 +155,9 @@ else:
|
||||
bzr_path = os.path.expanduser('~/work/calibre')
|
||||
b = branch.Branch.open(bzr_path)
|
||||
lf = UploadRss.ChangelogFormatter()
|
||||
self.info('\tGenerating bzr log...')
|
||||
log.show_log(b, lf)
|
||||
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
|
||||
settings.Close()
|
||||
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:
|
||||
print('Using proxies: %s' % proxies)
|
||||
prints('Using proxies: %s' % proxies)
|
||||
return proxies
|
||||
|
||||
|
||||
|
@ -2,7 +2,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = 'calibre'
|
||||
__version__ = '0.6.11'
|
||||
__version__ = '0.6.12'
|
||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
|
||||
import re
|
||||
|
@ -367,6 +367,7 @@ from calibre.devices.prs500.driver import PRS500
|
||||
from calibre.devices.prs505.driver import PRS505
|
||||
from calibre.devices.prs700.driver import PRS700
|
||||
from calibre.devices.android.driver import ANDROID
|
||||
from calibre.devices.eslick.driver import ESLICK
|
||||
|
||||
plugins = [HTML2ZIP]
|
||||
plugins += [
|
||||
@ -416,7 +417,8 @@ plugins += [
|
||||
PRS700,
|
||||
ANDROID,
|
||||
CYBOOK_OPUS,
|
||||
COOL_ER
|
||||
COOL_ER,
|
||||
ESLICK
|
||||
]
|
||||
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \
|
||||
x.__name__.endswith('MetadataReader')]
|
||||
|
@ -155,6 +155,9 @@ def debug_device_driver():
|
||||
print
|
||||
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):
|
||||
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 = []
|
||||
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'))
|
||||
if length:
|
||||
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 '
|
||||
'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)
|
||||
if d:
|
||||
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)
|
||||
else:
|
||||
d = None
|
||||
|
@ -309,7 +309,8 @@ class MobiReader(object):
|
||||
try:
|
||||
root = html.fromstring(self.processed_html)
|
||||
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:
|
||||
self.log.warning('MOBI markup appears to contain random bytes. Stripping.')
|
||||
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.html_preprocessor(data)
|
||||
|
||||
|
||||
# Remove DOCTYPE declaration as it messes up parsing
|
||||
# Inparticular it causes tostring to insert xmlns
|
||||
# declarations, which messes up the coercing logic
|
||||
|
@ -8,6 +8,7 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
import textwrap
|
||||
from xml.sax.saxutils import escape
|
||||
from itertools import repeat
|
||||
|
||||
from lxml import etree
|
||||
|
||||
@ -31,6 +32,7 @@ class Jacket(object):
|
||||
<h1 class="calibre_rescale_180">%(title)s</h1>
|
||||
<h2 class="calibre_rescale_140">%(jacket)s</h2>
|
||||
<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>
|
||||
<div style="margin-top:2em" class="calibre_rescale_100">
|
||||
@ -54,6 +56,23 @@ class Jacket(object):
|
||||
img.getparent().remove(img)
|
||||
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):
|
||||
self.log('Inserting metadata into book...')
|
||||
comments = mi.comments
|
||||
@ -87,7 +106,7 @@ class Jacket(object):
|
||||
html = self.JACKET_TEMPLATE%dict(xmlns=XPNSMAP['h'],
|
||||
title=escape(title), comments=escape(comments),
|
||||
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')
|
||||
root = etree.fromstring(html)
|
||||
item = self.oeb.manifest.add(id, href, guess_type(href)[0], data=root)
|
||||
|
@ -25,7 +25,7 @@ class PDBInput(InputFormatPlugin):
|
||||
OptionRecommendation(name='print_formatted_paras', recommended_value=False,
|
||||
help=_('Normally calibre treats blank lines as paragraph markers. '
|
||||
'With this option it will assume that every line starting with '
|
||||
'an indent (either a tab or 2+ spaces) represents a paragraph.'
|
||||
'an indent (either a tab or 2+ spaces) represents a paragraph. '
|
||||
'Paragraphs end when the next line that starts with an indent '
|
||||
'is reached.')),
|
||||
])
|
||||
|
@ -25,7 +25,7 @@ class TXTInput(InputFormatPlugin):
|
||||
OptionRecommendation(name='print_formatted_paras', recommended_value=False,
|
||||
help=_('Normally calibre treats blank lines as paragraph markers. '
|
||||
'With this option it will assume that every line starting with '
|
||||
'an indent (either a tab or 2+ spaces) represents a paragraph.'
|
||||
'an indent (either a tab or 2+ spaces) represents a paragraph. '
|
||||
'Paragraphs end when the next line that starts with an indent '
|
||||
'is reached.')),
|
||||
OptionRecommendation(name='markdown', recommended_value=False,
|
||||
|
@ -3,7 +3,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
""" The GUI """
|
||||
import os
|
||||
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, \
|
||||
QIcon, QTableView, QApplication, QDialog, QPushButton
|
||||
|
||||
@ -23,8 +23,6 @@ ALL_COLUMNS = ['title', 'authors', 'size', 'timestamp', 'rating', 'publisher',
|
||||
|
||||
def _config():
|
||||
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,
|
||||
help=_('Send file to storage card instead of main memory by default'))
|
||||
c.add_opt('confirm_delete', default=False,
|
||||
@ -83,6 +81,8 @@ def _config():
|
||||
help='Search history for the LRF viewer')
|
||||
c.add_opt('scheduler_search_history', default=[],
|
||||
help='Search history for the recipe scheduler')
|
||||
c.add_opt('worker_limit', default=6,
|
||||
help=_('Maximum number of waiting worker processes'))
|
||||
|
||||
return ConfigProxy(c)
|
||||
|
||||
@ -377,15 +377,8 @@ def file_icon_provider():
|
||||
global _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):
|
||||
def __init__(self, title='Choose Files',
|
||||
def __init__(self, title=_('Choose Files'),
|
||||
filters=[],
|
||||
add_all_files_filter=True,
|
||||
parent=None,
|
||||
@ -394,7 +387,6 @@ class FileDialog(QObject):
|
||||
mode = QFileDialog.ExistingFiles,
|
||||
):
|
||||
QObject.__init__(self)
|
||||
initialize_file_icon_provider()
|
||||
ftext = ''
|
||||
if filters:
|
||||
for filter in filters:
|
||||
@ -410,42 +402,27 @@ class FileDialog(QObject):
|
||||
self.selected_files = None
|
||||
self.fd = None
|
||||
|
||||
if islinux:
|
||||
self.fd = QFileDialog(parent)
|
||||
self.fd.setFileMode(mode)
|
||||
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('~'))
|
||||
initial_dir = dynamic.get(self.dialog_name, os.path.expanduser('~'))
|
||||
if not isinstance(initial_dir, basestring):
|
||||
initial_dir = os.path.expanduser('~')
|
||||
self.selected_files = []
|
||||
if mode == QFileDialog.AnyFile:
|
||||
f = qstring_to_unicode(
|
||||
QFileDialog.getSaveFileName(parent, title, dir, ftext, ""))
|
||||
if os.path.exists(f):
|
||||
f = unicode(QFileDialog.getSaveFileName(parent, title, initial_dir, ftext, ""))
|
||||
if f and os.path.exists(f):
|
||||
self.selected_files.append(f)
|
||||
elif mode == QFileDialog.ExistingFile:
|
||||
f = qstring_to_unicode(
|
||||
QFileDialog.getOpenFileName(parent, title, dir, ftext, ""))
|
||||
if os.path.exists(f):
|
||||
f = unicode(QFileDialog.getOpenFileName(parent, title, initial_dir, ftext, ""))
|
||||
if f and os.path.exists(f):
|
||||
self.selected_files.append(f)
|
||||
elif mode == QFileDialog.ExistingFiles:
|
||||
fs = QFileDialog.getOpenFileNames(parent, title, dir, ftext, "")
|
||||
fs = QFileDialog.getOpenFileNames(parent, title, initial_dir, ftext, "")
|
||||
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)
|
||||
else:
|
||||
opts = QFileDialog.ShowDirsOnly if mode == QFileDialog.DirectoryOnly else QFileDialog.Option()
|
||||
f = qstring_to_unicode(
|
||||
QFileDialog.getExistingDirectory(parent, title, dir, opts))
|
||||
f = unicode(QFileDialog.getExistingDirectory(parent, title, initial_dir, opts))
|
||||
if os.path.exists(f):
|
||||
self.selected_files.append(f)
|
||||
if self.selected_files:
|
||||
@ -457,16 +434,10 @@ class FileDialog(QObject):
|
||||
self.accepted = bool(self.selected_files)
|
||||
|
||||
def get_files(self):
|
||||
if islinux and self.fd.result() != self.fd.Accepted:
|
||||
return tuple()
|
||||
if self.selected_files is None:
|
||||
return tuple(os.path.abspath(qstring_to_unicode(i)) for i in self.fd.selectedFiles())
|
||||
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):
|
||||
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.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):
|
||||
|
||||
TITLE = _('Metadata')
|
||||
@ -181,12 +199,7 @@ class MetadataWidget(Widget, Ui_Form):
|
||||
self.cover_file = self.opf_file = None
|
||||
if self.db is not None:
|
||||
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.application_id = uuid.uuid4()
|
||||
opf = OPFCreator(os.getcwdu(), self.mi)
|
||||
self.opf_file = PersistentTemporaryFile('.opf')
|
||||
opf.render(self.opf_file)
|
||||
self.opf_file.close()
|
||||
self.mi, self.opf_file = create_opf_file(self.db, self.book_id)
|
||||
if self.cover_changed and self.cover_data is not None:
|
||||
self.db.set_cover(self.book_id, self.cover_data)
|
||||
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'
|
||||
|
||||
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):
|
||||
|
||||
@ -17,3 +17,8 @@ class PluginWidget(Widget, Ui_Form):
|
||||
['no_images', 'unwrap_factor'])
|
||||
self.db, self.book_id = 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 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):
|
||||
'''
|
||||
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):
|
||||
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,
|
||||
preferred_output_format)
|
||||
self.db, self.book_id = db, book_id
|
||||
@ -194,22 +218,10 @@ class Config(ResizableDialog, Ui_Dialog):
|
||||
preferred_output_format):
|
||||
if preferred_output_format:
|
||||
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.remove('oeb')
|
||||
preferred_input_format = preferred_input_format if \
|
||||
preferred_input_format in input_formats else \
|
||||
sort_formats_by_preference(input_formats,
|
||||
prefs['input_format_order'])[0]
|
||||
input_format, input_formats = get_input_format_for_book(db, book_id,
|
||||
preferred_input_format)
|
||||
preferred_output_format = preferred_output_format if \
|
||||
preferred_output_format in output_formats else \
|
||||
sort_formats_by_preference(output_formats,
|
||||
@ -218,7 +230,7 @@ class Config(ResizableDialog, Ui_Dialog):
|
||||
input_formats])))
|
||||
self.output_formats.addItems(list(map(QString, [x.upper() for x in
|
||||
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))
|
||||
|
||||
def show_pane(self, index):
|
||||
|
@ -10,7 +10,7 @@ from PyQt4.Qt import QDialog, QListWidgetItem, QIcon, \
|
||||
QDialogButtonBox, QTabWidget, QBrush, QLineEdit, \
|
||||
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 import qstring_to_unicode, choose_dir, error_dialog, config, \
|
||||
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_down, SIGNAL('clicked()'), self.down_input)
|
||||
|
||||
dirs = config['frequently_used_directories']
|
||||
rn = config['use_roman_numerals_for_series_number']
|
||||
self.timeout.setValue(prefs['network_timeout'])
|
||||
self.roman_numerals.setChecked(rn)
|
||||
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']
|
||||
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.username.setText(opts.username)
|
||||
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.systray_icon.setChecked(config['systray_icon'])
|
||||
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.delete_news.setEnabled)
|
||||
self.setup_conversion_options()
|
||||
self.opt_worker_limit.setValue(config['worker_limit'])
|
||||
|
||||
def create_symlinks(self):
|
||||
from calibre.utils.osx_symlinks import create_symlinks
|
||||
@ -674,15 +670,6 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
||||
if 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):
|
||||
mcs = unicode(self.max_cover_size.text()).strip()
|
||||
@ -696,6 +683,12 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
||||
return
|
||||
if not self.add_save.save_settings():
|
||||
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['new_version_notification'] = bool(self.new_version_notification.isChecked())
|
||||
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('port', self.port.value())
|
||||
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['upload_news_to_device'] = self.sync_news.isChecked()
|
||||
config['search_as_you_type'] = self.search_as_you_type.isChecked()
|
||||
@ -742,10 +736,6 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
||||
d.exec_()
|
||||
else:
|
||||
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)
|
||||
|
||||
class VacThread(QThread):
|
||||
|
@ -281,103 +281,6 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</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>
|
||||
</widget>
|
||||
<widget class="QWidget" name="page">
|
||||
@ -682,30 +585,38 @@
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="page_2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_9">
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>&Maximum number of waiting worker processes (needs restart):</string>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>154</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
<property name="buddy">
|
||||
<cstring>opt_worker_limit</cstring>
|
||||
</property>
|
||||
</spacer>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_12">
|
||||
<item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="opt_worker_limit">
|
||||
<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">
|
||||
<property name="text">
|
||||
<string>&Check database integrity</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QPushButton" name="button_osx_symlinks">
|
||||
<property name="text">
|
||||
<string>&Install command line tools</string>
|
||||
@ -713,21 +624,6 @@
|
||||
</widget>
|
||||
</item>
|
||||
</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 class="QWidget" name="page_4">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
@ -797,13 +693,6 @@
|
||||
</widget>
|
||||
</item>
|
||||
<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">
|
||||
<property name="toolTip">
|
||||
<string>The maximum size (widthxheight) for displayed covers. Larger covers are resized. </string>
|
||||
@ -813,7 +702,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Max. &cover size:</string>
|
||||
@ -823,6 +712,33 @@
|
||||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -14,7 +14,7 @@
|
||||
<string>Active Jobs</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../../../resources/images.qrc">
|
||||
<iconset resource="../../../work/calibre/resources/images.qrc">
|
||||
<normaloff>:/images/jobs.svg</normaloff>:/images/jobs.svg</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout">
|
||||
@ -57,6 +57,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="stop_all_jobs_button">
|
||||
<property name="text">
|
||||
<string>Stop &all jobs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
@ -67,7 +74,7 @@
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../../../resources/images.qrc"/>
|
||||
<include location="../../../work/calibre/resources/images.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
@ -252,9 +252,19 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
||||
if ext not in extensions:
|
||||
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)
|
||||
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.db = db
|
||||
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.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.dialogs.jobs_ui import Ui_JobsDialog
|
||||
from calibre import __appname__
|
||||
@ -31,7 +31,7 @@ class JobManager(QAbstractTableModel):
|
||||
|
||||
self.jobs = []
|
||||
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.timer = QTimer(self)
|
||||
@ -193,6 +193,12 @@ class JobManager(QAbstractTableModel):
|
||||
_('Job has already run')).exec_()
|
||||
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):
|
||||
self.server.killall()
|
||||
|
||||
@ -230,6 +236,8 @@ class JobsDialog(QDialog, Ui_JobsDialog):
|
||||
self.kill_job)
|
||||
self.connect(self.details_button, SIGNAL('clicked()'),
|
||||
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.jobs_view.model().kill_job)
|
||||
self.pb_delegate = ProgressBarDelegate(self)
|
||||
@ -247,7 +255,8 @@ class JobsDialog(QDialog, Ui_JobsDialog):
|
||||
self.jobs_view.show_details(index)
|
||||
return
|
||||
|
||||
|
||||
def kill_all_jobs(self):
|
||||
self.model.kill_all_jobs()
|
||||
|
||||
def closeEvent(self, e):
|
||||
self.jobs_view.write_settings()
|
||||
|
@ -20,11 +20,12 @@ from calibre.constants import __version__, __appname__, \
|
||||
from calibre.utils.filenames import ascii_filename
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre.utils.config import prefs, dynamic
|
||||
from calibre.utils.ipc import ADDRESS, RC
|
||||
from calibre.utils.ipc.server import Server
|
||||
from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \
|
||||
initialize_file_icon_provider, question_dialog,\
|
||||
pixmap_to_data, choose_dir, ORG_NAME, \
|
||||
set_sidebar_directories, Dispatcher, \
|
||||
Dispatcher, \
|
||||
Application, available_height, \
|
||||
max_available_height, config, info_dialog, \
|
||||
available_width, GetMetadata
|
||||
@ -50,9 +51,6 @@ from calibre.ebooks import BOOK_EXTENSIONS
|
||||
from calibre.library.database2 import LibraryDatabase2, CoverCache
|
||||
from calibre.gui2.dialogs.confirm_delete import confirm
|
||||
|
||||
ADDRESS = r'\\.\pipe\CalibreGUI' if iswindows else \
|
||||
os.path.expanduser('~/.calibre-gui.socket')
|
||||
|
||||
class SaveMenu(QMenu):
|
||||
|
||||
def __init__(self, parent):
|
||||
@ -208,6 +206,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.latest_version = ' '
|
||||
self.vanity.setText(self.vanity_template%dict(version=' ', device=' '))
|
||||
self.device_info = ' '
|
||||
if not opts.no_update_check:
|
||||
self.update_checker = CheckForUpdates()
|
||||
QObject.connect(self.update_checker,
|
||||
SIGNAL('update_found(PyQt_PyObject)'), self.update_found)
|
||||
@ -531,7 +530,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self._sync_menu.trigger_default)
|
||||
|
||||
def add_spare_server(self, *args):
|
||||
self.spare_servers.append(Server())
|
||||
self.spare_servers.append(Server(limit=int(config['worker_limit']/2.0)))
|
||||
|
||||
@property
|
||||
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())
|
||||
d = MetadataSingleDialog(self, row.row(),
|
||||
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.metadata_view_format)
|
||||
d.exec_()
|
||||
if d.cancel_all:
|
||||
break
|
||||
if rows:
|
||||
current = self.library_view.currentIndex()
|
||||
self.library_view.model().current_changed(current, previous)
|
||||
@ -1484,8 +1486,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.save_menu.actions()[2].setText(
|
||||
_('Save only %s format to disk')%
|
||||
prefs['output_format'].upper())
|
||||
if hasattr(d, 'directories'):
|
||||
set_sidebar_directories(d.directories)
|
||||
self.library_view.model().read_config()
|
||||
self.create_device_menu()
|
||||
|
||||
@ -1546,12 +1546,16 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.view_menu.actions()[1].setEnabled(True)
|
||||
self.action_open_containing_folder.setEnabled(True)
|
||||
self.action_sync.setEnabled(True)
|
||||
self.status_bar.tag_view_button.setEnabled(True)
|
||||
self.status_bar.cover_flow_button.setEnabled(True)
|
||||
else:
|
||||
self.action_edit.setEnabled(False)
|
||||
self.action_convert.setEnabled(False)
|
||||
self.view_menu.actions()[1].setEnabled(False)
|
||||
self.action_open_containing_folder.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):
|
||||
@ -1644,7 +1648,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
geometry = config['main_window_geometry']
|
||||
if geometry is not None:
|
||||
self.restoreGeometry(geometry)
|
||||
set_sidebar_directories(None)
|
||||
self.tool_bar.setIconSize(config['toolbar_icon_size'])
|
||||
self.tool_bar.setToolButtonStyle(
|
||||
Qt.ToolButtonTextUnderIcon if \
|
||||
@ -1814,6 +1817,8 @@ path_to_ebook to the database.
|
||||
help=_('Start minimized to system tray.'))
|
||||
parser.add_option('-v', '--verbose', default=0, action='count',
|
||||
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
|
||||
|
||||
def init_qt(args):
|
||||
@ -1882,14 +1887,6 @@ def cant_start(msg=_('If you are sure it is not running')+', ',
|
||||
d.exec_()
|
||||
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):
|
||||
t = RC()
|
||||
t.start()
|
||||
|
@ -36,6 +36,7 @@ class SearchBox2(QComboBox):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QComboBox.__init__(self, parent)
|
||||
self.normal_background = 'rgb(255, 255, 255, 0%)'
|
||||
self.line_edit = SearchLineEdit(self)
|
||||
self.setLineEdit(self.line_edit)
|
||||
self.connect(self.line_edit, SIGNAL('key_pressed(PyQt_PyObject)'),
|
||||
@ -62,14 +63,18 @@ class SearchBox2(QComboBox):
|
||||
|
||||
def normalize_state(self):
|
||||
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
|
||||
|
||||
def clear_to_help(self):
|
||||
self.setEditText(self.help_text)
|
||||
self.line_edit.home(False)
|
||||
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()'))
|
||||
|
||||
def text(self):
|
||||
@ -84,7 +89,7 @@ class SearchBox2(QComboBox):
|
||||
return self.clear_to_help()
|
||||
col = 'rgba(0,255,0,20%)' if ok else 'rgb(255,0,0,20%)'
|
||||
if not self.colorize:
|
||||
col = 'white'
|
||||
col = self.normal_background
|
||||
self.line_edit.setStyleSheet('QLineEdit { color: black; background-color: %s; }' % col)
|
||||
|
||||
def key_pressed(self, event):
|
||||
|
@ -14,8 +14,10 @@ from PyQt4.Qt import QDialog
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre.gui2 import warning_dialog, question_dialog
|
||||
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.metadata import create_opf_file, create_cover_file
|
||||
from calibre.customize.conversion import OptionRecommendation
|
||||
from calibre.utils.config import prefs
|
||||
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()
|
||||
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)
|
||||
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)
|
||||
for i, book_id in enumerate(book_ids):
|
||||
temp_files = []
|
||||
input_format = get_input_format_for_book(db, book_id, None)[0]
|
||||
|
||||
try:
|
||||
d = SingleConfig(parent, db, book_id, None, output_format)
|
||||
d.accept()
|
||||
|
||||
mi = db.get_metadata(book_id, True)
|
||||
in_file = db.format_abspath(book_id, d.input_format, True)
|
||||
mi, opf_file = create_opf_file(db, book_id)
|
||||
in_file = db.format_abspath(book_id, input_format, True)
|
||||
|
||||
out_file = PersistentTemporaryFile('.' + output_format)
|
||||
out_file.write(output_format)
|
||||
@ -122,7 +126,7 @@ def convert_bulk_ebook(parent, db, book_ids, out_format=None):
|
||||
temp_files = []
|
||||
|
||||
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)
|
||||
for key in default_recs:
|
||||
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)
|
||||
lrecs = list(combined_recs.to_recommendations())
|
||||
|
||||
if d.opf_file is not None:
|
||||
lrecs.append(('read_metadata_from_opf', d.opf_file.name,
|
||||
cover_file = create_cover_file(db, book_id)
|
||||
|
||||
if opf_file is not None:
|
||||
lrecs.append(('read_metadata_from_opf', opf_file.name,
|
||||
OptionRecommendation.HIGH))
|
||||
temp_files.append(d.opf_file)
|
||||
if d.cover_file is not None:
|
||||
lrecs.append(('cover', d.cover_file.name,
|
||||
temp_files.append(opf_file)
|
||||
if cover_file is not None:
|
||||
lrecs.append(('cover', cover_file.name,
|
||||
OptionRecommendation.HIGH))
|
||||
temp_files.append(d.cover_file)
|
||||
temp_files.append(cover_file)
|
||||
|
||||
for x in list(lrecs):
|
||||
if x[0] == 'debug_pipeline':
|
||||
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]
|
||||
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
|
||||
except NoSupportedInputFormats:
|
||||
@ -168,6 +178,7 @@ def convert_bulk_ebook(parent, db, book_ids, out_format=None):
|
||||
'source format was found.') % (len(res), total),
|
||||
msg).exec_()
|
||||
|
||||
jobs.reverse()
|
||||
return jobs, changed, bad
|
||||
|
||||
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.')
|
||||
c.add_opt('max_cover', ['--max-cover'], default='600x800',
|
||||
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
|
||||
|
@ -13,11 +13,6 @@ from urllib import quote
|
||||
|
||||
from calibre import terminal_controller, preferred_encoding, prints
|
||||
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.library.database2 import LibraryDatabase2
|
||||
from calibre.ebooks.metadata.opf2 import OPFCreator, OPF
|
||||
@ -102,6 +97,20 @@ STANZA_TEMPLATE='''\
|
||||
</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):
|
||||
parser = OptionParser(usage)
|
||||
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\t ', path
|
||||
|
||||
if send_message is not None:
|
||||
send_message('refreshdb:', 'calibre GUI')
|
||||
send_message()
|
||||
finally:
|
||||
sys.stdout = orig
|
||||
|
||||
@ -356,8 +364,7 @@ def do_remove(db, ids):
|
||||
for y in x:
|
||||
db.delete_book(y)
|
||||
|
||||
if send_message is not None:
|
||||
send_message('refreshdb:', 'calibre GUI')
|
||||
send_message()
|
||||
|
||||
def remove_option_parser():
|
||||
return get_parser(_(
|
||||
@ -386,7 +393,7 @@ def command_remove(args, dbpath):
|
||||
else:
|
||||
ids.append(int(y[0]))
|
||||
|
||||
do_remove(get_db(dbpath, opts), ids)
|
||||
do_remove(get_db(dbpath, opts), set(ids))
|
||||
|
||||
return 0
|
||||
|
||||
@ -484,8 +491,7 @@ def do_set_metadata(db, id, stream):
|
||||
mi = OPF(stream)
|
||||
db.set_metadata(id, mi)
|
||||
do_show_metadata(db, id, False)
|
||||
if send_message is not None:
|
||||
send_message('refreshdb:', 'calibre GUI')
|
||||
send_message()
|
||||
|
||||
def set_metadata_option_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 \
|
||||
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):
|
||||
return [ (i[0], i[1]) for i in \
|
||||
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):
|
||||
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):
|
||||
data = self.conn.get('SELECT data FROM conversion_options WHERE book=? AND format=?', (id, format.upper()), all=False)
|
||||
if data:
|
||||
|
@ -921,9 +921,12 @@ class LibraryDatabase2(LibraryDatabase):
|
||||
'''
|
||||
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))
|
||||
except:
|
||||
path = None
|
||||
self.data.remove(id)
|
||||
if os.path.exists(path):
|
||||
if path and os.path.exists(path):
|
||||
try:
|
||||
winshell.delete_file(path, no_confirm=True, silent=True)
|
||||
except:
|
||||
|
@ -101,30 +101,13 @@ class LibraryServer(object):
|
||||
</entry>
|
||||
'''))
|
||||
|
||||
STANZA_AUTHOR_ENTRY=MarkupTemplate(textwrap.dedent('''\
|
||||
STANZA_SUBCATALOG_ENTRY=MarkupTemplate(textwrap.dedent('''\
|
||||
<entry xmlns:py="http://genshi.edgewall.org/">
|
||||
<title>${authors}</title>
|
||||
<id>urn:calibre:${record[FM['id']]}</id>
|
||||
<updated>${timestamp}</updated>
|
||||
<link type="application/atom+xml" href="/stanza/?authorid=${record[FM['id']]}" />
|
||||
</entry>
|
||||
'''))
|
||||
|
||||
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']]}" />
|
||||
<title>${title}</title>
|
||||
<id>urn:calibre:${id}</id>
|
||||
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
||||
<link type="application/atom+xml" href="/stanza/?${what}id=${id}" />
|
||||
<content type="text">${count} books</content>
|
||||
</entry>
|
||||
'''))
|
||||
|
||||
@ -135,6 +118,7 @@ class LibraryServer(object):
|
||||
<id>$id</id>
|
||||
<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}"/>
|
||||
${Markup(next_link)}
|
||||
<author>
|
||||
<name>calibre</name>
|
||||
<uri>http://calibre.kovidgoyal.net</uri>
|
||||
@ -167,30 +151,35 @@ class LibraryServer(object):
|
||||
<id>urn:uuid:fc000fa0-8c23-11de-a31d-0002a5d5c51b</id>
|
||||
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
||||
<link type="application/atom+xml" href="/stanza/?sortby=byauthor" />
|
||||
<content type="text">Books sorted by Author</content>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>By Title</title>
|
||||
<id>urn:uuid:1df4fe40-8c24-11de-b4c6-0002a5d5c51b</id>
|
||||
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
||||
<link type="application/atom+xml" href="/stanza/?sortby=bytitle" />
|
||||
<content type="text">Books sorted by Title</content>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>By Newest</title>
|
||||
<id>urn:uuid:3c6d4940-8c24-11de-a4d7-0002a5d5c51b</id>
|
||||
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
||||
<link type="application/atom+xml" href="/stanza/?sortby=bynewest" />
|
||||
<content type="text">Books sorted by Date</content>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>By Tag</title>
|
||||
<id>urn:uuid:824921e8-db8a-4e61-7d38-f1ce41502853</id>
|
||||
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
||||
<link type="application/atom+xml" href="/stanza/?sortby=bytag" />
|
||||
<content type="text">Books sorted by Tags</content>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>By Series</title>
|
||||
<id>urn:uuid:512a5e50-a88f-f6b8-82aa-8f129c719f61</id>
|
||||
<updated>${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}</updated>
|
||||
<link type="application/atom+xml" href="/stanza/?sortby=byseries" />
|
||||
<content type="text">Books sorted by Series</content>
|
||||
</entry>
|
||||
</feed>
|
||||
'''))
|
||||
@ -204,6 +193,7 @@ class LibraryServer(object):
|
||||
self.opts = opts
|
||||
self.max_cover_width, self.max_cover_height = \
|
||||
map(int, self.opts.max_cover.split('x'))
|
||||
self.max_stanza_items = opts.max_opds_items
|
||||
path = P('content_server')
|
||||
self.build_time = datetime.fromtimestamp(os.stat(path).st_mtime)
|
||||
self.default_cover = open(P('content_server/default_cover.jpg'), 'rb').read()
|
||||
@ -281,6 +271,7 @@ class LibraryServer(object):
|
||||
if cover is None:
|
||||
cover = self.default_cover
|
||||
cherrypy.response.headers['Content-Type'] = 'image/jpeg'
|
||||
cherrypy.response.timeout = 3600
|
||||
path = getattr(cover, 'name', False)
|
||||
updated = datetime.utcfromtimestamp(os.stat(path).st_mtime) if path and \
|
||||
os.access(path, os.R_OK) else self.build_time
|
||||
@ -326,6 +317,7 @@ class LibraryServer(object):
|
||||
if mt is None:
|
||||
mt = 'application/octet-stream'
|
||||
cherrypy.response.headers['Content-Type'] = mt
|
||||
cherrypy.response.timeout = 3600
|
||||
path = getattr(fmt, 'name', None)
|
||||
if path and os.path.exists(path):
|
||||
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'}
|
||||
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
|
||||
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.'
|
||||
books = []
|
||||
updated = self.db.last_modified()
|
||||
offset = int(offset)
|
||||
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
|
||||
cherrypy.response.headers['Content-Type'] = 'text/xml'
|
||||
# Main feed
|
||||
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,
|
||||
updated=updated, id='urn:calibre:main').render('xml')
|
||||
return self.stanza_main(updated)
|
||||
if sortby in ('byseries', 'byauthor', 'bytag'):
|
||||
return self.stanza_sortby_subcategory(updated, sortby, offset)
|
||||
|
||||
# Get matching ids
|
||||
if authorid:
|
||||
authorid=int(authorid)
|
||||
au = self.db.authors(authorid, index_is_id=True)
|
||||
ids = self.db.data.get_matches('authors', au)
|
||||
au = self.db.author_name(authorid)
|
||||
ids = self.get_matches('authors', au)
|
||||
elif tagid:
|
||||
tagid=int(tagid)
|
||||
ta = self.db.tags(tagid, index_is_id=True)
|
||||
ids = self.db.data.get_matches('tags', ta)
|
||||
ta = self.db.tag_name(tagid)
|
||||
ids = self.get_matches('tags', ta)
|
||||
elif seriesid:
|
||||
seriesid=int(seriesid)
|
||||
se = self.db.series(seriesid, index_is_id=True)
|
||||
ids = self.db.data.get_matches('series', se)
|
||||
se = self.db.series_name(seriesid)
|
||||
ids = self.get_matches('series', se)
|
||||
else:
|
||||
ids = self.db.data.parse(search) if search and search.strip() else self.db.data.universal_set()
|
||||
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']]))
|
||||
elif sortby == "bytag":
|
||||
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:
|
||||
|
||||
# Sort the record list
|
||||
if sortby == "bytitle" or authorid or tagid:
|
||||
record_list.sort(lambda x, y: cmp(title_sort(x[FIELD_MAP['title']]),
|
||||
title_sort(y[FIELD_MAP['title']])))
|
||||
elif seriesid:
|
||||
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)
|
||||
|
||||
|
||||
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=[]
|
||||
tag_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 = r.upper() if r else ''
|
||||
if 'EPUB' in r or 'PDB' in r:
|
||||
|
||||
z = record[FIELD_MAP['authors']]
|
||||
if not z:
|
||||
z = _('Unknown')
|
||||
authors = ' & '.join([i.replace('|', ',') for i in
|
||||
z.split(',')])
|
||||
|
||||
# Setup extra description
|
||||
extra = []
|
||||
rating = record[FIELD_MAP['rating']]
|
||||
if rating > 0:
|
||||
@ -433,57 +492,29 @@ class LibraryServer(object):
|
||||
extra.append('SERIES: %s [%s]<br />'%\
|
||||
(prepare_string_for_xml(series),
|
||||
fmt_sidx(float(record[FIELD_MAP['series_index']]))))
|
||||
|
||||
fmt = 'epub' if 'EPUB' in r else 'pdb'
|
||||
mimetype = guess_type('dummy.'+fmt)[0]
|
||||
if sortby == "byauthor":
|
||||
if authors and authors not in author_list:
|
||||
author_list.append(authors)
|
||||
books.append(self.STANZA_AUTHOR_ENTRY.generate(
|
||||
|
||||
# Create the sub-catalog, which is either a list of
|
||||
# authors/tags/series or a list of books
|
||||
data = dict(
|
||||
record=record,
|
||||
updated=updated,
|
||||
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,
|
||||
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,
|
||||
record=record, FM=FIELD_MAP,
|
||||
port=self.opts.port,
|
||||
extra=''.join(extra),
|
||||
FM=FIELD_MAP,
|
||||
extra='\n'.join(extra),
|
||||
mimetype=mimetype,
|
||||
fmt=fmt,
|
||||
timestamp=strftime('%Y-%m-%dT%H:%M:%S+00:00', record[5]),
|
||||
).render('xml').decode('utf8'))
|
||||
else:
|
||||
books.append(self.STANZA_ENTRY.generate(
|
||||
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'))
|
||||
timestamp=strftime('%Y-%m-%dT%H:%M:%S+00:00', record[5])
|
||||
)
|
||||
books.append(self.STANZA_ENTRY.generate(**data)\
|
||||
.render('xml').decode('utf8'))
|
||||
|
||||
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
|
||||
@ -537,7 +568,9 @@ class LibraryServer(object):
|
||||
cherrypy.request.headers.get('Want-OPDS-Catalog', 919) != 919 or \
|
||||
ua.startswith('Stanza')
|
||||
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
|
||||
|
@ -22,6 +22,22 @@ _run_once = False
|
||||
if not _run_once:
|
||||
_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
|
||||
import calibre.utils.resources as resources
|
||||
@ -89,18 +105,4 @@ if not _run_once:
|
||||
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__,
|
||||
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>
|
||||
<blockquote>
|
||||
<p>
|
||||
|
@ -108,8 +108,7 @@ sudo python -c "import urllib2; exec urllib2.urlopen('http://status.calibre-eboo
|
||||
<pre class="wiki">
|
||||
wget -O- http://calibre.kovidgoyal.net/downloads/${app}-${version}.tar.gz | tar xvz
|
||||
cd calibre*
|
||||
python setup.py build_ext && python setup.py build && sudo python setup.py install
|
||||
sudo calibre_postinstall
|
||||
sudo python setup.py install
|
||||
</pre>
|
||||
Note that if your distribution does not have a
|
||||
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'):
|
||||
config_dir = os.path.abspath(os.environ['CALIBRE_CONFIG_DIRECTORY'])
|
||||
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)
|
||||
if not os.access(config_dir, os.W_OK|os.X_OK):
|
||||
config_dir = os.path.expanduser('~')
|
||||
|
@ -6,5 +6,30 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__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.killed = False
|
||||
self.failed = False
|
||||
self.kill_on_start = False
|
||||
self.start_time = None
|
||||
self.result = None
|
||||
self.duration = None
|
||||
|
@ -6,7 +6,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, cPickle, time, tempfile
|
||||
import sys, os, cPickle, time, tempfile
|
||||
from math import ceil
|
||||
from threading import Thread, RLock
|
||||
from Queue import Queue, Empty
|
||||
@ -83,14 +83,16 @@ class CriticalError(Exception):
|
||||
|
||||
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)
|
||||
self.daemon = True
|
||||
global _counter
|
||||
self.id = _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.auth_key = os.urandom(32)
|
||||
self.address = arbitrary_address('AF_PIPE' if iswindows else 'AF_UNIX')
|
||||
@ -168,6 +170,7 @@ class Server(Thread):
|
||||
except Empty:
|
||||
pass
|
||||
|
||||
# Get notifications from worker process
|
||||
for worker in self.workers:
|
||||
while True:
|
||||
try:
|
||||
@ -177,6 +180,7 @@ class Server(Thread):
|
||||
except Empty:
|
||||
break
|
||||
|
||||
# Remove finished jobs
|
||||
for worker in [w for w in self.workers if not w.is_alive]:
|
||||
self.workers.remove(worker)
|
||||
job = worker.job
|
||||
@ -189,16 +193,24 @@ class Server(Thread):
|
||||
job.duration = time.time() - job.start_time
|
||||
self.changed_jobs_queue.put(job)
|
||||
|
||||
# Start new workers
|
||||
if len(self.pool) + len(self.workers) < self.pool_size:
|
||||
try:
|
||||
self.pool.append(self.launch_worker())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Start waiting jobs
|
||||
if len(self.pool) > 0 and len(self.waiting_jobs) > 0:
|
||||
job = self.waiting_jobs.pop()
|
||||
worker = self.pool.pop()
|
||||
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)
|
||||
self.workers.append(worker)
|
||||
job.log_path = worker.log_path
|
||||
@ -215,11 +227,13 @@ class Server(Thread):
|
||||
self.kill_queue.put(job)
|
||||
|
||||
def killall(self):
|
||||
for job in self.workers:
|
||||
self.kill_queue.put(job)
|
||||
for worker in self.workers:
|
||||
self.kill_queue.put(worker.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:
|
||||
if job is worker.job:
|
||||
worker.kill()
|
||||
|
@ -57,7 +57,7 @@ recipe_modules = ['recipe_' + r for r in (
|
||||
'monitor', 'republika', 'beta', 'beta_en', 'glasjavnosti',
|
||||
'esquire', 'livemint', 'thedgesingapore', 'darknet', 'rga',
|
||||
'intelligencer', 'theoldfoodie', 'hln_be', 'honvedelem',
|
||||
'the_new_republic',
|
||||
'the_new_republic', 'philly',
|
||||
)]
|
||||
|
||||
|
||||
|
@ -7,7 +7,7 @@ lemonde.fr
|
||||
'''
|
||||
|
||||
import re
|
||||
from datetime import date
|
||||
#from datetime import date
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
|
||||
@ -20,7 +20,28 @@ class LeMonde(BasicNewsRecipe):
|
||||
|
||||
max_articles_per_feed = 30
|
||||
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 = [
|
||||
('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'),
|
||||
('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'}),
|
||||
dict(name='div', attrs={'id':'xiti-logo-noscript'}),
|
||||
dict(name='br', 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
|
||||
[
|
||||
@ -68,8 +95,8 @@ class LeMonde(BasicNewsRecipe):
|
||||
]
|
||||
]
|
||||
|
||||
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)
|
||||
# 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)
|
||||
|
||||
# Used to filter duplicated articles
|
||||
articles_list = []
|
||||
@ -94,3 +121,5 @@ class LeMonde(BasicNewsRecipe):
|
||||
return True
|
||||
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