Sync to trunk.

This commit is contained in:
John Schember 2011-02-05 22:21:15 -05:00
commit 1990797e45
11 changed files with 217 additions and 282 deletions

View File

@ -55,7 +55,7 @@
- title: "Add search to the plugin preferences dialog"
bug fixes:
- title: "Fix a bug that could cause fiels to be lost when changing metadata on east asian windows installs if the title and/or author is very long."
- title: "Fix a bug that could cause files to be lost when changing metadata on east asian windows installs if the title and/or author is very long."
tickets: [8620]
- title: "Tag browser: Fix searching with items in a user category not owrking if the main category is hidden"
@ -88,7 +88,7 @@
- title: "Do not discard the result of a conversion if the user opens the edit metadata dialog while the conversion is running"
tickets: [8672]
- title: "CHM Input: When the chm file lacks a hhc, lookf for index.html instead"
- title: "CHM Input: When the chm file lacks a hhc, look for index.html instead"
tickets: [8688]
- title: "EPUB Input: Filter some invalid media types from the spine"

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 B

View File

@ -113,8 +113,8 @@ table.cbj_header tr.cbj_series {
/* display:none; */
}
table.cbj_header tr.cbj_pubdate {
/* Uncomment the next line to remove 'Published' from banner section */
table.cbj_header tr.cbj_pubdata {
/* Uncomment the next line to remove 'Published (year of publication)' from banner section */
/* display:none; */
}

View File

@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
__license__ = 'GPL v3'
__copyright__ = '2011, Attis <attis@attis.one.pl>'
__version__ = 'v. 0.1'
import re
from calibre.web.feeds.recipes import BasicNewsRecipe
class KopalniaWiedzy(BasicNewsRecipe):
title = u'Kopalnia Wiedzy'
publisher = u'Kopalnia Wiedzy'
description = u'Ciekawostki ze świata nauki i techniki'
encoding = 'utf-8'
__author__ = 'Attis'
language = 'pl'
oldest_article = 7
max_articles_per_feed = 100
INDEX = u'http://kopalniawiedzy.pl/'
remove_javascript = True
no_stylesheets = True
remove_tags = [{'name':'p', 'attrs': {'class': 'keywords'} }]
remove_tags_after = dict(attrs={'class':'ad-square'})
keep_only_tags = [dict(name="div", attrs={'id':'articleContent'})]
extra_css = '.topimage {margin-top: 30px}'
preprocess_regexps = [
(re.compile(u'<a .* rel="lightboxText" .*><img (.*)></a>'),
lambda match: '<img class="topimage" ' + match.group(1) + '>' ),
(re.compile(u'<br /><br />'),
lambda match: '<br\/>')
]
feeds = [
(u'Biologia', u'http://kopalniawiedzy.pl/wiadomosci_biologia.rss'),
(u'Medycyna', u'http://kopalniawiedzy.pl/wiadomosci_medycyna.rss'),
(u'Psychologia', u'http://kopalniawiedzy.pl/wiadomosci_psychologia.rss'),
(u'Technologie', u'http://kopalniawiedzy.pl/wiadomosci_technologie.rss'),
(u'Ciekawostki', u'http://kopalniawiedzy.pl/wiadomosci_ciekawostki.rss'),
(u'Artykuły', u'http://kopalniawiedzy.pl/artykuly.rss')
]
def is_link_wanted(self, url, tag):
return tag['class'] == 'next'
def remove_beyond(self, tag, next):
while tag is not None and getattr(tag, 'name', None) != 'body':
after = getattr(tag, next)
while after is not None:
ns = getattr(tag, next)
after.extract()
after = ns
tag = tag.parent
def append_page(self, soup, appendtag, position):
pager = soup.find('a',attrs={'class':'next'})
if pager:
nexturl = self.INDEX + pager['href']
soup2 = self.index_to_soup(nexturl)
texttag = soup2.find('div', attrs={'id':'articleContent'})
tag = texttag.find(attrs={'class':'pages'})
self.remove_beyond(tag, 'nextSibling')
newpos = len(texttag.contents)
self.append_page(soup2,texttag,newpos)
appendtag.insert(position,texttag)
def preprocess_html(self, soup):
self.append_page(soup, soup.body, 3)
for item in soup.findAll('div',attrs={'class':'pages'}):
item.extract()
for item in soup.findAll('p', attrs={'class':'wykop'}):
item.extract()
return soup

View File

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
__license__ = 'GPL v3'
__copyright__ = '2011, Attis <attis@attis.one.pl>'
__version__ = 'v. 0.1'
import re
from calibre.web.feeds.recipes import BasicNewsRecipe
class KorespondentPL(BasicNewsRecipe):
title = u'Korespondent.pl'
publisher = u'Korespondent.pl'
description = u'Centrum wolnorynkowe - serwis ludzi wolnych'
encoding = 'utf-8'
__author__ = 'Attis'
language = 'pl'
oldest_article = 15
max_articles_per_feed = 100
remove_javascript = True
no_stylesheets = True
keep_only_tags = [dict(name='div', attrs={'class':'publicystyka'})]
remove_tags = [{'name': 'meta'}, {'name':'div', 'attrs': {'class': 'zdjecie'} }]
extra_css = '.naglowek {font-size: small}\n .tytul {font-size: x-large; padding-bottom: 10px; padding-top: 30px} \n .external {font-size: small}'
preprocess_regexps = [
(re.compile(u'<a href="index\.php.*>(.*)</a>'),
lambda match: match.group(1) ),
(re.compile(u'<i>'),
lambda match:'<i class="external">' ),
(re.compile(u'<p></p>Więcej'),
lambda match:'Więcej' ),
(re.compile(u'target="_blank"'),
lambda match:'target="_blank" class="external"' ),
(re.compile(u'<p align="center">\nPoczytaj inne teksty w <a href="http://www.korespondent.pl">Serwisie wolnorynkowym Korespondent.pl</a>.*</body>', re.DOTALL|re.IGNORECASE),
lambda match: '</div></body>'),
]
feeds = [(u'Serwis informacyjny', u'http://korespondent.pl/rss.xml')]

View File

@ -109,7 +109,7 @@ def get_rating(rating, rchar, e_rchar):
def render_jacket(mi, output_profile,
alt_title=_('Unknown'), alt_tags=[], alt_comments='',
alt_publisher=('Unknown publisher')):
alt_publisher=('')):
css = P('jacket/stylesheet.css', data=True).decode('utf-8')
try:
@ -127,7 +127,7 @@ def render_jacket(mi, output_profile,
try:
publisher = mi.publisher if mi.publisher else alt_publisher
except:
publisher = _('Unknown publisher')
publisher = ''
try:
pubdate = strftime(u'%Y', mi.pubdate.timetuple())

View File

@ -6,157 +6,38 @@ __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from PyQt4.Qt import QLineEdit, QListView, QAbstractListModel, Qt, QTimer, \
QApplication, QPoint, QItemDelegate, QStyleOptionViewItem, \
QStyle, QEvent, pyqtSignal
from PyQt4.Qt import QLineEdit, QAbstractListModel, Qt, \
QApplication, QCompleter
from calibre.utils.config import tweaks
from calibre.utils.icu import sort_key, lower
from calibre.gui2 import NONE
from calibre.gui2.widgets import EnComboBox
class CompleterItemDelegate(QItemDelegate): # {{{
''' Renders the current item as thought it were selected '''
def __init__(self, view):
self.view = view
QItemDelegate.__init__(self, view)
def paint(self, p, opt, idx):
opt = QStyleOptionViewItem(opt)
opt.showDecorationSelected = True
if self.view.currentIndex() == idx:
opt.state |= QStyle.State_HasFocus
QItemDelegate.paint(self, p, opt, idx)
# }}}
class CompleteWindow(QListView): # {{{
'''
The completion popup. For keyboard and mouse handling see
:meth:`eventFilter`.
'''
#: This signal is emitted when the user selects one of the listed
#: completions, by mouse or keyboard
completion_selected = pyqtSignal(object)
def __init__(self, widget, model):
self.widget = widget
QListView.__init__(self)
self.setVisible(False)
self.setParent(None, Qt.Popup)
self.setAlternatingRowColors(True)
self.setFocusPolicy(Qt.NoFocus)
self._d = CompleterItemDelegate(self)
self.setItemDelegate(self._d)
self.setModel(model)
self.setFocusProxy(widget)
self.installEventFilter(self)
self.clicked.connect(self.do_selected)
self.entered.connect(self.do_entered)
self.setMouseTracking(True)
def do_entered(self, idx):
if idx.isValid():
self.setCurrentIndex(idx)
def do_selected(self, idx=None):
idx = self.currentIndex() if idx is None else idx
if idx.isValid():
data = unicode(self.model().data(idx, Qt.DisplayRole))
self.completion_selected.emit(data)
self.hide()
def eventFilter(self, o, e):
if o is not self:
return False
if e.type() == e.KeyPress:
key = e.key()
if key in (Qt.Key_Escape, Qt.Key_Backtab) or \
(key == Qt.Key_F4 and (e.modifiers() & Qt.AltModifier)):
self.hide()
return True
elif key in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab):
if key == Qt.Key_Tab and not self.currentIndex().isValid():
if self.model().rowCount() > 0:
self.setCurrentIndex(self.model().index(0))
self.do_selected()
return True
elif key in (Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp,
Qt.Key_PageDown):
return False
# Send key event to associated line edit
self.widget.eat_focus_out = False
try:
self.widget.event(e)
finally:
self.widget.eat_focus_out = True
if not self.widget.hasFocus():
# Line edit lost focus
self.hide()
if e.isAccepted():
# Line edit consumed event
return True
elif e.type() == e.MouseButtonPress:
# Hide popup if user clicks outside it, otherwise
# pass event to popup
if not self.underMouse():
self.hide()
return True
elif e.type() in (e.InputMethod, e.ShortcutOverride):
QApplication.sendEvent(self.widget, e)
return False # Do not filter this event
# }}}
class CompleteModel(QAbstractListModel):
def __init__(self, parent=None):
QAbstractListModel.__init__(self, parent)
self.sep = ','
self.space_before_sep = False
self.items = []
self.lowered_items = []
self.matches = []
def set_items(self, items):
items = [unicode(x.strip()) for x in items]
self.items = list(sorted(items, key=lambda x: sort_key(x)))
self.lowered_items = [lower(x) for x in self.items]
self.matches = []
self.reset()
def rowCount(self, *args):
return len(self.matches)
return len(self.items)
def data(self, index, role):
if role == Qt.DisplayRole:
r = index.row()
try:
return self.matches[r]
return self.items[r]
except IndexError:
pass
return NONE
def get_matches(self, prefix):
'''
Return all matches that (case insensitively) start with prefix
'''
prefix = lower(prefix)
ans = []
if prefix:
for i, test in enumerate(self.lowered_items):
if test.startswith(prefix):
ans.append(self.items[i])
return ans
def update_matches(self, matches):
self.matches = matches
self.reset()
class MultiCompleteLineEdit(QLineEdit):
'''
@ -170,16 +51,26 @@ class MultiCompleteLineEdit(QLineEdit):
'''
def __init__(self, parent=None):
self.eat_focus_out = True
self.max_visible_items = 7
self.current_prefix = None
QLineEdit.__init__(self, parent)
self.sep = ','
self.space_before_sep = False
self._model = CompleteModel(parent=self)
self.complete_window = CompleteWindow(self, self._model)
self._completer = c = QCompleter(self._model, self)
c.setWidget(self)
c.setCompletionMode(QCompleter.PopupCompletion)
c.setCaseSensitivity(Qt.CaseInsensitive)
c.setModelSorting(QCompleter.CaseInsensitivelySortedModel)
c.setCompletionRole(Qt.DisplayRole)
p = c.popup()
p.setMouseTracking(True)
p.entered.connect(self.item_entered)
c.popup().setAlternatingRowColors(True)
c.activated.connect(self.completion_selected,
type=Qt.QueuedConnection)
self.textEdited.connect(self.text_edited)
self.complete_window.completion_selected.connect(self.completion_selected)
self.installEventFilter(self)
# Interface {{{
def update_items_cache(self, complete_items):
@ -193,33 +84,23 @@ class MultiCompleteLineEdit(QLineEdit):
# }}}
def eventFilter(self, o, e):
if self.eat_focus_out and o is self and e.type() == QEvent.FocusOut:
if self.complete_window.isVisible():
return True # Filter this event since the cw is visible
return QLineEdit.eventFilter(self, o, e)
def hide_completion_window(self):
self.complete_window.hide()
def item_entered(self, idx):
self._completer.popup().setCurrentIndex(idx)
def text_edited(self, *args):
self.update_completions()
self._completer.complete()
def update_completions(self):
' Update the list of completions '
if not self.complete_window.isVisible() and not self.hasFocus():
return
cpos = self.cursorPosition()
text = unicode(self.text())
prefix = text[:cpos]
self.current_prefix = prefix
complete_prefix = prefix.lstrip()
if self.sep:
complete_prefix = prefix = prefix.split(self.sep)[-1].lstrip()
matches = self._model.get_matches(complete_prefix)
self.update_complete_window(matches)
complete_prefix = prefix.split(self.sep)[-1].lstrip()
self._completer.setCompletionPrefix(complete_prefix)
def get_completed_text(self, text):
'''
@ -242,11 +123,11 @@ class MultiCompleteLineEdit(QLineEdit):
else:
prefix_len = len(before_text.split(self.sep)[-1].lstrip())
completed_text = before_text[:cursor_pos - prefix_len] + text + after_text
return prefix_len, completed_text
return prefix_len, completed_text
def completion_selected(self, text):
prefix_len, ctext = self.get_completed_text(text)
prefix_len, ctext = self.get_completed_text(unicode(text))
if self.sep is None:
self.setText(ctext)
self.setCursorPosition(len(ctext))
@ -255,60 +136,6 @@ class MultiCompleteLineEdit(QLineEdit):
self.setText(ctext)
self.setCursorPosition(cursor_pos - prefix_len + len(text))
def update_complete_window(self, matches):
self._model.update_matches(matches)
if matches:
self.show_complete_window()
else:
self.complete_window.hide()
def position_complete_window(self):
popup = self.complete_window
screen = QApplication.desktop().availableGeometry(self)
h = (popup.sizeHintForRow(0) * min(self.max_visible_items,
popup.model().rowCount()) + 3) + 3
hsb = popup.horizontalScrollBar()
if hsb and hsb.isVisible():
h += hsb.sizeHint().height()
rh = self.height()
pos = self.mapToGlobal(QPoint(0, self.height() - 2))
w = self.width()
if w > screen.width():
w = screen.width()
if (pos.x() + w) > (screen.x() + screen.width()):
pos.setX(screen.x() + screen.width() - w)
if (pos.x() < screen.x()):
pos.setX(screen.x())
top = pos.y() - rh - screen.top() + 2
bottom = screen.bottom() - pos.y()
h = max(h, popup.minimumHeight())
if h > bottom:
h = min(max(top, bottom), h)
if top > bottom:
pos.setY(pos.y() - h - rh + 2)
popup.setGeometry(pos.x(), pos.y(), w, h)
def show_complete_window(self):
self.position_complete_window()
self.complete_window.show()
def moveEvent(self, ev):
ret = QLineEdit.moveEvent(self, ev)
QTimer.singleShot(0, self.position_complete_window)
return ret
def resizeEvent(self, ev):
ret = QLineEdit.resizeEvent(self, ev)
QTimer.singleShot(0, self.position_complete_window)
return ret
@dynamic_property
def all_items(self):
def fget(self):
@ -317,22 +144,6 @@ class MultiCompleteLineEdit(QLineEdit):
self._model.set_items(items)
return property(fget=fget, fset=fset)
@dynamic_property
def sep(self):
def fget(self):
return self._model.sep
def fset(self, val):
self._model.sep = val
return property(fget=fget, fset=fset)
@dynamic_property
def space_before_sep(self):
def fget(self):
return self._model.space_before_sep
def fset(self, val):
self._model.space_before_sep = val
return property(fget=fget, fset=fset)
class MultiCompleteComboBox(EnComboBox):
def __init__(self, *args):

View File

@ -11,7 +11,7 @@ from threading import Thread
from Queue import Queue, Empty
from functools import partial
from PyQt4.Qt import QObject, Qt, pyqtSignal, QTimer, QDialog, \
from PyQt4.Qt import QObject, QTimer, QDialog, \
QVBoxLayout, QTextBrowser, QLabel, QGroupBox, QDialogButtonBox
from calibre.ebooks.metadata.fetch import search, get_social_metadata
@ -163,27 +163,23 @@ class DownloadMetadata(Thread):
class DoDownload(QObject):
idle_process = pyqtSignal()
def __init__(self, parent, title, db, ids, get_covers, set_metadata=True,
get_social_metadata=True):
QObject.__init__(self, parent)
self.pd = ProgressDialog(title, min=0, max=0, parent=parent)
self.pd.canceled_signal.connect(self.cancel)
self.idle_process.connect(self.do_one, type=Qt.QueuedConnection)
self.downloader = None
self.create = partial(DownloadMetadata, db, ids, get_covers,
set_metadata=set_metadata,
get_social_metadata=get_social_metadata)
self.timer = QTimer(self)
self.get_covers = get_covers
self.timer.timeout.connect(self.do_one, type=Qt.QueuedConnection)
self.db = db
self.updated = set([])
self.total = len(ids)
self.keep_going = True
def exec_(self):
self.timer.start(50)
QTimer.singleShot(50, self.do_one)
ret = self.pd.exec_()
if getattr(self.downloader, 'exception', None) is not None and \
ret == self.pd.Accepted:
@ -194,30 +190,37 @@ class DoDownload(QObject):
return ret
def cancel(self, *args):
self.timer.stop()
self.keep_going = False
self.downloader.keep_going = False
self.pd.reject()
def do_one(self):
if self.downloader is None:
self.downloader = self.create()
self.downloader.start()
self.pd.set_min(0)
self.pd.set_max(self.downloader.total)
try:
r = self.downloader.results.get_nowait()
self.handle_result(r)
except Empty:
pass
if not self.downloader.is_alive():
self.timer.stop()
while True:
try:
r = self.downloader.results.get_nowait()
self.handle_result(r)
except Empty:
break
self.pd.accept()
if not self.keep_going:
return
if self.downloader is None:
self.downloader = self.create()
self.downloader.start()
self.pd.set_min(0)
self.pd.set_max(self.downloader.total)
try:
r = self.downloader.results.get_nowait()
self.handle_result(r)
except Empty:
pass
if not self.downloader.is_alive():
while True:
try:
r = self.downloader.results.get_nowait()
self.handle_result(r)
except Empty:
break
self.pd.accept()
return
except:
self.cancel()
raise
QTimer.singleShot(50, self.do_one)
def handle_result(self, r):
id_, typ, ok, title = r

View File

@ -9,7 +9,7 @@ Logic for setting up conversion jobs
import cPickle, os
from PyQt4.Qt import QDialog, QProgressDialog, QString, QTimer, SIGNAL
from PyQt4.Qt import QDialog, QProgressDialog, QString, QTimer
from calibre.ptempfile import PersistentTemporaryFile
from calibre.gui2 import warning_dialog, question_dialog
@ -24,7 +24,8 @@ from calibre.ebooks.conversion.config import GuiRecommendations, \
load_defaults, load_specifics, save_specifics
from calibre.gui2.convert import bulk_defaults_for_input_format
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
jobs = []
bad = []
@ -95,7 +96,9 @@ def convert_single_ebook(parent, db, book_ids, auto_conversion=False, out_format
msg).exec_()
return jobs, changed, bad
# }}}
# Bulk convert {{{
def convert_bulk_ebook(parent, queue, db, book_ids, out_format=None, args=[]):
total = len(book_ids)
if total == 0:
@ -125,14 +128,11 @@ class QueueBulk(QProgressDialog):
self.parent = parent
self.use_saved_single_settings = use_saved_single_settings
self.i, self.bad, self.jobs, self.changed = 0, [], [], False
self.timer = QTimer(self)
self.connect(self.timer, SIGNAL('timeout()'), self.do_book)
self.timer.start()
QTimer.singleShot(0, self.do_book)
self.exec_()
def do_book(self):
if self.i >= len(self.book_ids):
self.timer.stop()
return self.do_queue()
book_id = self.book_ids[self.i]
self.i += 1
@ -191,6 +191,7 @@ class QueueBulk(QProgressDialog):
self.setValue(self.i)
except NoSupportedInputFormats:
self.bad.append(book_id)
QTimer.singleShot(0, self.do_book)
def do_queue(self):
self.hide()
@ -209,7 +210,9 @@ class QueueBulk(QProgressDialog):
self.jobs.reverse()
self.queue(self.jobs, self.changed, self.bad, *self.args)
def fetch_scheduled_recipe(arg):
# }}}
def fetch_scheduled_recipe(arg): # {{{
fmt = prefs['output_format'].lower()
pt = PersistentTemporaryFile(suffix='_recipe_out.%s'%fmt.lower())
pt.close()
@ -250,7 +253,9 @@ def fetch_scheduled_recipe(arg):
return 'gui_convert', args, _('Fetch news from ')+arg['title'], fmt.upper(), [pt]
def generate_catalog(parent, dbspec, ids, device_manager, db):
# }}}
def generate_catalog(parent, dbspec, ids, device_manager, db): # {{{
from calibre.gui2.dialogs.catalog import Catalog
# Build the Catalog dialog in gui2.dialogs.catalog
@ -308,8 +313,9 @@ def generate_catalog(parent, dbspec, ids, device_manager, db):
# Which then calls gui2.convert.gui_conversion:gui_catalog() with the args inline
return 'gui_catalog', args, _('Generate catalog'), out.name, d.catalog_sync, \
d.catalog_title
# }}}
def convert_existing(parent, db, book_ids, output_format):
def convert_existing(parent, db, book_ids, output_format): # {{{
already_converted_ids = []
already_converted_titles = []
for book_id in book_ids:
@ -325,3 +331,5 @@ def convert_existing(parent, db, book_ids, output_format):
book_ids = [x for x in book_ids if x not in already_converted_ids]
return book_ids
# }}}

View File

@ -4442,46 +4442,39 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
# Insert the link to the series or remove <a class="series">
aTag = body.find('a', attrs={'class':'series_id'})
if book['series']:
if self.opts.generate_series:
aTag['href'] = "%s.html#%s_series" % ('BySeries',
re.sub('\W','',book['series']).lower())
else:
aTag.extract()
if aTag:
if book['series']:
if self.opts.generate_series:
aTag['href'] = "%s.html#%s_series" % ('BySeries',
re.sub('\W','',book['series']).lower())
else:
aTag.extract()
# Insert the author link (always)
# Insert the author link
aTag = body.find('a', attrs={'class':'author'})
if self.opts.generate_authors:
if self.opts.generate_authors and aTag:
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor",
self.generateAuthorAnchor(book['author']))
if publisher == ' ':
try:
publisherTag = body.find('td', attrs={'class':'publisher'})
publisherTag = body.find('td', attrs={'class':'publisher'})
if publisherTag:
publisherTag.contents[0].replaceWith('&nbsp;')
except:
pass
if not genres:
try:
genresTag = body.find('p',attrs={'class':'genres'})
genresTag = body.find('p',attrs={'class':'genres'})
if genresTag:
genresTag.extract()
except:
pass
if not formats:
try:
formatsTag = body.find('p',attrs={'class':'formats'})
formatsTag = body.find('p',attrs={'class':'formats'})
if formatsTag:
formatsTag.extract()
except:
pass
if note_content == '':
try:
tdTag = body.find('td', attrs={'class':'notes'})
tdTag = body.find('td', attrs={'class':'notes'})
if tdTag:
tdTag.contents[0].replaceWith('&nbsp;')
except:
pass
emptyTags = body.findAll('td', attrs={'class':'empty'})
for mt in emptyTags: