Sycn to pluginize

This commit is contained in:
John Schember 2009-05-09 07:45:16 -04:00
commit d69b1ffbdc
23 changed files with 407 additions and 338 deletions

View File

@ -59,10 +59,9 @@ class HTMLRenderer(object):
def render_html(path_to_html, width=590, height=750): def render_html(path_to_html, width=590, height=750):
from PyQt4.QtWebKit import QWebPage from PyQt4.QtWebKit import QWebPage
from PyQt4.Qt import QEventLoop, QPalette, Qt, SIGNAL, QUrl, QSize, \ from PyQt4.Qt import QEventLoop, QPalette, Qt, SIGNAL, QUrl, QSize
QApplication from calibre.gui2 import is_ok_to_use_qt
if QApplication.instance() is None: if not is_ok_to_use_qt(): return None
QApplication([])
path_to_html = os.path.abspath(path_to_html) path_to_html = os.path.abspath(path_to_html)
with CurrentDir(os.path.dirname(path_to_html)): with CurrentDir(os.path.dirname(path_to_html)):
page = QWebPage() page = QWebPage()

View File

@ -80,8 +80,10 @@ class EPUBInput(InputFormatPlugin):
t.set('href', guide_cover) t.set('href', guide_cover)
t.set('title', 'Title Page') t.set('title', 'Title Page')
from calibre.ebooks import render_html from calibre.ebooks import render_html
renderer = render_html(guide_cover)
if renderer is not None:
open('calibre_raster_cover.jpg', 'wb').write( open('calibre_raster_cover.jpg', 'wb').write(
render_html(guide_cover).data) renderer.data)
def convert(self, stream, options, file_ext, log, accelerators): def convert(self, stream, options, file_ext, log, accelerators):

View File

@ -290,14 +290,6 @@ class MobiReader(object):
self.replace_page_breaks() self.replace_page_breaks()
self.cleanup_html() self.cleanup_html()
if self.processed_html.startswith('<body'):
self.processed_html = '<html><head></head>'+self.processed_html+'</html>'
self.processed_html = \
re.compile('<head>', re.IGNORECASE).sub(
'\n<head>\n'
'\t<link type="text/css" href="styles.css" />\n',
self.processed_html)
self.log.debug('Parsing HTML...') self.log.debug('Parsing HTML...')
root = html.fromstring(self.processed_html) root = html.fromstring(self.processed_html)
if root.xpath('descendant::p/descendant::p'): if root.xpath('descendant::p/descendant::p'):
@ -305,7 +297,7 @@ class MobiReader(object):
self.log.warning('Markup contains unclosed <p> tags, parsing using', self.log.warning('Markup contains unclosed <p> tags, parsing using',
'BeatifulSoup') 'BeatifulSoup')
root = soupparser.fromstring(self.processed_html) root = soupparser.fromstring(self.processed_html)
if root[0].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>')
bod = nroot.find('body') bod = nroot.find('body')
@ -314,6 +306,35 @@ class MobiReader(object):
bod.append(child) bod.append(child)
root = nroot root = nroot
htmls = list(root.xpath('//html'))
if len(htmls) > 1:
self.log.warn('Markup contains multiple <html> tags')
# Keep only the largest head and body
bodies, heads = root.xpath('//body'), root.xpath('//head')
def sz(x): return len(list(x.iter()))
def scmp(x, y): return cmp(sz(x), sz(y))
body = list(sorted(bodies, cmp=scmp))
head = list(sorted(heads, cmp=scmp))
for x in root: root.remove(x)
if head:
root.append(head[-1])
if body:
root.append(body[-1])
for x in root.xpath('//script'):
x.getparent().remove(x)
head = root.xpath('//head')
if head:
head = head[0]
else:
head = root.makeelement('head', {})
root.insert(0, head)
head.text = '\n\t'
link = head.makeelement('link', {'type':'text/css',
'href':'styles.css'})
head.insert(0, link)
link.tail = '\n\t'
self.upshift_markup(root) self.upshift_markup(root)
guides = root.xpath('//guide') guides = root.xpath('//guide')
guide = guides[0] if guides else None guide = guides[0] if guides else None

View File

