Merge from trunk

This commit is contained in:
Charles Haley 2010-12-15 14:33:16 +00:00
commit 1617ffb510
20 changed files with 181 additions and 74 deletions

View File

@ -4,6 +4,7 @@ __copyright__ = '2008-2010, Darko Miletic <darko.miletic at gmail.com>'
economictimes.indiatimes.com economictimes.indiatimes.com
''' '''
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class TheEconomicTimes(BasicNewsRecipe): class TheEconomicTimes(BasicNewsRecipe):
@ -32,18 +33,17 @@ class TheEconomicTimes(BasicNewsRecipe):
, 'language' : language , 'language' : language
} }
keep_only_tags = [dict(attrs={'class':'printdiv'})]
remove_tags = [dict(name=['object','link','embed','iframe','base','table','meta'])]
remove_attributes = ['name']
remove_tags_before = dict(name='h1')
feeds = [(u'All articles', u'http://economictimes.indiatimes.com/rssfeedsdefault.cms')] feeds = [(u'All articles', u'http://economictimes.indiatimes.com/rssfeedsdefault.cms')]
def print_version(self, url): def print_version(self, url):
rest, sep, art = url.rpartition('/articleshow/') rest, sep, art = url.rpartition('/articleshow/')
return 'http://m.economictimes.com/PDAET/articleshow/' + art
return 'http://economictimes.indiatimes.com/articleshow/' + art + '?prtpage=1' return 'http://economictimes.indiatimes.com/articleshow/' + art + '?prtpage=1'
def get_article_url(self, article): def get_article_url(self, article):
rurl = article.get('link', None) rurl = article.get('guid', None)
if (rurl.find('/quickieslist/') > 0) or (rurl.find('/quickiearticleshow/') > 0): if (rurl.find('/quickieslist/') > 0) or (rurl.find('/quickiearticleshow/') > 0):
return None return None
return rurl return rurl

View File

@ -8,9 +8,10 @@ class TimesOfIndia(BasicNewsRecipe):
max_articles_per_feed = 25 max_articles_per_feed = 25
no_stylesheets = True no_stylesheets = True
keep_only_tags = [dict(attrs={'class':'prttabl'})] keep_only_tags = [dict(attrs={'class':'maintable12'})]
remove_tags = [ remove_tags = [
dict(style=lambda x: x and 'float' in x) dict(style=lambda x: x and 'float' in x),
dict(attrs={'class':'prvnxtbg'}),
] ]
feeds = [ feeds = [

View File

@ -101,6 +101,13 @@ class EPUBOutput(OutputFormatPlugin):
) )
), ),
OptionRecommendation(name='epub_flatten', recommended_value=False,
help=_('This option is needed only if you intend to use the EPUB'
' with FBReaderJ. It will flatten the file system inside the'
' EPUB, putting all files into the top level.')
),
]) ])
recommendations = set([('pretty_print', True, OptionRecommendation.HIGH)]) recommendations = set([('pretty_print', True, OptionRecommendation.HIGH)])
@ -142,8 +149,12 @@ class EPUBOutput(OutputFormatPlugin):
def convert(self, oeb, output_path, input_plugin, opts, log): def convert(self, oeb, output_path, input_plugin, opts, log):
self.log, self.opts, self.oeb = log, opts, oeb self.log, self.opts, self.oeb = log, opts, oeb
#from calibre.ebooks.oeb.transforms.filenames import UniqueFilenames if self.opts.epub_flatten:
#UniqueFilenames()(oeb, opts) from calibre.ebooks.oeb.transforms.filenames import FlatFilenames
FlatFilenames()(oeb, opts)
else:
from calibre.ebooks.oeb.transforms.filenames import UniqueFilenames
UniqueFilenames()(oeb, opts)
self.workaround_ade_quirks() self.workaround_ade_quirks()
self.workaround_webkit_quirks() self.workaround_webkit_quirks()

View File

