Sync to trunk.

This commit is contained in:
John Schember 2009-09-12 19:40:03 -04:00
commit de809a8fdf
83 changed files with 13464 additions and 18632 deletions

BIN
resources/images/star.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -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)

View File

@ -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)

View File

@ -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'

View File

@ -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',

View File

@ -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']

View File

@ -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()

View File

@ -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())

View File

@ -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

View File

@ -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

View File

@ -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')]

View File

@ -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

View 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'

View 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

View File

@ -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(

View File

@ -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.')),
])

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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,

View File

@ -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)

View File

@ -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

View File

@ -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):

View File

@ -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):

View File

@ -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>&amp;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>&amp;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>&amp;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>&amp;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. &amp;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>&amp;Show password</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Max. &amp;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>

View File

@ -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 &amp;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>

View File

@ -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)

View File

@ -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()

View File

@ -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()

View File

@ -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):

View File

@ -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):

View File

@ -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

View File

@ -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(_(

View File

@ -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:

View File

@ -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:

View File

@ -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&amp;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'
) % '&amp;'.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

View File

@ -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')

View File

@ -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>

View File

@ -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 &amp;&amp; python setup.py build &amp;&amp; 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

View File

@ -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('~')

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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',
)]

View File

@ -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

View 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')
]