@ -24,6 +24,19 @@ def asfloat(value, default):
value = default value = default
return float(value) return float(value)
def dynamic_rescale_factor(node):
classes = node.get('class', '').split(' ')
classes = [x.replace('calibre_rescale_', '') for x in classes if
x.startswith('calibre_rescale_')]
if not classes: return None
factor = 1.0
for x in classes:
try:
factor *= float(x)/100.
except ValueError:
continue
return factor
class KeyMapper(object): class KeyMapper(object):
def __init__(self, sbase, dbase, dkey): def __init__(self, sbase, dbase, dkey):
@ -202,8 +215,16 @@ class CSSFlattener(object):
if 'bgcolor' in node.attrib: if 'bgcolor' in node.attrib:
cssdict['background-color'] = node.attrib['bgcolor'] cssdict['background-color'] = node.attrib['bgcolor']
del node.attrib['bgcolor'] del node.attrib['bgcolor']
if not self.context.disable_font_rescaling and \ if not self.context.disable_font_rescaling:
'font-size' in cssdict or tag == 'body': _sbase = self.sbase if self.sbase is not None else \
self.context.source.fbase
dyn_rescale = dynamic_rescale_factor(node)
if dyn_rescale is not None:
fsize = self.fmap[_sbase]
fsize *= dyn_rescale
cssdict['font-size'] = '%0.5fem'%(fsize/psize)
psize = fsize
elif 'font-size' in cssdict or tag == 'body':
fsize = self.fmap[style['font-size']] fsize = self.fmap[style['font-size']]
cssdict['font-size'] = "%0.5fem" % (fsize / psize) cssdict['font-size'] = "%0.5fem" % (fsize / psize)
psize = fsize psize = fsize

View File

@ -25,15 +25,17 @@ class Jacket(object):
<title>%(title)s</title> <title>%(title)s</title>
</head> </head>
<body> <body>
<div class="calibre_rescale_100">
<div style="text-align:center"> <div style="text-align:center">
<h1>%(title)s</h1> <h1 class="calibre_rescale_180">%(title)s</h1>
<h2>%(jacket)s</h2> <h2 class="calibre_rescale_140">%(jacket)s</h2>
<div>%(series)s</div> <div class="calibre_rescale_100">%(series)s</div>
<div>%(tags)s</div> <div class="calibre_rescale_100">%(tags)s</div>
</div> </div>
<div style="margin-top:2em"> <div style="margin-top:2em" class="calibre_rescale_100">
%(comments)s %(comments)s
</div> </div>
</div>
</body> </body>
</html> </html>
''') ''')

View File

@ -369,13 +369,13 @@ class FlowSplitter(object):
for path in ( for path in (
'//*[re:match(name(), "h[1-6]", "i")]', '//*[re:match(name(), "h[1-6]", "i")]',
'/html/body/div', '/h:html/h:body/h:div',
'//pre', '//h:pre',
'//hr', '//h:hr',
'//p', '//h:p',
'//div', '//h:div',
'//br', '//h:br',
'//li', '//h:li',
): ):
elems = root.xpath(path, namespaces=NAMESPACES) elems = root.xpath(path, namespaces=NAMESPACES)
elem = pick_elem(elems) elem = pick_elem(elems)

View File

@ -27,8 +27,6 @@ def _config():
help=_('Frequently used directories')) help=_('Frequently used directories'))
c.add_opt('send_to_storage_card_by_default', default=False, c.add_opt('send_to_storage_card_by_default', default=False,
help=_('Send file to storage card instead of main memory by default')) help=_('Send file to storage card instead of main memory by default'))
c.add_opt('save_to_disk_single_format', default='lrf',
help=_('The format to use when saving single files to disk'))
c.add_opt('confirm_delete', default=False, c.add_opt('confirm_delete', default=False,
help=_('Confirm before deleting')) help=_('Confirm before deleting'))
c.add_opt('toolbar_icon_size', default=QSize(48, 48), c.add_opt('toolbar_icon_size', default=QSize(48, 48),
@ -100,18 +98,23 @@ def available_width():
def extension(path): def extension(path):
return os.path.splitext(path)[1][1:].lower() return os.path.splitext(path)[1][1:].lower()
def warning_dialog(parent, title, msg, det_msg=''): def warning_dialog(parent, title, msg, det_msg='', show=False):
d = QMessageBox(QMessageBox.Warning, 'WARNING: '+title, msg, QMessageBox.Ok, d = QMessageBox(QMessageBox.Warning, 'WARNING: '+title, msg, QMessageBox.Ok,
parent) parent)
d.setDetailedText(det_msg) d.setDetailedText(det_msg)
d.setIconPixmap(QPixmap(':/images/dialog_warning.svg')) d.setIconPixmap(QPixmap(':/images/dialog_warning.svg'))
if show:
return d.exec_()
return d return d
def error_dialog(parent, title, msg, det_msg=''): def error_dialog(parent, title, msg, det_msg='', show=False):
d = QMessageBox(QMessageBox.Critical, 'ERROR: '+title, msg, QMessageBox.Ok, d = QMessageBox(QMessageBox.Critical, 'ERROR: '+title, msg, QMessageBox.Ok,
parent) parent)
d.setDetailedText(det_msg) d.setDetailedText(det_msg)
d.setIconPixmap(QPixmap(':/images/dialog_error.svg')) d.setIconPixmap(QPixmap(':/images/dialog_error.svg'))
if show:
return d.exec_()
return d return d
def question_dialog(parent, title, msg, det_msg=''): def question_dialog(parent, title, msg, det_msg=''):
@ -121,13 +124,16 @@ def question_dialog(parent, title, msg, det_msg=''):
d.setIconPixmap(QPixmap(':/images/dialog_information.svg')) d.setIconPixmap(QPixmap(':/images/dialog_information.svg'))
return d return d
def info_dialog(parent, title, msg, det_msg=''): def info_dialog(parent, title, msg, det_msg='', show=False):
d = QMessageBox(QMessageBox.Information, title, msg, QMessageBox.NoButton, d = QMessageBox(QMessageBox.Information, title, msg, QMessageBox.NoButton,
parent) parent)
d.setDetailedText(det_msg) d.setDetailedText(det_msg)
d.setIconPixmap(QPixmap(':/images/dialog_information.svg')) d.setIconPixmap(QPixmap(':/images/dialog_information.svg'))
if show:
return d.exec_()
return d return d
def qstring_to_unicode(q): def qstring_to_unicode(q):
return unicode(q) return unicode(q)

View File

@ -530,8 +530,8 @@ class DeviceGUI(object):
]) ])
error_dialog(self, _('Failed to email books'), error_dialog(self, _('Failed to email books'),
_('Failed to email the following books:'), _('Failed to email the following books:'),
'%s'%errors, '%s'%errors
show=True) )
else: else:
self.status_bar.showMessage(_('Sent by email:') + ', '.join(good), self.status_bar.showMessage(_('Sent by email:') + ', '.join(good),
5000) 5000)