@ -276,12 +276,13 @@ def result_index(source, result):
return -1 return -1
def merge_results(one, two): def merge_results(one, two):
for x in two: if two is not None and one is not None:
idx = result_index(one, x) for x in two:
if idx < 0: idx = result_index(one, x)
one.append(x) if idx < 0:
else: one.append(x)
one[idx].smart_update(x) else:
one[idx].smart_update(x)
class MetadataSources(object): class MetadataSources(object):
@ -337,7 +338,7 @@ def search(title=None, author=None, publisher=None, isbn=None, isbndb_key=None,
manager(title, author, publisher, isbn, verbose) manager(title, author, publisher, isbn, verbose)
manager.join() manager.join()
results = list(fetchers[0].results) results = list(fetchers[0].results) if fetchers else []
for fetcher in fetchers[1:]: for fetcher in fetchers[1:]:
merge_results(results, fetcher.results) merge_results(results, fetcher.results)

View File

@ -29,6 +29,9 @@ from calibre.ebooks.metadata import MetaInformation
from calibre.ebooks.metadata.opf2 import OPFCreator, OPF from calibre.ebooks.metadata.opf2 import OPFCreator, OPF
from calibre.ebooks.metadata.toc import TOC from calibre.ebooks.metadata.toc import TOC
class TopazError(ValueError):
pass
class EXTHHeader(object): class EXTHHeader(object):
def __init__(self, raw, codec, title): def __init__(self, raw, codec, title):
@ -239,7 +242,7 @@ class MobiReader(object):
self.base_css_rules = textwrap.dedent(''' self.base_css_rules = textwrap.dedent('''
blockquote { margin: 0em 0em 0em 2em; text-align: justify } blockquote { margin: 0em 0em 0em 2em; text-align: justify }
p { margin: 0em; text-align: justify } p { margin: 0em; text-align: justify; text-indent: 1.5em }
.bold { font-weight: bold } .bold { font-weight: bold }
@ -259,7 +262,7 @@ class MobiReader(object):
raw = stream.read() raw = stream.read()
if raw.startswith('TPZ'): if raw.startswith('TPZ'):
raise ValueError(_('This is an Amazon Topaz book. It cannot be processed.')) raise TopazError(_('This is an Amazon Topaz book. It cannot be processed.'))
self.header = raw[0:72] self.header = raw[0:72]
self.name = self.header[:32].replace('\x00', '') self.name = self.header[:32].replace('\x00', '')
@ -832,6 +835,15 @@ class MobiReader(object):
im.save(open(path, 'wb'), format='JPEG') im.save(open(path, 'wb'), format='JPEG')
def get_metadata(stream): def get_metadata(stream):
stream.seek(0)
try:
raw = stream.read(3)
except:
raw = ''
stream.seek(0)
if raw == 'TPZ':
from calibre.ebooks.metadata.topaz import get_metadata
return get_metadata(stream)
from calibre.utils.logging import Log from calibre.utils.logging import Log
log = Log() log = Log()
mi = MetaInformation(os.path.basename(stream.name), [_('Unknown')]) mi = MetaInformation(os.path.basename(stream.name), [_('Unknown')])
@ -861,7 +873,10 @@ def get_metadata(stream):
cover_index = mh.first_image_index + mh.exth.cover_offset cover_index = mh.first_image_index + mh.exth.cover_offset
data = mh.section_data(int(cover_index)) data = mh.section_data(int(cover_index))
else: else:
data = mh.section_data(mh.first_image_index) try:
data = mh.section_data(mh.first_image_index)
except:
data = ''
buf = cStringIO.StringIO(data) buf = cStringIO.StringIO(data)
try: try:
im = PILImage.open(buf) im = PILImage.open(buf)

View File

@ -13,15 +13,16 @@ import cssutils
from calibre.ebooks.oeb.base import rewrite_links, urlnormalize from calibre.ebooks.oeb.base import rewrite_links, urlnormalize
class RenameFiles(object): class RenameFiles(object): # {{{
''' '''
Rename files and adjust all links pointing to them. Note that the spine Rename files and adjust all links pointing to them. Note that the spine
and manifest are not touched by this transform. and manifest are not touched by this transform.
''' '''
def __init__(self, rename_map): def __init__(self, rename_map, renamed_items_map = None):
self.rename_map = rename_map self.rename_map = rename_map
self.renamed_items_map = renamed_items_map
def __call__(self, oeb, opts): def __call__(self, oeb, opts):
self.log = oeb.logger self.log = oeb.logger
@ -49,7 +50,6 @@ class RenameFiles(object):
if self.oeb.toc: if self.oeb.toc:
self.fix_toc_entry(self.oeb.toc) self.fix_toc_entry(self.oeb.toc)
def fix_toc_entry(self, toc): def fix_toc_entry(self, toc):
if toc.href: if toc.href:
href = urlnormalize(toc.href) href = urlnormalize(toc.href)
@ -66,18 +66,22 @@ class RenameFiles(object):
self.fix_toc_entry(x) self.fix_toc_entry(x)
def url_replacer(self, orig_url): def url_replacer(self, orig_url):
url = urlnormalize(orig_url) url = urlnormalize(orig_url)
path, frag = urldefrag(url) path, frag = urldefrag(url)
href = self.current_item.abshref(path) if self.renamed_items_map:
replacement = self.rename_map.get(href, None) orig_item = self.renamed_items_map.get(self.current_item.href, self.current_item)
if replacement is None: else:
return orig_url orig_item = self.current_item
replacement = self.current_item.relhref(replacement)
if frag:
replacement += '#' + frag
return replacement
class UniqueFilenames(object): href = orig_item.abshref(path)
replacement = self.current_item.relhref(self.rename_map.get(href, href))
if frag:
replacement += '#' + frag
return replacement
# }}}
class UniqueFilenames(object): # {{{
'Ensure that every item in the manifest has a unique filename' 'Ensure that every item in the manifest has a unique filename'
@ -127,4 +131,48 @@ class UniqueFilenames(object):
candidate = base + suffix + ext candidate = base + suffix + ext
if candidate not in self.seen_filenames: if candidate not in self.seen_filenames:
return suffix return suffix
# }}}
class FlatFilenames(object): # {{{
'Ensure that every item in the manifest has a unique filename without subdirectories.'
def __call__(self, oeb, opts):
self.log = oeb.logger
self.opts = opts
self.oeb = oeb
self.rename_map = {}
self.renamed_items_map = {}
for item in list(oeb.manifest.items):
# Flatten URL by removing directories.
# Example: a/b/c/index.html -> a_b_c_index.html
nhref = item.href.replace("/", "_")
if item.href == nhref:
# URL hasn't changed, skip item.
continue
data = item.data
nhref = oeb.manifest.generate(href=nhref)[1]
nitem = oeb.manifest.add(item.id, nhref, item.media_type, data=data,
fallback=item.fallback)
self.rename_map[item.href] = nhref
self.renamed_items_map[nhref] = item
if item.spine_position is not None:
oeb.spine.insert(item.spine_position, nitem, item.linear)
oeb.spine.remove(item)
oeb.manifest.remove(item)
if self.rename_map:
self.log('Found non-flat filenames, renaming to support broken'
' EPUB readers like FBReader...')
from pprint import pformat
self.log.debug(pformat(self.rename_map))
self.log.debug(pformat(self.renamed_items_map))
renamer = RenameFiles(self.rename_map, self.renamed_items_map)
renamer(oeb, opts)
# }}}

View File

@ -120,6 +120,7 @@ class AddAction(InterfaceAction):
if self.gui.current_view() is not self.gui.library_view: if self.gui.current_view() is not self.gui.library_view:
return return
db = self.gui.library_view.model().db db = self.gui.library_view.model().db
cover_changed = False
current_idx = self.gui.library_view.currentIndex() current_idx = self.gui.library_view.currentIndex()
if not current_idx.isValid(): return if not current_idx.isValid(): return
cid = db.id(current_idx.row()) cid = db.id(current_idx.row())
@ -133,12 +134,16 @@ class AddAction(InterfaceAction):
if not pmap.isNull(): if not pmap.isNull():
accept = True accept = True
db.set_cover(cid, pmap) db.set_cover(cid, pmap)
cover_changed = True
elif ext in BOOK_EXTENSIONS: elif ext in BOOK_EXTENSIONS:
db.add_format_with_hooks(cid, ext, path, index_is_id=True) db.add_format_with_hooks(cid, ext, path, index_is_id=True)
accept = True accept = True
if accept: if accept:
event.accept() event.accept()
self.gui.library_view.model().current_changed(current_idx, current_idx) self.gui.library_view.model().current_changed(current_idx, current_idx)
if cover_changed:
if self.gui.cover_flow:
self.gui.cover_flow.dataChanged()
def __add_filesystem_book(self, paths, allow_device=True): def __add_filesystem_book(self, paths, allow_device=True):
if isinstance(paths, basestring): if isinstance(paths, basestring):

View File

@ -253,7 +253,12 @@ class BookInfo(QWebView):
% (left_pane, right_pane))) % (left_pane, right_pane)))
def mouseDoubleClickEvent(self, ev): def mouseDoubleClickEvent(self, ev):
ev.ignore() if self.width() - ev.x() < 25 or \
self.height() - ev.y() < 25:
# Filter out double clicks on the scroll bar
ev.accept()
else:
ev.ignore()
# }}} # }}}

