mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge from trunk
This commit is contained in:
commit
69f06412fa
@ -6,7 +6,7 @@ class HBR(BasicNewsRecipe):
|
|||||||
title = 'Harvard Business Review Blogs'
|
title = 'Harvard Business Review Blogs'
|
||||||
description = 'To subscribe go to http://hbr.harvardbusiness.org'
|
description = 'To subscribe go to http://hbr.harvardbusiness.org'
|
||||||
needs_subscription = True
|
needs_subscription = True
|
||||||
__author__ = 'Kovid Goyal and Sujata Raman, enhanced by BrianG'
|
__author__ = 'Kovid Goyal, enhanced by BrianG'
|
||||||
language = 'en'
|
language = 'en'
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
|
|
||||||
|
@ -348,7 +348,6 @@ class MobiReader(object):
|
|||||||
self.processed_html = self.remove_random_bytes(self.processed_html)
|
self.processed_html = self.remove_random_bytes(self.processed_html)
|
||||||
root = soupparser.fromstring(self.processed_html)
|
root = soupparser.fromstring(self.processed_html)
|
||||||
|
|
||||||
|
|
||||||
if root.tag != 'html':
|
if root.tag != 'html':
|
||||||
self.log.warn('File does not have opening <html> tag')
|
self.log.warn('File does not have opening <html> tag')
|
||||||
nroot = html.fromstring('<html><head></head><body></body></html>')
|
nroot = html.fromstring('<html><head></head><body></body></html>')
|
||||||
|
@ -38,6 +38,10 @@ class DeviceJob(BaseJob): # {{{
|
|||||||
BaseJob.__init__(self, description)
|
BaseJob.__init__(self, description)
|
||||||
self.func = func
|
self.func = func
|
||||||
self.callback_on_done = done
|
self.callback_on_done = done
|
||||||
|
if not isinstance(self.callback_on_done, (Dispatcher,
|
||||||
|
FunctionDispatcher)):
|
||||||
|
self.callback_on_done = FunctionDispatcher(self.callback_on_done)
|
||||||
|
|
||||||
self.args, self.kwargs = args, kwargs
|
self.args, self.kwargs = args, kwargs
|
||||||
self.exception = None
|
self.exception = None
|
||||||
self.job_manager = job_manager
|
self.job_manager = job_manager
|
||||||
@ -259,7 +263,8 @@ class DeviceManager(Thread): # {{{
|
|||||||
job = self.next()
|
job = self.next()
|
||||||
if job is not None:
|
if job is not None:
|
||||||
self.current_job = job
|
self.current_job = job
|
||||||
self.device.set_progress_reporter(job.report_progress)
|
if self.device is not None:
|
||||||
|
self.device.set_progress_reporter(job.report_progress)
|
||||||
self.current_job.run()
|
self.current_job.run()
|
||||||
self.current_job = None
|
self.current_job = None
|
||||||
else:
|
else:
|
||||||
@ -592,7 +597,7 @@ class DeviceMenu(QMenu): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class DeviceSignals(QObject):
|
class DeviceSignals(QObject): # {{{
|
||||||
#: This signal is emitted once, after metadata is downloaded from the
|
#: This signal is emitted once, after metadata is downloaded from the
|
||||||
#: connected device.
|
#: connected device.
|
||||||
#: The sequence: gui.device_manager.is_device_connected will become True,
|
#: The sequence: gui.device_manager.is_device_connected will become True,
|
||||||
@ -609,6 +614,7 @@ class DeviceSignals(QObject):
|
|||||||
device_connection_changed = pyqtSignal(object)
|
device_connection_changed = pyqtSignal(object)
|
||||||
|
|
||||||
device_signals = DeviceSignals()
|
device_signals = DeviceSignals()
|
||||||
|
# }}}
|
||||||
|
|
||||||
class DeviceMixin(object): # {{{
|
class DeviceMixin(object): # {{{
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ __docformat__ = 'restructuredtext en'
|
|||||||
import os, socket, time
|
import os, socket, time
|
||||||
from binascii import unhexlify
|
from binascii import unhexlify
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from threading import Thread
|
||||||
from itertools import repeat
|
from itertools import repeat
|
||||||
|
|
||||||
from calibre.utils.smtp import (compose_mail, sendmail, extract_email_address,
|
from calibre.utils.smtp import (compose_mail, sendmail, extract_email_address,
|
||||||
@ -22,9 +23,30 @@ from calibre.library.save_to_disk import get_components
|
|||||||
from calibre.utils.config import tweaks
|
from calibre.utils.config import tweaks
|
||||||
from calibre.gui2.threaded_jobs import ThreadedJob
|
from calibre.gui2.threaded_jobs import ThreadedJob
|
||||||
|
|
||||||
|
class Worker(Thread):
|
||||||
|
|
||||||
|
def __init__(self, func, args):
|
||||||
|
Thread.__init__(self)
|
||||||
|
self.daemon = True
|
||||||
|
self.exception = self.tb = None
|
||||||
|
self.func, self.args = func, args
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
#time.sleep(1000)
|
||||||
|
try:
|
||||||
|
self.func(*self.args)
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
self.exception = e
|
||||||
|
self.tb = traceback.format_exc()
|
||||||
|
finally:
|
||||||
|
self.func = self.args = None
|
||||||
|
|
||||||
|
|
||||||
class Sendmail(object):
|
class Sendmail(object):
|
||||||
|
|
||||||
MAX_RETRIES = 1
|
MAX_RETRIES = 1
|
||||||
|
TIMEOUT = 15 * 60 # seconds
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.calculate_rate_limit()
|
self.calculate_rate_limit()
|
||||||
@ -42,22 +64,32 @@ class Sendmail(object):
|
|||||||
abort=None, notifications=None):
|
abort=None, notifications=None):
|
||||||
|
|
||||||
try_count = 0
|
try_count = 0
|
||||||
while try_count <= self.MAX_RETRIES:
|
while True:
|
||||||
if try_count > 0:
|
if try_count > 0:
|
||||||
log('\nRetrying in %d seconds...\n' %
|
log('\nRetrying in %d seconds...\n' %
|
||||||
self.rate_limit)
|
self.rate_limit)
|
||||||
try:
|
worker = Worker(self.sendmail,
|
||||||
self.sendmail(attachment, aname, to, subject, text, log)
|
(attachment, aname, to, subject, text, log))
|
||||||
try_count = self.MAX_RETRIES
|
worker.start()
|
||||||
log('Email successfully sent')
|
start_time = time.time()
|
||||||
except:
|
while worker.is_alive():
|
||||||
|
worker.join(0.2)
|
||||||
if abort.is_set():
|
if abort.is_set():
|
||||||
|
log('Sending aborted by user')
|
||||||
return
|
return
|
||||||
if try_count == self.MAX_RETRIES:
|
if time.time() - start_time > self.TIMEOUT:
|
||||||
raise
|
log('Sending timed out')
|
||||||
log.exception('\nSending failed...\n')
|
raise Exception(
|
||||||
|
'Sending email %r to %r timed out, aborting'% (subject,
|
||||||
|
to))
|
||||||
|
if worker.exception is None:
|
||||||
|
log('Email successfully sent')
|
||||||
|
return
|
||||||
|
log.error('\nSending failed...\n')
|
||||||
|
log.debug(worker.tb)
|
||||||
try_count += 1
|
try_count += 1
|
||||||
|
if try_count > self.MAX_RETRIES:
|
||||||
|
raise worker.exception
|
||||||
|
|
||||||
def sendmail(self, attachment, aname, to, subject, text, log):
|
def sendmail(self, attachment, aname, to, subject, text, log):
|
||||||
while time.time() - self.last_send_time <= self.rate_limit:
|
while time.time() - self.last_send_time <= self.rate_limit:
|
||||||
@ -90,7 +122,7 @@ def send_mails(jobnames, callback, attachments, to_s, subjects,
|
|||||||
attachments, to_s, subjects, texts, attachment_names):
|
attachments, to_s, subjects, texts, attachment_names):
|
||||||
description = _('Email %s to %s') % (name, to)
|
description = _('Email %s to %s') % (name, to)
|
||||||
job = ThreadedJob('email', description, gui_sendmail, (attachment, aname, to,
|
job = ThreadedJob('email', description, gui_sendmail, (attachment, aname, to,
|
||||||
subject, text), {}, callback, killable=False)
|
subject, text), {}, callback)
|
||||||
job_manager.run_threaded_job(job)
|
job_manager.run_threaded_job(job)
|
||||||
|
|
||||||
|
|
||||||
|
@ -457,7 +457,7 @@ class JobsDialog(QDialog, Ui_JobsDialog):
|
|||||||
|
|
||||||
def kill_job(self, *args):
|
def kill_job(self, *args):
|
||||||
if question_dialog(self, _('Are you sure?'), _('Do you really want to stop the selected job?')):
|
if question_dialog(self, _('Are you sure?'), _('Do you really want to stop the selected job?')):
|
||||||
for index in self.jobs_view.selectedIndexes():
|
for index in self.jobs_view.selectionModel().selectedRows():
|
||||||
row = index.row()
|
row = index.row()
|
||||||
self.model.kill_job(row, self)
|
self.model.kill_job(row, self)
|
||||||
|
|
||||||
|
@ -462,11 +462,11 @@ class EditRules(QWidget): # {{{
|
|||||||
self.l = l = QGridLayout(self)
|
self.l = l = QGridLayout(self)
|
||||||
self.setLayout(l)
|
self.setLayout(l)
|
||||||
|
|
||||||
self.l1 = l1 = QLabel(_(
|
self.l1 = l1 = QLabel('<p>'+_(
|
||||||
'You can control the color of columns in the'
|
'You can control the color of columns in the'
|
||||||
' book list by creating "rules" that tell calibre'
|
' book list by creating "rules" that tell calibre'
|
||||||
' what color to use. Click the Add Rule button below'
|
' what color to use. Click the Add Rule button below'
|
||||||
' to get started. You can change an existing rule by double'
|
' to get started.<p>You can <b>change an existing rule</b> by double'
|
||||||
' clicking it.'))
|
' clicking it.'))
|
||||||
l1.setWordWrap(True)
|
l1.setWordWrap(True)
|
||||||
l.addWidget(l1, 0, 0, 1, 2)
|
l.addWidget(l1, 0, 0, 1, 2)
|
||||||
|
@ -139,14 +139,26 @@ class AmazonKindleStore(StorePlugin):
|
|||||||
cover_xpath = './/div[@class="productTitle"]//img/@src'
|
cover_xpath = './/div[@class="productTitle"]//img/@src'
|
||||||
title_xpath = './/div[@class="productTitle"]/a//text()'
|
title_xpath = './/div[@class="productTitle"]/a//text()'
|
||||||
price_xpath = './/div[@class="newPrice"]/span/text()'
|
price_xpath = './/div[@class="newPrice"]/span/text()'
|
||||||
# Vertical list of books. Search "martin"
|
# Vertical list of books.
|
||||||
else:
|
else:
|
||||||
data_xpath = '//div[contains(@class, "results")]//div[contains(@class, "result")]'
|
# New style list. Search "Paolo Bacigalupi"
|
||||||
format_xpath = './/span[@class="binding"]//text()'
|
if doc.xpath('boolean(//div[@class="image"])'):
|
||||||
asin_xpath = './/div[@class="image"]/a[1]'
|
data_xpath = '//div[contains(@class, "results")]//div[contains(@class, "result")]'
|
||||||
cover_xpath = './/img[@class="productImage"]/@src'
|
format_xpath = './/span[@class="binding"]//text()'
|
||||||
title_xpath = './/a[@class="title"]/text()'
|
asin_xpath = './/div[@class="image"]/a[1]'
|
||||||
price_xpath = './/span[@class="price"]/text()'
|
cover_xpath = './/img[@class="productImage"]/@src'
|
||||||
|
title_xpath = './/a[@class="title"]/text()'
|
||||||
|
price_xpath = './/span[@class="price"]/text()'
|
||||||
|
# Old style list. Search "martin"
|
||||||
|
else:
|
||||||
|
data_xpath = '//div[contains(@class, "result")]'
|
||||||
|
format_xpath = './/span[@class="format"]//text()'
|
||||||
|
asin_xpath = './/div[@class="productImage"]/a[1]'
|
||||||
|
cover_xpath = './/div[@class="productImage"]//img/@src'
|
||||||
|
title_xpath = './/div[@class="productTitle"]/a/text()'
|
||||||
|
price_xpath = './/div[@class="newPrice"]//span//text()'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for data in doc.xpath(data_xpath):
|
for data in doc.xpath(data_xpath):
|
||||||
if counter <= 0:
|
if counter <= 0:
|
||||||
|
@ -26,7 +26,7 @@ from calibre.library.custom_columns import CustomColumns
|
|||||||
from calibre.library.sqlite import connect, IntegrityError
|
from calibre.library.sqlite import connect, IntegrityError
|
||||||
from calibre.library.prefs import DBPrefs
|
from calibre.library.prefs import DBPrefs
|
||||||
from calibre.ebooks.metadata.book.base import Metadata
|
from calibre.ebooks.metadata.book.base import Metadata
|
||||||
from calibre.constants import preferred_encoding, iswindows, isosx, filesystem_encoding
|
from calibre.constants import preferred_encoding, iswindows, filesystem_encoding
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.customize.ui import run_plugins_on_import
|
from calibre.customize.ui import run_plugins_on_import
|
||||||
from calibre import isbytestring
|
from calibre import isbytestring
|
||||||
@ -188,8 +188,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
apply_default_prefs = not os.path.exists(self.dbpath)
|
apply_default_prefs = not os.path.exists(self.dbpath)
|
||||||
self.connect()
|
self.connect()
|
||||||
|
|
||||||
self.is_case_sensitive = not iswindows and not isosx and \
|
self.is_case_sensitive = (not iswindows and
|
||||||
not os.path.exists(self.dbpath.replace('metadata.db', 'MeTAdAtA.dB'))
|
not os.path.exists(self.dbpath.replace('metadata.db',
|
||||||
|
'MeTAdAtA.dB')))
|
||||||
SchemaUpgrade.__init__(self)
|
SchemaUpgrade.__init__(self)
|
||||||
# Guarantee that the library_id is set
|
# Guarantee that the library_id is set
|
||||||
self.library_id
|
self.library_id
|
||||||
@ -606,7 +607,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
f = self.format(id, format, index_is_id=True, as_file=True)
|
f = self.format(id, format, index_is_id=True, as_file=True)
|
||||||
if f is None:
|
if f is None:
|
||||||
continue
|
continue
|
||||||
with tempfile.SpooledTemporaryFile(max_size=100*(1024**2)) as stream:
|
with tempfile.SpooledTemporaryFile(max_size=30*(1024**2)) as stream:
|
||||||
with f:
|
with f:
|
||||||
shutil.copyfileobj(f, stream)
|
shutil.copyfileobj(f, stream)
|
||||||
stream.seek(0)
|
stream.seek(0)
|
||||||
|
@ -189,7 +189,7 @@ Extra CSS
|
|||||||
|
|
||||||
This option allows you to specify arbitrary CSS that will be applied to all HTML files in the
|
This option allows you to specify arbitrary CSS that will be applied to all HTML files in the
|
||||||
input. This CSS is applied with very high priority and so should override most CSS present in
|
input. This CSS is applied with very high priority and so should override most CSS present in
|
||||||
the input document itself. You can use this setting to fine tune the presentation/layout of your
|
the **input document** itself. You can use this setting to fine tune the presentation/layout of your
|
||||||
document. For example, if you want all paragraphs of class `endnote` to be right aligned, just
|
document. For example, if you want all paragraphs of class `endnote` to be right aligned, just
|
||||||
add::
|
add::
|
||||||
|
|
||||||
@ -200,7 +200,8 @@ or if you want to change the indentation of all paragraphs::
|
|||||||
p { text-indent: 5mm; }
|
p { text-indent: 5mm; }
|
||||||
|
|
||||||
:guilabel:`Extra CSS` is a very powerful option, but you do need an understanding of how CSS works
|
:guilabel:`Extra CSS` is a very powerful option, but you do need an understanding of how CSS works
|
||||||
to use it to its full potential.
|
to use it to its full potential. You can use the debug pipeline option described above to see what
|
||||||
|
CSS is present in your input document.
|
||||||
|
|
||||||
Miscellaneous
|
Miscellaneous
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
@ -317,6 +317,9 @@ def feed_from_xml(raw_xml, title=None, oldest_article=7,
|
|||||||
max_articles_per_feed=100,
|
max_articles_per_feed=100,
|
||||||
get_article_url=lambda item: item.get('link', None),
|
get_article_url=lambda item: item.get('link', None),
|
||||||
log=default_log):
|
log=default_log):
|
||||||
|
# Handle unclosed escaped entities. They trip up feedparser and HBR for one
|
||||||
|
# generates them
|
||||||
|
raw_xml = re.sub(r'(&#\d+)([^0-9;])', r'\1;\2', raw_xml)
|
||||||
feed = parse(raw_xml)
|
feed = parse(raw_xml)
|
||||||
pfeed = Feed(get_article_url=get_article_url, log=log)
|
pfeed = Feed(get_article_url=get_article_url, log=log)
|
||||||
pfeed.populate_from_feed(feed, title=title,
|
pfeed.populate_from_feed(feed, title=title,
|
||||||
|
@ -13,8 +13,8 @@ from functools import partial
|
|||||||
from contextlib import nested, closing
|
from contextlib import nested, closing
|
||||||
|
|
||||||
|
|
||||||
from calibre import browser, __appname__, iswindows, \
|
from calibre import (browser, __appname__, iswindows,
|
||||||
strftime, preferred_encoding, as_unicode
|
strftime, preferred_encoding, as_unicode)
|
||||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString, CData, Tag
|
from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString, CData, Tag
|
||||||
from calibre.ebooks.metadata.opf2 import OPFCreator
|
from calibre.ebooks.metadata.opf2 import OPFCreator
|
||||||
from calibre import entity_to_unicode
|
from calibre import entity_to_unicode
|
||||||
|
Loading…
x
Reference in New Issue
Block a user