View File

@ -399,12 +399,15 @@ class ConfigDialog(QDialog, Ui_Dialog):
default_index = self.output_format.findText(prefs['output_format']) default_index = self.output_format.findText(prefs['output_format'])
self.output_format.setCurrentIndex(default_index if default_index != -1 else 0) self.output_format.setCurrentIndex(default_index if default_index != -1 else 0)
self.book_exts = sorted(BOOK_EXTENSIONS) output_formats = sorted(available_output_formats())
for ext in self.book_exts: output_formats.remove('oeb')
self.single_format.addItem(ext.upper(), QVariant(ext)) for f in output_formats:
self.output_format.addItem(f.upper())
default_index = \
self.output_format.findText(prefs['output_format'].upper())
self.output_format.setCurrentIndex(default_index if default_index != -1 else 0)
single_format = config['save_to_disk_single_format']
self.single_format.setCurrentIndex(self.book_exts.index(single_format))
self.cover_browse.setValue(config['cover_flow_queue_length']) self.cover_browse.setValue(config['cover_flow_queue_length'])
self.systray_notifications.setChecked(not config['disable_tray_notification']) self.systray_notifications.setChecked(not config['disable_tray_notification'])
from calibre.translations.compiled import translations from calibre.translations.compiled import translations
@ -426,17 +429,17 @@ class ConfigDialog(QDialog, Ui_Dialog):
self.pdf_metadata.setChecked(prefs['read_file_metadata']) self.pdf_metadata.setChecked(prefs['read_file_metadata'])
added_html = False exts = set([])
for ext in self.book_exts: for ext in BOOK_EXTENSIONS:
ext = ext.lower() ext = ext.lower()
ext = re.sub(r'(x{0,1})htm(l{0,1})', 'html', ext) ext = re.sub(r'(x{0,1})htm(l{0,1})', 'html', ext)
if ext == 'lrf' or is_supported('book.'+ext): if ext == 'lrf' or is_supported('book.'+ext):
if ext == 'html' and added_html: exts.add(ext)
continue
for ext in sorted(exts):
self.viewer.addItem(ext.upper()) self.viewer.addItem(ext.upper())
self.viewer.item(self.viewer.count()-1).setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable) self.viewer.item(self.viewer.count()-1).setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable)
self.viewer.item(self.viewer.count()-1).setCheckState(Qt.Checked if ext.upper() in config['internally_viewed_formats'] else Qt.Unchecked) self.viewer.item(self.viewer.count()-1).setCheckState(Qt.Checked if ext.upper() in config['internally_viewed_formats'] else Qt.Unchecked)
added_html = ext == 'html'
self.viewer.sortItems() self.viewer.sortItems()
self.start.setEnabled(not getattr(self.server, 'is_running', False)) self.start.setEnabled(not getattr(self.server, 'is_running', False))
self.test.setEnabled(not self.start.isEnabled()) self.test.setEnabled(not self.start.isEnabled())
@ -767,8 +770,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
p = {0:'normal', 1:'high', 2:'low'}[self.priority.currentIndex()] p = {0:'normal', 1:'high', 2:'low'}[self.priority.currentIndex()]
prefs['worker_process_priority'] = p prefs['worker_process_priority'] = p
prefs['read_file_metadata'] = bool(self.pdf_metadata.isChecked()) prefs['read_file_metadata'] = bool(self.pdf_metadata.isChecked())
prefs['output_format'] = self.output_format.currentText() prefs['output_format'] = unicode(self.output_format.currentText()).upper()
config['save_to_disk_single_format'] = self.book_exts[self.single_format.currentIndex()]
config['cover_flow_queue_length'] = self.cover_browse.value() config['cover_flow_queue_length'] = self.cover_browse.value()
prefs['language'] = str(self.language.itemData(self.language.currentIndex()).toString()) prefs['language'] = str(self.language.itemData(self.language.currentIndex()).toString())
config['systray_icon'] = self.systray_icon.checkState() == Qt.Checked config['systray_icon'] = self.systray_icon.checkState() == Qt.Checked