View File

@ -21,7 +21,7 @@ class PluginWidget(Widget, Ui_Form):
Widget.__init__(self, parent, Widget.__init__(self, parent,
['dont_split_on_page_breaks', 'flow_size', ['dont_split_on_page_breaks', 'flow_size',
'no_default_epub_cover', 'no_svg_cover', 'no_default_epub_cover', 'no_svg_cover',
'preserve_cover_aspect_ratio',] 'preserve_cover_aspect_ratio', 'epub_flatten']
) )
for i in range(2): for i in range(2):
self.opt_no_svg_cover.toggle() self.opt_no_svg_cover.toggle()

View File

@ -81,6 +81,13 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="1" column="1">
<widget class="QCheckBox" name="opt_epub_flatten">
<property name="text">
<string>&amp;Flatten EPUB file structure</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>

View File

@ -23,10 +23,6 @@ class BookInfo(QDialog, Ui_BookInfo):
self.comments.sizeHint = self.comments_size_hint self.comments.sizeHint = self.comments_size_hint
self.view_func = view_func self.view_func = view_func
desktop = QCoreApplication.instance().desktop()
screen_height = desktop.availableGeometry().height() - 100
self.resize(self.size().width(), screen_height)
self.view = view self.view = view
self.current_row = None self.current_row = None
@ -40,8 +36,13 @@ class BookInfo(QDialog, Ui_BookInfo):
self.fit_cover.stateChanged.connect(self.toggle_cover_fit) self.fit_cover.stateChanged.connect(self.toggle_cover_fit)
self.cover.resizeEvent = self.cover_view_resized self.cover.resizeEvent = self.cover_view_resized
desktop = QCoreApplication.instance().desktop()
screen_height = desktop.availableGeometry().height() - 100
self.resize(self.size().width(), screen_height)
def comments_size_hint(self): def comments_size_hint(self):
return QSize(350, 350) return QSize(350, 250)
def toggle_cover_fit(self, state): def toggle_cover_fit(self, state):
dynamic.set('book_info_dialog_fit_cover', self.fit_cover.isChecked()) dynamic.set('book_info_dialog_fit_cover', self.fit_cover.isChecked())

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>917</width> <width>917</width>
<height>783</height> <height>480</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">

View File

@ -399,14 +399,11 @@ Future conversion of these books will use the default settings.</string>
<property name="title"> <property name="title">
<string>Change &amp;cover</string> <string>Change &amp;cover</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<widget class="QRadioButton" name="cover_no_change"> <widget class="QRadioButton" name="cover_generate">
<property name="text"> <property name="text">
<string>&amp;No change</string> <string>&amp;Generate default cover</string>
</property>
<property name="checked">
<bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
@ -417,13 +414,6 @@ Future conversion of these books will use the default settings.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QRadioButton" name="cover_generate">
<property name="text">
<string>&amp;Generate default cover</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View File

@ -105,7 +105,8 @@ class BooksView(QTableView): # {{{
hv.setCursor(Qt.PointingHandCursor) hv.setCursor(Qt.PointingHandCursor)
self.selected_ids = [] self.selected_ids = []
self._model.about_to_be_sorted.connect(self.about_to_be_sorted) self._model.about_to_be_sorted.connect(self.about_to_be_sorted)
self._model.sorting_done.connect(self.sorting_done) self._model.sorting_done.connect(self.sorting_done,
type=Qt.QueuedConnection)
# Column Header Context Menu {{{ # Column Header Context Menu {{{
def column_header_context_handler(self, action=None, column=None): def column_header_context_handler(self, action=None, column=None):
@ -227,6 +228,7 @@ class BooksView(QTableView): # {{{
sm = self.selectionModel() sm = self.selectionModel()
for idx in indices: for idx in indices:
sm.select(idx, sm.Select|sm.Rows) sm.select(idx, sm.Select|sm.Rows)
self.scroll_to_row(indices[0].row())
self.selected_ids = [] self.selected_ids = []
# }}} # }}}