View File

@ -147,19 +147,6 @@
<item> <item>
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Format for &amp;single file save:</string>
</property>
<property name="buddy">
<cstring>single_format</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="single_format"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
<property name="text"> <property name="text">
<string>Default network &amp;timeout:</string> <string>Default network &amp;timeout:</string>
@ -169,7 +156,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="1" column="1">
<widget class="QSpinBox" name="timeout"> <widget class="QSpinBox" name="timeout">
<property name="toolTip"> <property name="toolTip">
<string>Set the default timeout for network fetches (i.e. anytime we go out to the internet to get information)</string> <string>Set the default timeout for network fetches (i.e. anytime we go out to the internet to get information)</string>
@ -188,10 +175,10 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="2" column="1">
<widget class="QComboBox" name="language"/> <widget class="QComboBox" name="language"/>
</item> </item>
<item row="3" column="0"> <item row="2" column="0">
<widget class="QLabel" name="label_7"> <widget class="QLabel" name="label_7">
<property name="text"> <property name="text">
<string>Choose &amp;language (requires restart):</string> <string>Choose &amp;language (requires restart):</string>
@ -201,7 +188,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1"> <item row="3" column="1">
<widget class="QComboBox" name="priority"> <widget class="QComboBox" name="priority">
<item> <item>
<property name="text"> <property name="text">
@ -220,7 +207,7 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="4" column="0"> <item row="3" column="0">
<widget class="QLabel" name="priority_label"> <widget class="QLabel" name="priority_label">
<property name="text"> <property name="text">
<string>Job &amp;priority:</string> <string>Job &amp;priority:</string>

View File

@ -20,6 +20,7 @@ from calibre.utils.search_query_parser import SearchQueryParser
from calibre.utils.pyparsing import ParseException from calibre.utils.pyparsing import ParseException
from calibre.gui2 import NONE, error_dialog, config as gconf from calibre.gui2 import NONE, error_dialog, config as gconf
from calibre.utils.config import DynamicConfig from calibre.utils.config import DynamicConfig
from calibre.ptempfile import PersistentTemporaryFile
from calibre.gui2.dialogs.user_profiles import UserProfiles from calibre.gui2.dialogs.user_profiles import UserProfiles
config = DynamicConfig('scheduler') config = DynamicConfig('scheduler')
@ -522,8 +523,12 @@ class Scheduler(QObject):
self.recipes.remove(recipe) self.recipes.remove(recipe)
save_recipes(self.recipes) save_recipes(self.recipes)
return return
pt = PersistentTemporaryFile('_builtin.recipe')
pt.write(script)
pt.close()
script = pt.name
except ValueError: except ValueError:
script = recipe.title script = recipe.title + '.recipe'
self.debug('\tQueueing:', recipe) self.debug('\tQueueing:', recipe)
self.main.download_scheduled_recipe(recipe, script, self.recipe_downloaded) self.main.download_scheduled_recipe(recipe, script, self.recipe_downloaded)
self.queue.add(recipe) self.queue.add(recipe)

View File