View File

@ -88,7 +88,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
name = unicode(fi.family()) name = unicode(fi.family())
self.font_display.setFont(font) self.font_display.setFont(font)
self.font_display.setText(_('Current font:') + ' ' + name + self.font_display.setText(name +
' [%dpt]'%fi.pointSize()) ' [%dpt]'%fi.pointSize())
def change_font(self, *args): def change_font(self, *args):

View File

@ -183,6 +183,34 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="8" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Interface font:</string>
</property>
<property name="buddy">
<cstring>font_display</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="font_display">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="8" column="1">
<widget class="QPushButton" name="change_font_button">
<property name="text">
<string>Change &amp;font (needs restart)</string>
</property>
</widget>
</item>
<item row="9" column="0" colspan="2"> <item row="9" column="0" colspan="2">
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">
@ -196,20 +224,6 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="8" column="0">
<widget class="QLineEdit" name="font_display">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QPushButton" name="change_font_button">
<property name="text">
<string>Change &amp;font (needs restart)</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>

View File

@ -236,6 +236,10 @@ def fetch_scheduled_recipe(arg):
recs.append(('header', True, OptionRecommendation.HIGH)) recs.append(('header', True, OptionRecommendation.HIGH))
recs.append(('header_format', '%t', OptionRecommendation.HIGH)) recs.append(('header_format', '%t', OptionRecommendation.HIGH))
epub = load_defaults('epub_output')
if epub.get('epub_flatten', False):
recs.append(('epub_flatten', True, OptionRecommendation.HIGH))
args = [arg['recipe'], pt.name, recs] args = [arg['recipe'], pt.name, recs]
if arg['username'] is not None: if arg['username'] is not None:
recs.append(('username', arg['username'], OptionRecommendation.HIGH)) recs.append(('username', arg['username'], OptionRecommendation.HIGH))

View File

@ -51,7 +51,7 @@ def config(defaults=None):
c.add_opt('hyphenate_default_lang', default='en', c.add_opt('hyphenate_default_lang', default='en',
help=_('Default language for hyphenation rules')) help=_('Default language for hyphenation rules'))
c.add_opt('remember_current_page', default=True, c.add_opt('remember_current_page', default=True,
help=_('Save the current position in the documentwhen quitting')) help=_('Save the current position in the document, when quitting'))
fonts = c.add_group('FONTS', _('Font options')) fonts = c.add_group('FONTS', _('Font options'))
fonts('serif_family', default='Times New Roman' if iswindows else 'Liberation Serif', fonts('serif_family', default='Times New Roman' if iswindows else 'Liberation Serif',

View File

@ -1153,6 +1153,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
else: else:
vals = book[dex].split(mult) vals = book[dex].split(mult)
for val in vals: for val in vals:
if not val: continue
try: try:
(item_id, sort_val) = tids[cat][val] # let exceptions fly (item_id, sort_val) = tids[cat][val] # let exceptions fly
item = tcategories[cat].get(val, None) item = tcategories[cat].get(val, None)

View File

@ -552,16 +552,18 @@ class BrowseServer(object):
ids = self.search_cache('search:"%s"'%which) ids = self.search_cache('search:"%s"'%which)
except: except:
raise cherrypy.HTTPError(404, 'Search: %r not understood'%which) raise cherrypy.HTTPError(404, 'Search: %r not understood'%which)
elif category == 'newest': all_ids = self.search_cache('')
ids = self.search_cache('') if category == 'newest':
ids = all_ids
hide_sort = 'true' hide_sort = 'true'
elif category == 'allbooks': elif category == 'allbooks':
ids = self.search_cache('') ids = all_ids
else: else:
q = category q = category
if q == 'news': if q == 'news':
q = 'tags' q = 'tags'
ids = self.db.get_books_for_category(q, cid) ids = self.db.get_books_for_category(q, cid)
ids = [x for x in ids if x in all_ids]
items = [self.db.data._data[x] for x in ids] items = [self.db.data._data[x] for x in ids]
if category == 'newest': if category == 'newest':