@ -47,6 +47,19 @@ from calibre.library.database2 import LibraryDatabase2, CoverCache
from calibre.parallel import JobKilled from calibre.parallel import JobKilled
from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.dialogs.confirm_delete import confirm
class SaveMenu(QMenu):
def __init__(self, parent):
QMenu.__init__(self, _('Save single format to disk...'), parent)
for ext in sorted(BOOK_EXTENSIONS):
action = self.addAction(ext.upper())
setattr(self, 'do_'+ext, partial(self.do, ext))
self.connect(action, SIGNAL('triggered(bool)'),
getattr(self, 'do_'+ext))
def do(self, ext, *args):
self.emit(SIGNAL('save_fmt(PyQt_PyObject)'), ext)
class Main(MainWindow, Ui_MainWindow, DeviceGUI): class Main(MainWindow, Ui_MainWindow, DeviceGUI):
'The main GUI' 'The main GUI'
@ -201,8 +214,12 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.save_menu = QMenu() self.save_menu = QMenu()
self.save_menu.addAction(_('Save to disk')) self.save_menu.addAction(_('Save to disk'))
self.save_menu.addAction(_('Save to disk in a single directory')) self.save_menu.addAction(_('Save to disk in a single directory'))
self.save_menu.addAction(_('Save only %s format to disk')%\ self.save_menu.addAction(_('Save only %s format to disk')%
config.get('save_to_disk_single_format').upper()) prefs['output_format'].upper())
self.save_sub_menu = SaveMenu(self)
self.save_menu.addMenu(self.save_sub_menu)
self.connect(self.save_sub_menu, SIGNAL('save_fmt(PyQt_PyObject)'),
self.save_specific_format_disk)
self.view_menu = QMenu() self.view_menu = QMenu()
self.view_menu.addAction(_('View')) self.view_menu.addAction(_('View'))
@ -856,10 +873,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
_('Failed to download metadata for the following:'), _('Failed to download metadata for the following:'),
details, self).exec_() details, self).exec_()
else: else:
err = _('<b>Failed to download metadata:')+\ err = _('Failed to download metadata:')
'</b><br><pre>'+x.tb+'</pre>' error_dialog(self, _('Error'), err, det_msg=x.tb).exec_()
error_dialog(self, _('Error'), err,
show=True)
@ -912,7 +927,10 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
############################## Save to disk ################################ ############################## Save to disk ################################
def save_single_format_to_disk(self, checked): def save_single_format_to_disk(self, checked):
self.save_to_disk(checked, True, config['save_to_disk_single_format']) self.save_to_disk(checked, True, prefs['output_format'])
def save_specific_format_disk(self, fmt):
self.save_to_disk(False, True, fmt)
def save_to_single_dir(self, checked): def save_to_single_dir(self, checked):
self.save_to_disk(checked, True) self.save_to_disk(checked, True)
@ -921,10 +939,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
rows = self.current_view().selectionModel().selectedRows() rows = self.current_view().selectionModel().selectedRows()
if not rows or len(rows) == 0: if not rows or len(rows) == 0:
d = error_dialog(self, _('Cannot save to disk'), return error_dialog(self, _('Cannot save to disk'),
_('No books selected')) _('No books selected'), show=True)
d.exec_()
return
progress = ProgressDialog(_('Saving to disk...'), min=0, max=len(rows), progress = ProgressDialog(_('Saving to disk...'), min=0, max=len(rows),
parent=self) parent=self)
@ -1266,8 +1282,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
config['show_text_in_toolbar'] else \ config['show_text_in_toolbar'] else \
Qt.ToolButtonIconOnly) Qt.ToolButtonIconOnly)
self.save_menu.actions()[2].setText( self.save_menu.actions()[2].setText(
_('Save only %s format to disk')%config.get( _('Save only %s format to disk')%
'save_to_disk_single_format').upper()) prefs['output_format'].upper())
if self.library_path != d.database_location: if self.library_path != d.database_location:
try: try:
newloc = d.database_location newloc = d.database_location
@ -1414,8 +1430,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
return return
if isinstance(job.exception, JobKilled): if isinstance(job.exception, JobKilled):
return return
error_dialog(self, _('Conversion Error'), job.gui_text(), error_dialog(self, _('Conversion Error'),
show=True) _('Failed to process')+': '+unicode(job.description),
det_msg=job.console_text()).exec_()
def initialize_database(self): def initialize_database(self):

View File

@ -42,7 +42,7 @@
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>16777215</width> <width>16777215</width>
<height>100</height> <height>75</height>
</size> </size>
</property> </property>
<property name="verticalScrollBarPolicy"> <property name="verticalScrollBarPolicy">

View File

@ -7,6 +7,7 @@ from PyQt4.Qt import QMainWindow, QString, Qt, QFont, QCoreApplication, SIGNAL,\
QAction, QMenu, QMenuBar, QIcon QAction, QMenu, QMenuBar, QIcon
from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog
from calibre.utils.config import OptionParser from calibre.utils.config import OptionParser
from calibre.gui2 import error_dialog
def option_parser(usage='''\ def option_parser(usage='''\
Usage: %prog [options] Usage: %prog [options]
@ -79,9 +80,8 @@ class MainWindow(QMainWindow):
traceback.print_exception(type, value, tb, file=sio) traceback.print_exception(type, value, tb, file=sio)
fe = sio.getvalue() fe = sio.getvalue()
print >>sys.stderr, fe print >>sys.stderr, fe
msg = '<p><b>' + unicode(str(value), 'utf8', 'replace') + '</b></p>' msg = unicode(str(value), 'utf8', 'replace')
msg += '<p>Detailed <b>traceback</b>:<pre>'+fe+'</pre>' error_dialog(self, _('ERROR: Unhandled exception'), msg, det_msg=fe,
d = ConversionErrorDialog(self, _('ERROR: Unhandled exception'), msg) show=True)
d.exec_()
except: except:
pass pass

View File

@ -7,16 +7,16 @@ __docformat__ = 'restructuredtext en'
Logic for setting up conversion jobs Logic for setting up conversion jobs
''' '''
import cPickle, os import cPickle
from PyQt4.Qt import QDialog from PyQt4.Qt import QDialog
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
from calibre.gui2 import warning_dialog from calibre.gui2 import warning_dialog
from calibre.gui2.convert import load_specifics
from calibre.gui2.convert.single import NoSupportedInputFormats from calibre.gui2.convert.single import NoSupportedInputFormats
from calibre.gui2.convert.single import Config as SingleConfig from calibre.gui2.convert.single import Config as SingleConfig
from calibre.gui2.convert.bulk import BulkConfig from calibre.gui2.convert.bulk import BulkConfig
from calibre.utils.config import prefs
def convert_single_ebook(parent, db, book_ids, auto_conversion=False, out_format=None): def convert_single_ebook(parent, db, book_ids, auto_conversion=False, out_format=None):
changed = False changed = False
@ -126,33 +126,18 @@ def convert_bulk_ebook(parent, db, book_ids, out_format=None):
return jobs, changed, bad return jobs, changed, bad
def _fetch_news(data, fmt):
pt = PersistentTemporaryFile(suffix='_feeds2%s.%s'%(fmt.lower(), fmt.lower()))
pt.close()
args = ['feeds2%s'%fmt.lower(), '--output', pt.name, '--debug']
if data['username']:
args.extend(['--username', data['username']])
if data['password']:
args.extend(['--password', data['password']])
args.append(data['script'] if data['script'] else data['title'])
return 'fconvert_bulk_ebookseeds2'+fmt.lower(), [args], _('Fetch news from ')+data['title'], fmt.upper(), [pt]
def fetch_scheduled_recipe(recipe, script): def fetch_scheduled_recipe(recipe, script):
from calibre.gui2.dialogs.scheduler import config from calibre.gui2.dialogs.scheduler import config
fmt = prefs['output_format'].lower() fmt = prefs['output_format'].lower()
pt = PersistentTemporaryFile(suffix='_feeds2%s.%s'%(fmt.lower(), fmt.lower())) pt = PersistentTemporaryFile(suffix='_recipe_out.%s'%fmt.lower())
pt.close() pt.close()
args = ['feeds2%s'%fmt.lower(), '--output', pt.name, '--debug'] args = ['ebook-convert', script, pt.name, '-vv']
if recipe.needs_subscription: if recipe.needs_subscription:
x = config.get('recipe_account_info_%s'%recipe.id, False) x = config.get('recipe_account_info_%s'%recipe.id, False)
if not x: if not x:
raise ValueError(_('You must set a username and password for %s')%recipe.title) raise ValueError(_('You must set a username and password for %s')%recipe.title)
args.extend(['--username', x[0], '--password', x[1]]) args.extend(['--username', x[0], '--password', x[1]])
args.append(script)
return 'feeds2'+fmt, [args], _('Fetch news from ')+recipe.title, fmt.upper(), [pt]
def fetch_news(data): return 'ebook-convert', [args], _('Fetch news from ')+recipe.title, fmt.upper(), [pt]
fmt = prefs['output_format'].lower()
return _fetch_news(data, fmt)

View File

@ -19,7 +19,6 @@ from calibre.gui2 import Application, ORG_NAME, APP_UID, choose_files, \
info_dialog, error_dialog info_dialog, error_dialog
from calibre.ebooks.oeb.iterator import EbookIterator from calibre.ebooks.oeb.iterator import EbookIterator
from calibre.ebooks import DRMError from calibre.ebooks import DRMError
from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog
from calibre.constants import islinux from calibre.constants import islinux
from calibre.utils.config import Config, StringConfig from calibre.utils.config import Config, StringConfig
from calibre.gui2.library import SearchBox from calibre.gui2.library import SearchBox
@ -543,8 +542,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
if isinstance(worker.exception, DRMError): if isinstance(worker.exception, DRMError):
error_dialog(self, _('DRM Error'), _('<p>This book is protected by <a href="%s">DRM</a>')%'http://wiki.mobileread.com/wiki/DRM').exec_() error_dialog(self, _('DRM Error'), _('<p>This book is protected by <a href="%s">DRM</a>')%'http://wiki.mobileread.com/wiki/DRM').exec_()
else: else:
ConversionErrorDialog(self, _('Could not open ebook'), error_dialog(self, _('Could not open ebook'),
_('<b>%s</b><br/><p>%s</p>')%(worker.exception, worker.traceback.replace('\n', '<br>')), show=True) unicode(worker.exception), det_msg=worker.traceback, show=True)
self.close_progress_indicator() self.close_progress_indicator()
else: else:
self.metadata.show_opf(self.iterator.opf) self.metadata.show_opf(self.iterator.opf)

View File

@ -18,9 +18,13 @@ class RecipeInput(InputFormatPlugin):
file_types = set(['recipe']) file_types = set(['recipe'])
recommendations = set([ recommendations = set([
('chapter_mark', 'none', OptionRecommendation.HIGH), ('chapter', None, OptionRecommendation.HIGH),
('dont_split_on_page_breaks', True, OptionRecommendation.HIGH), ('dont_split_on_page_breaks', True, OptionRecommendation.HIGH),
('use_auto_toc', False, OptionRecommendation.HIGH), ('use_auto_toc', False, OptionRecommendation.HIGH),
('input_encoding', None, OptionRecommendation.HIGH),
('input_profile', 'default', OptionRecommendation.HIGH),
('page_breaks_before', None, OptionRecommendation.HIGH),
('insert_metadata', False, OptionRecommendation.HIGH),
]) ])
options = set([ options = set([

View File

@ -231,23 +231,23 @@ class BasicNewsRecipe(Recipe):
#: use :member:`extra_css` in your recipe to customize look and feel. #: use :member:`extra_css` in your recipe to customize look and feel.
template_css = u''' template_css = u'''
.article_date { .article_date {
font-size: x-small; color: gray; font-family: monospace; color: gray; font-family: monospace;
} }
.article_description { .article_description {
font-size: small; font-family: sans; text-indent: 0pt; font-family: sans; text-indent: 0pt;
} }
a.article { a.article {
font-weight: bold; font-size: large; font-weight: bold;
} }
a.feed { a.feed {
font-weight: bold; font-size: large; font-weight: bold;
} }
.navbar { .navbar {
font-family:monospace; font-size:8pt font-family:monospace;
} }
''' '''
@ -579,8 +579,9 @@ class BasicNewsRecipe(Recipe):
def feeds2index(self, feeds): def feeds2index(self, feeds):
templ = templates.IndexTemplate() templ = templates.IndexTemplate()
css = self.template_css + '\n\n' +(self.extra_css if self.extra_css else '')
return templ.generate(self.title, self.timefmt, feeds, return templ.generate(self.title, self.timefmt, feeds,
extra_css=self.extra_css).render(doctype='xhtml') extra_css=css).render(doctype='xhtml')
@classmethod @classmethod
def description_limiter(cls, src): def description_limiter(cls, src):
@ -631,8 +632,9 @@ class BasicNewsRecipe(Recipe):
templ = templates.FeedTemplate() templ = templates.FeedTemplate()
css = self.template_css + '\n\n' +(self.extra_css if self.extra_css else '')
return templ.generate(feed, self.description_limiter, return templ.generate(feed, self.description_limiter,
extra_css=self.extra_css).render(doctype='xhtml') extra_css=css).render(doctype='xhtml')
def _fetch_article(self, url, dir, f, a, num_of_feeds): def _fetch_article(self, url, dir, f, a, num_of_feeds):

View File

@ -10,6 +10,7 @@ class DNAIndia(BasicNewsRecipe):
description = 'Mumbai news, India news, World news, breaking news' description = 'Mumbai news, India news, World news, breaking news'
__author__ = 'Kovid Goyal' __author__ = 'Kovid Goyal'
language = _('English') language = _('English')
encoding = 'cp1252'
feeds = [ feeds = [
('Top News', 'http://www.dnaindia.com/syndication/rss_topnews.xml'), ('Top News', 'http://www.dnaindia.com/syndication/rss_topnews.xml'),

View File

@ -5,7 +5,6 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import re, string, time import re, string, time
from calibre import strftime from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import BeautifulSoup
class Newsweek(BasicNewsRecipe): class Newsweek(BasicNewsRecipe):
@ -15,7 +14,12 @@ class Newsweek(BasicNewsRecipe):
no_stylesheets = True no_stylesheets = True
language = _('English') language = _('English')
extra_css = '#content { font:serif 12pt; }\n.story {font:12pt}\n.HorizontalHeader {font:18pt}\n.deck {font:16pt}' extra_css = '''
#content { font-size:normal; font-family: serif }
.story { font-size:normal }
.HorizontalHeader {font-size:xx-large}
.deck {font-size:x-large}
'''
keep_only_tags = [dict(name='div', id='content')] keep_only_tags = [dict(name='div', id='content')]
remove_tags = [ remove_tags = [

View File

@ -38,7 +38,7 @@ class NavBarTemplate(Template):
</style> </style>
</head> </head>
<body> <body>
<div class="navbar" style="text-align:${'center' if center else 'left'};"> <div class="navbar calibre_rescale_70" style="text-align:${'center' if center else 'left'};">
<hr py:if="bottom" /> <hr py:if="bottom" />
<p py:if="bottom" style="text-align:left"> <p py:if="bottom" style="text-align:left">
This article was downloaded by <b>${__appname__}</b> from <a href="${url}">${url}</a> This article was downloaded by <b>${__appname__}</b> from <a href="${url}">${url}</a>
@ -99,15 +99,17 @@ class IndexTemplate(Template):
</style> </style>
</head> </head>
<body> <body>
<h1 class="calibre_recipe_title">${title}</h1> <div class="calibre_rescale_100">
<h1 class="calibre_recipe_title calibre_rescale_180">${title}</h1>
<p style="text-align:right">${date}</p> <p style="text-align:right">${date}</p>
<ul class="calibre_feed_list"> <ul class="calibre_feed_list">
<py:for each="i, feed in enumerate(feeds)"> <py:for each="i, feed in enumerate(feeds)">
<li py:if="feed" id="feed_${str(i)}"> <li py:if="feed" id="feed_${str(i)}">
<a class="feed" href="${'feed_%d/index.html'%i}">${feed.title}</a> <a class="feed calibre_rescale_120" href="${'feed_%d/index.html'%i}">${feed.title}</a>
</li> </li>
</py:for> </py:for>
</ul> </ul>
</div>
</body> </body>
</html> </html>
''') ''')
@ -143,29 +145,32 @@ class FeedTemplate(Template):
</style> </style>
</head> </head>
<body style="page-break-before:always"> <body style="page-break-before:always">
<h2 class="calibre_feed_title">${feed.title}</h2> <div class="calibre_rescale_100">
<h2 class="calibre_feed_title calibre_rescale_160">${feed.title}</h2>
<py:if test="getattr(feed, 'image', None)"> <py:if test="getattr(feed, 'image', None)">
<div class="calibre_feed_image"> <div class="calibre_feed_image">
<img alt="${feed.image_alt}" src="${feed.image_url}" /> <img alt="${feed.image_alt}" src="${feed.image_url}" />
</div> </div>
</py:if> </py:if>
<div class="calibre_feed_description" py:if="getattr(feed, 'description', None)"> <div class="calibre_feed_description calibre_rescale_80" py:if="getattr(feed, 'description', None)">
${feed.description}<br /> ${feed.description}<br />
</div> </div>
<ul class="calibre_article_list"> <ul class="calibre_article_list">
<py:for each="i, article in enumerate(feed.articles)"> <py:for each="i, article in enumerate(feed.articles)">
<li id="${'article_%d'%i}" py:if="getattr(article, 'downloaded', False)" style="padding-bottom:0.5em"> <li id="${'article_%d'%i}" py:if="getattr(article, 'downloaded',
<a class="article" href="${article.url}">${article.title}</a> False)" style="padding-bottom:0.5em" class="calibre_rescale_100">
<a class="article calibre_rescale_120" href="${article.url}">${article.title}</a>
<span class="article_date">${article.localtime.strftime(" [%a, %d %b %H:%M]")}</span> <span class="article_date">${article.localtime.strftime(" [%a, %d %b %H:%M]")}</span>
<div class="article_decription" py:if="article.summary"> <div class="article_decription calibre_rescale_70" py:if="article.summary">
${Markup(cutoff(article.text_summary))} ${Markup(cutoff(article.text_summary))}
</div> </div>
</li> </li>
</py:for> </py:for>
</ul> </ul>
<div class="navbar" style="text-align:center; font-family:monospace; font-size:8pt"> <div class="navbar calibre_rescale_70">
| <a href="../index.html">Up one level</a> | | <a href="../index.html">Up one level</a> |
</div> </div>
</div>
</body> </body>
</html> </html>
''') ''')

View File

@ -53,9 +53,15 @@ def save_soup(soup, target):
ns = BeautifulSoup('<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />') ns = BeautifulSoup('<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />')
nm = ns.find('meta') nm = ns.find('meta')
metas = soup.findAll('meta', content=True) metas = soup.findAll('meta', content=True)
added = False
for meta in metas: for meta in metas:
if 'charset' in meta.get('content', '').lower(): if 'charset' in meta.get('content', '').lower():
meta.replaceWith(nm) meta.replaceWith(nm)
added = True
if not added:
head = soup.find('head')
if head is not None:
head.insert(0, nm)
selfdir = os.path.dirname(target) selfdir = os.path.dirname(target)
@ -67,6 +73,7 @@ def save_soup(soup, target):
html = unicode(soup) html = unicode(soup)
with open(target, 'wb') as f: with open(target, 'wb') as f:
idx = html.find('hoping')
f.write(html.encode('utf-8')) f.write(html.encode('utf-8'))
class response(str): class response(str):