From 343d8f448c59fdd456c195b56460f75a7b71ebe1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 2 Jul 2012 10:17:54 +0530 Subject: [PATCH 01/15] Get Books: Fix ozon.ru --- src/calibre/ebooks/metadata/sources/ozon.py | 81 ++++++++++--------- .../gui2/store/stores/ozon_ru_plugin.py | 53 +++++++----- 2 files changed, 80 insertions(+), 54 deletions(-) diff --git a/src/calibre/ebooks/metadata/sources/ozon.py b/src/calibre/ebooks/metadata/sources/ozon.py index 3845ebf97b..ebb104818f 100644 --- a/src/calibre/ebooks/metadata/sources/ozon.py +++ b/src/calibre/ebooks/metadata/sources/ozon.py @@ -54,30 +54,35 @@ class Ozon(Source): # for ozon.ru search we have to format ISBN with '-' isbn = _format_isbn(log, identifiers.get('isbn', None)) - # TODO: format isbn! - qItems = set([isbn, title]) - if authors: - qItems |= frozenset(authors) - qItems.discard(None) - qItems.discard('') - qItems = map(_quoteString, qItems) - - q = u' '.join(qItems).strip() - log.info(u'search string: ' + q) - - if isinstance(q, unicode): - q = q.encode('utf-8') - if not q: - return None - - search_url += quote_plus(q) + ozonid = identifiers.get('ozon', None) + + unk = unicode(_('Unknown')).upper() + if (title and title != unk) or (authors and authors != [unk]) or isbn or not ozonid: + qItems = set([isbn, title]) + if authors: + qItems |= frozenset(authors) + qItems.discard(None) + qItems.discard('') + qItems = map(_quoteString, qItems) + + q = u' '.join(qItems).strip() + log.info(u'search string: ' + q) + + if isinstance(q, unicode): + q = q.encode('utf-8') + if not q: + return None + + search_url += quote_plus(q) + else: + search_url = self.ozon_url + '/webservices/OzonWebSvc.asmx/ItemDetail?ID=%s' % ozonid + log.debug(u'search url: %r'%search_url) - return search_url # }}} def identify(self, log, result_queue, abort, title=None, authors=None, - identifiers={}, timeout=30): # {{{ + identifiers={}, timeout=60): # {{{ from lxml import etree from calibre.ebooks.chardet import xml_to_unicode @@ -99,7 +104,7 @@ class Ozon(Source): try: parser = etree.XMLParser(recover=True, no_network=True) feed = etree.fromstring(xml_to_unicode(raw, strip_encoding_pats=True, assume_utf8=True)[0], parser=parser) - entries = feed.xpath('//*[local-name() = "SearchItems"]') + entries = feed.xpath('//*[local-name()="SearchItems" or local-name()="ItemDetail"]') if entries: metadata = self.get_metadata(log, entries, title, authors, identifiers) self.get_all_details(log, metadata, abort, result_queue, identifiers, timeout) @@ -112,8 +117,8 @@ class Ozon(Source): def get_metadata(self, log, entries, title, authors, identifiers): # {{{ # some book titles have extra characters like this # TODO: make a twick - reRemoveFromTitle = None - #reRemoveFromTitle = re.compile(r'[?!:.,;+-/&%"\'=]') + #reRemoveFromTitle = None + reRemoveFromTitle = re.compile(r'[?!:.,;+-/&%"\'=]') title = unicode(title).upper() if title else '' if reRemoveFromTitle: @@ -163,7 +168,7 @@ class Ozon(Source): metadata.append(mi) #log.debug(u'added metadata %s %s.'%(mi.title, mi.authors)) else: - log.debug(u'skipped metadata %s %s. (does not match the query)'%(mi.title, mi.authors)) + log.debug(u'skipped metadata %s %s. (does not match the query)'%(unicode(mi.title), mi.authors)) return metadata # }}} @@ -301,7 +306,7 @@ class Ozon(Source): if series: metadata.series = series - xpt = u'normalize-space(substring-after(//meta[@name="description"]/@content, "ISBN"))' + xpt = u'normalize-space(//*[@class="product-detail"]//text()[starts-with(., "ISBN")])' isbn_str = doc.xpath(xpt) if isbn_str: all_isbns = [check_isbn(isbn) for isbn in self.isbnRegex.findall(isbn_str) if _verifyISBNIntegrity(log, isbn)] @@ -326,7 +331,7 @@ class Ozon(Source): # can be set before from xml search responce if not metadata.pubdate: - xpt = u'normalize-space(//div[@class="product-misc"]//text()[contains(., "г.")])' + xpt = u'normalize-space(substring-after(//div[@class="product-detail"]//text()[contains(., "г.")],";"))' yearIn = doc.xpath(xpt) if yearIn: matcher = re.search(r'\d{4}', yearIn) @@ -334,17 +339,20 @@ class Ozon(Source): metadata.pubdate = toPubdate(log, matcher.group(0)) # overwrite comments from HTML if any - xpt = u'//table[@id="detail_description"]//tr/td' + xpt = u'//*[@id="detail_description"]//*[contains(text(), "От производителя")]/../node()[not(self::comment())][not(self::br)][preceding::*[contains(text(), "От производителя")]]' + from lxml.etree import ElementBase comment_elem = doc.xpath(xpt) if comment_elem: - comments = unicode(etree.tostring(comment_elem[0], encoding=unicode)) - if comments: - # cleanup root tag, TODO: remove tags like object/embeded - comments = re.sub(ur'\A.*?|.*\Z', u'', comments.strip(), re.MULTILINE).strip() - if comments and (not metadata.comments or len(comments) > len(metadata.comments)): - metadata.comments = comments - else: - log.debug('HTML book description skipped in favour of search service xml responce') + comments = u'' + for node in comment_elem: + if isinstance(node, ElementBase): + comments += unicode(etree.tostring(node, encoding=unicode)) + elif isinstance(node, basestring) and node.strip(): + comments += unicode(node) + u'\n' + if comments and (not metadata.comments or len(comments) > len(metadata.comments)): + metadata.comments = comments + else: + log.debug('HTML book description skipped in favour of search service xml responce') else: log.debug('No book description found in HTML') # }}} @@ -430,7 +438,8 @@ def _translageLanguageToCode(displayLang): # {{{ u'Китайский': 'zh', u'Японский': 'ja', u'Финский' : 'fi', - u'Польский' : 'pl',} + u'Польский' : 'pl', + u'Украинский' : 'uk',} return langTbl.get(displayLang, None) # }}} @@ -454,7 +463,7 @@ def toPubdate(log, yearAsString): # {{{ res = None if yearAsString: try: - res = parse_only_date(yearAsString) + res = parse_only_date(u"01.01." + yearAsString) except: log.error('cannot parse to date %s'%yearAsString) return res diff --git a/src/calibre/gui2/store/stores/ozon_ru_plugin.py b/src/calibre/gui2/store/stores/ozon_ru_plugin.py index 5d977700c8..b54bf01daf 100644 --- a/src/calibre/gui2/store/stores/ozon_ru_plugin.py +++ b/src/calibre/gui2/store/stores/ozon_ru_plugin.py @@ -46,30 +46,37 @@ class OzonRUStore(BasicStoreConfig, StorePlugin): d.set_tags(self.config.get('tags', '')) d.exec_() - - def search(self, query, max_results=10, timeout=60): + def search(self, query, max_results=15, timeout=60): search_url = self.shop_url + '/webservice/webservice.asmx/SearchWebService?'\ 'searchText=%s&searchContext=ebook' % urllib2.quote(query) + search_urls = [ search_url ] + + ## add this as the fist try if it looks like ozon ID + if re.match("^\d{6,9}$", query): + ozon_detail = self.shop_url + '/webservices/OzonWebSvc.asmx/ItemDetail?ID=%s' % query + search_urls.insert(0, ozon_detail) + xp_template = 'normalize-space(./*[local-name() = "{0}"]/text())' - counter = max_results br = browser() - with closing(br.open(search_url, timeout=timeout)) as f: - raw = xml_to_unicode(f.read(), strip_encoding_pats=True, assume_utf8=True)[0] - doc = etree.fromstring(raw) - for data in doc.xpath('//*[local-name() = "SearchItems"]'): - if counter <= 0: - break - counter -= 1 + + for url in search_urls: + with closing(br.open(url, timeout=timeout)) as f: + raw = xml_to_unicode(f.read(), strip_encoding_pats=True, assume_utf8=True)[0] + doc = etree.fromstring(raw) + for data in doc.xpath('//*[local-name()="SearchItems" or local-name()="ItemDetail"]'): + if counter <= 0: + break + counter -= 1 - s = SearchResult() - s.detail_item = data.xpath(xp_template.format('ID')) - s.title = data.xpath(xp_template.format('Name')) - s.author = data.xpath(xp_template.format('Author')) - s.price = data.xpath(xp_template.format('Price')) - s.cover_url = data.xpath(xp_template.format('Picture')) - s.price = format_price_in_RUR(s.price) - yield s + s = SearchResult() + s.detail_item = data.xpath(xp_template.format('ID')) + s.title = data.xpath(xp_template.format('Name')) + s.author = data.xpath(xp_template.format('Author')) + s.price = data.xpath(xp_template.format('Price')) + s.cover_url = data.xpath(xp_template.format('Picture')) + s.price = format_price_in_RUR(s.price) + yield s def get_details(self, search_result, timeout=60): url = self.shop_url + '/context/detail/id/' + urllib2.quote(search_result.detail_item) @@ -97,6 +104,16 @@ class OzonRUStore(BasicStoreConfig, StorePlugin): search_result.formats = ', '.join(_parse_ebook_formats(formats)) # unfortunately no direct links to download books (only buy link) # search_result.downloads['BF2'] = self.shop_url + '/order/digitalorder.aspx?id=' + + urllib2.quote(search_result.detail_item) + + #

21500 руб.

+ # + # + + # if the price not in the search result (the ID search case) + if not search_result.price: + price = doc.xpath(u'normalize-space(//*[@itemprop="price"]/text())') + search_result.price = format_price_in_RUR(price) + return result def format_price_in_RUR(price): From e6788b5814226fc812447ca258c05c62c2baebbd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 2 Jul 2012 10:21:10 +0530 Subject: [PATCH 02/15] Update FHM UK --- recipes/fhm_uk.recipe | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/recipes/fhm_uk.recipe b/recipes/fhm_uk.recipe index 07f2b4b64e..6ee5ae3fb6 100644 --- a/recipes/fhm_uk.recipe +++ b/recipes/fhm_uk.recipe @@ -2,19 +2,19 @@ from calibre.web.feeds.news import BasicNewsRecipe class AdvancedUserRecipe1325006965(BasicNewsRecipe): title = u'FHM UK' - description = 'Good News for Men' + description = 'Good News for Men.' cover_url = 'http://www.greatmagazines.co.uk/covers/large/w197/current/fhm.jpg' # cover_url = 'http://profile.ak.fbcdn.net/hprofile-ak-snc4/373529_38324934806_64930243_n.jpg' masthead_url = 'http://www.fhm.com/App_Resources/Images/Site/re-design/logo.gif' __author__ = 'Dave Asbury' - # last updated 14/4/12 + # last updated 1/7/12 language = 'en_GB' oldest_article = 28 - max_articles_per_feed = 12 + max_articles_per_feed = 8 remove_empty_feeds = True no_stylesheets = True #auto_cleanup = True - #articles_are_obfuscated = True + # articles_are_obfuscated = True keep_only_tags = [ dict(name='h1'), dict(name='img',attrs={'id' : 'ctl00_Body_imgMainImage'}), @@ -28,11 +28,18 @@ class AdvancedUserRecipe1325006965(BasicNewsRecipe): #] feeds = [ - (u'From the Homepage',u'http://feed43.com/0032328550253453.xml'), - #http://feed43.com/8053226782885416.xml'), - (u'Funny - The Very Best Of The Internet',u'http://feed43.com/4538510106331565.xml'), - (u'Upgrade',u'http://feed43.com/0877305847443234.xml'), - #(u'The Final Countdown', u'http://feed43.com/3576106158530118.xml'), - #(u'Gaming',u'http://feed43.com/0755006465351035.xml'), - (u'Gaming',u'http://feed43.com/6537162612465672.xml'), + (u'Homepage 1',u'http://feed43.com/6655867614547036.xml'), + (u'Homepage 2',u'http://feed43.com/4167731873103110.xml'), + (u'Homepage 3',u'http://feed43.com/7667138788771570.xml'), + (u'Homepage 4',u'http://feed43.com/6550421522527341.xml'), + (u'Funny - The Very Best Of The Internet',u'http://feed43.com/4538510106331565.xml'), + (u'Gaming',u'http://feed43.com/6537162612465672.xml'), + (u'Girls',u'http://feed43.com/3674777224513254.xml'), ] + + extra_css = ''' + h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;} + h2{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;} + p{font-family:Arial,Helvetica,sans-serif;font-size:small;} + body{font-family:Helvetica,Arial,sans-serif;font-size:small;} + ''' From c8b5db52098f5a417ecf1f58343db1ef1d3c1668 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 2 Jul 2012 14:12:28 +0530 Subject: [PATCH 03/15] Fix (I hope) crashes in MultiCompleteComboBox. Also the drop down arrow now only shows the items that start with whatever text is currently entered in the box --- src/calibre/gui2/complete.py | 43 ++++++++-------------- src/calibre/gui2/convert/metadata.py | 28 +++----------- src/calibre/gui2/custom_column_widgets.py | 30 ++------------- src/calibre/gui2/dialogs/add_empty_book.py | 12 +----- src/calibre/gui2/dialogs/metadata_bulk.py | 19 ++-------- src/calibre/gui2/dialogs/search.py | 9 +---- src/calibre/gui2/languages.py | 2 - src/calibre/gui2/library/delegates.py | 4 -- src/calibre/gui2/metadata/basic_widgets.py | 43 ++++++---------------- 9 files changed, 41 insertions(+), 149 deletions(-) diff --git a/src/calibre/gui2/complete.py b/src/calibre/gui2/complete.py index fb1f39dfa3..9d78003231 100644 --- a/src/calibre/gui2/complete.py +++ b/src/calibre/gui2/complete.py @@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en' from PyQt4.Qt import (QLineEdit, QAbstractListModel, Qt, - QApplication, QCompleter, pyqtSignal) + QApplication, QCompleter) from calibre.utils.icu import sort_key, lower from calibre.gui2 import NONE @@ -56,7 +56,7 @@ class MultiCompleteLineEdit(QLineEdit, LineEditECM): to complete non multiple fields as well. ''' - def __init__(self, parent=None): + def __init__(self, parent=None, completer_widget=None): QLineEdit.__init__(self, parent) self.sep = ',' @@ -66,7 +66,7 @@ class MultiCompleteLineEdit(QLineEdit, LineEditECM): self._model = CompleteModel(parent=self) self._completer = c = QCompleter(self._model, self) - c.setWidget(self) + c.setWidget(self if completer_widget is None else completer_widget) c.setCompletionMode(QCompleter.PopupCompletion) c.setCaseSensitivity(Qt.CaseInsensitive) c.setModelSorting(self._model.sorting) @@ -158,21 +158,13 @@ class MultiCompleteLineEdit(QLineEdit, LineEditECM): class MultiCompleteComboBox(EnComboBox): - clear_edit_text = pyqtSignal() - def __init__(self, *args): EnComboBox.__init__(self, *args) - self.setLineEdit(MultiCompleteLineEdit(self)) - # Needed to allow changing the case of an existing item - # otherwise on focus out, the text is changed to the - # item that matches case insensitively - c = self.lineEdit().completer() - c.setCaseSensitivity(Qt.CaseSensitive) - self.dummy_model = CompleteModel(self) - c.setModel(self.dummy_model) - self.lineEdit()._completer.setWidget(self) - self.clear_edit_text.connect(self.clearEditText, - type=Qt.QueuedConnection) + self.le = MultiCompleteLineEdit(self, completer_widget=self) + self.setLineEdit(self.le) + + def showPopup(self): + self.le._completer.complete() def update_items_cache(self, complete_items): self.lineEdit().update_items_cache(complete_items) @@ -187,18 +179,10 @@ class MultiCompleteComboBox(EnComboBox): self.lineEdit().set_add_separator(what) def show_initial_value(self, what): - ''' - Show an initial value. Handle the case of the initial value being blank - correctly (on Qt 4.8.0 having a blank value causes the first value from - the completer to be shown, when the event loop runs). - ''' - what = unicode(what) + what = unicode(what) if what else u'' le = self.lineEdit() - if not what.strip(): - self.clear_edit_text.emit() - else: - self.setEditText(what) - le.selectAll() + self.setEditText(what) + le.selectAll() if __name__ == '__main__': from PyQt4.Qt import QDialog, QVBoxLayout @@ -207,5 +191,8 @@ if __name__ == '__main__': d.setLayout(QVBoxLayout()) le = MultiCompleteComboBox(d) d.layout().addWidget(le) - le.all_items = ['one', 'otwo', 'othree', 'ooone', 'ootwo', 'oothree'] + items = ['one', 'otwo', 'othree', 'ooone', 'ootwo', + 'oothree'] + le.update_items_cache(items) + le.show_initial_value('') d.exec_() diff --git a/src/calibre/gui2/convert/metadata.py b/src/calibre/gui2/convert/metadata.py index ba2fa0713e..3643e5548e 100644 --- a/src/calibre/gui2/convert/metadata.py +++ b/src/calibre/gui2/convert/metadata.py @@ -12,8 +12,8 @@ from PyQt4.Qt import QPixmap, SIGNAL from calibre.gui2 import choose_images, error_dialog from calibre.gui2.convert.metadata_ui import Ui_Form -from calibre.ebooks.metadata import (authors_to_string, string_to_authors, - MetaInformation, title_sort) +from calibre.ebooks.metadata import (string_to_authors, MetaInformation, + title_sort) from calibre.ebooks.metadata.opf2 import metadata_to_opf from calibre.ptempfile import PersistentTemporaryFile from calibre.gui2.convert import Widget @@ -74,14 +74,12 @@ class MetadataWidget(Widget, Ui_Form): mi = self.db.get_metadata(self.book_id, index_is_id=True) self.title.setText(mi.title) - if mi.publisher: - self.publisher.setCurrentIndex(self.publisher.findText(mi.publisher)) + self.publisher.show_initial_value(mi.publisher if mi.publisher else '') self.author_sort.setText(mi.author_sort if mi.author_sort else '') self.tags.setText(', '.join(mi.tags if mi.tags else [])) self.tags.update_items_cache(self.db.all_tags()) self.comment.html = comments_to_html(mi.comments) if mi.comments else '' - if mi.series: - self.series.setCurrentIndex(self.series.findText(mi.series)) + self.series.show_initial_value(mi.series if mi.series else '') if mi.series_index is not None: try: self.series_index.setValue(mi.series_index) @@ -118,16 +116,11 @@ class MetadataWidget(Widget, Ui_Form): self.author.set_add_separator(tweaks['authors_completer_append_separator']) self.author.update_items_cache(self.db.all_author_names()) - for i in all_authors: - id, name = i - name = authors_to_string([name.strip().replace('|', ',') for n in name.split(',')]) - self.author.addItem(name) - au = self.db.authors(self.book_id, True) if not au: au = _('Unknown') au = ' & '.join([a.strip().replace('|', ',') for a in au.split(',')]) - self.author.setEditText(au) + self.author.show_initial_value(au) def initialize_series(self): all_series = self.db.all_series() @@ -135,22 +128,12 @@ class MetadataWidget(Widget, Ui_Form): self.series.set_separator(None) self.series.update_items_cache([x[1] for x in all_series]) - for i in all_series: - id, name = i - self.series.addItem(name) - self.series.setCurrentIndex(-1) - def initialize_publisher(self): all_publishers = self.db.all_publishers() all_publishers.sort(key=lambda x : sort_key(x[1])) self.publisher.set_separator(None) self.publisher.update_items_cache([x[1] for x in all_publishers]) - for i in all_publishers: - id, name = i - self.publisher.addItem(name) - self.publisher.setCurrentIndex(-1) - def get_title_and_authors(self): title = unicode(self.title.text()).strip() if not title: @@ -179,6 +162,7 @@ class MetadataWidget(Widget, Ui_Form): if tags: mi.tags = tags + print (mi) return mi def select_cover(self): diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 654a9f4b5b..c9c8255076 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -314,14 +314,7 @@ class Text(Base): if self.col_metadata['is_multiple']: self.setter(val) else: - idx = None - for i, c in enumerate(values): - if c == val: - idx = i - self.widgets[1].addItem(c) - self.widgets[1].setEditText('') - if idx is not None: - self.widgets[1].setCurrentIndex(idx) + self.widgets[1].show_initial_value(val) def setter(self, val): if self.col_metadata['is_multiple']: @@ -396,16 +389,8 @@ class Series(Base): self.initial_index = s_index self.initial_val = val val = self.normalize_db_val(val) - idx = None - self.name_widget.clear() - for i, c in enumerate(values): - if c == val: - idx = i - self.name_widget.addItem(c) self.name_widget.update_items_cache(values) - self.name_widget.setEditText('') - if idx is not None: - self.widgets[1].setCurrentIndex(idx) + self.name_widget.show_initial_value(val) def getter(self): n = unicode(self.name_widget.currentText()).strip() @@ -860,8 +845,6 @@ class BulkSeries(BulkBase): self.idx_widget.setChecked(False) self.main_widget.set_separator(None) self.main_widget.update_items_cache(self.all_values) - for c in self.all_values: - self.main_widget.addItem(c) self.main_widget.setEditText('') self.a_c_checkbox.setChecked(False) @@ -1005,15 +988,8 @@ class BulkText(BulkBase): if not self.col_metadata['is_multiple']: val = self.get_initial_value(book_ids) self.initial_val = val = self.normalize_db_val(val) - idx = None self.main_widget.blockSignals(True) - for i, c in enumerate(self.all_values): - if c == val: - idx = i - self.main_widget.addItem(c) - self.main_widget.setEditText('') - if idx is not None: - self.main_widget.setCurrentIndex(idx) + self.main_widget.show_initial_value(val) self.main_widget.blockSignals(False) def commit(self, book_ids, notify=False): diff --git a/src/calibre/gui2/dialogs/add_empty_book.py b/src/calibre/gui2/dialogs/add_empty_book.py index d4990e14d4..218bd90483 100644 --- a/src/calibre/gui2/dialogs/add_empty_book.py +++ b/src/calibre/gui2/dialogs/add_empty_book.py @@ -6,8 +6,7 @@ __license__ = 'GPL v3' from PyQt4.Qt import QDialog, QGridLayout, QLabel, QDialogButtonBox, \ QApplication, QSpinBox, QToolButton, QIcon -from calibre.ebooks.metadata import authors_to_string, string_to_authors -from calibre.utils.icu import sort_key +from calibre.ebooks.metadata import string_to_authors from calibre.gui2.complete import MultiCompleteComboBox from calibre.utils.config import tweaks @@ -56,17 +55,10 @@ class AddEmptyBookDialog(QDialog): self.authors_combo.setEditText(_('Unknown')) def initialize_authors(self, db, author): - all_authors = db.all_authors() - all_authors.sort(key=lambda x : sort_key(x[1])) - for i in all_authors: - id, name = i - name = [name.strip().replace('|', ',') for n in name.split(',')] - self.authors_combo.addItem(authors_to_string(name)) - au = author if not au: au = _('Unknown') - self.authors_combo.setEditText(au.replace('|', ',')) + self.authors_combo.show_initial_value(au.replace('|', ',')) self.authors_combo.set_separator('&') self.authors_combo.set_space_before_sep(True) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index 09a244debd..b8f30f3541 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -876,38 +876,25 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): all_authors = self.db.all_authors() all_authors.sort(key=lambda x : sort_key(x[1])) - for i in all_authors: - id, name = i - name = name.strip().replace('|', ',') - self.authors.addItem(name) - self.authors.setEditText('') - self.authors.set_separator('&') self.authors.set_space_before_sep(True) self.authors.set_add_separator(tweaks['authors_completer_append_separator']) self.authors.update_items_cache(self.db.all_author_names()) + self.authors.show_initial_value('') def initialize_series(self): all_series = self.db.all_series() all_series.sort(key=lambda x : sort_key(x[1])) self.series.set_separator(None) self.series.update_items_cache([x[1] for x in all_series]) - - for i in all_series: - id, name = i - self.series.addItem(name) - self.series.setEditText('') + self.series.show_initial_value('') def initialize_publisher(self): all_publishers = self.db.all_publishers() all_publishers.sort(key=lambda x : sort_key(x[1])) self.publisher.set_separator(None) self.publisher.update_items_cache([x[1] for x in all_publishers]) - - for i in all_publishers: - id, name = i - self.publisher.addItem(name) - self.publisher.setEditText('') + self.publisher.show_initial_value('') def tag_editor(self, *args): d = TagEditor(self, self.db, None) diff --git a/src/calibre/gui2/dialogs/search.py b/src/calibre/gui2/dialogs/search.py index 8736ae2259..cf63d150e6 100644 --- a/src/calibre/gui2/dialogs/search.py +++ b/src/calibre/gui2/dialogs/search.py @@ -25,10 +25,6 @@ class SearchDialog(QDialog, Ui_Dialog): all_authors = db.all_authors() all_authors.sort(key=lambda x : sort_key(x[1])) - for i in all_authors: - id, name = i - name = name.strip().replace('|', ',') - self.authors_box.addItem(name) self.authors_box.setEditText('') self.authors_box.set_separator('&') self.authors_box.set_space_before_sep(True) @@ -39,10 +35,7 @@ class SearchDialog(QDialog, Ui_Dialog): all_series.sort(key=lambda x : sort_key(x[1])) self.series_box.set_separator(None) self.series_box.update_items_cache([x[1] for x in all_series]) - for i in all_series: - id, name = i - self.series_box.addItem(name) - self.series_box.setEditText('') + self.series_box.show_initial_value('') all_tags = db.all_tags() self.tags_box.update_items_cache(all_tags) diff --git a/src/calibre/gui2/languages.py b/src/calibre/gui2/languages.py index 0dfbb38b08..f067027097 100644 --- a/src/calibre/gui2/languages.py +++ b/src/calibre/gui2/languages.py @@ -32,8 +32,6 @@ class LanguagesEdit(MultiCompleteComboBox): all_items = sorted(self._lang_map.itervalues(), key=lambda x: (-pmap.get(x, 0), sort_key(x))) self.update_items_cache(all_items) - for item in all_items: - self.addItem(item) @property def vals(self): diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py index 60b8e3445d..77c3152842 100644 --- a/src/calibre/gui2/library/delegates.py +++ b/src/calibre/gui2/library/delegates.py @@ -125,8 +125,6 @@ class TextDelegate(QStyledItemDelegate): # {{{ editor.set_separator(None) complete_items = [i[1] for i in self.auto_complete_function()] editor.update_items_cache(complete_items) - for item in sorted(complete_items, key=sort_key): - editor.addItem(item) ct = index.data(Qt.DisplayRole).toString() editor.show_initial_value(ct) else: @@ -166,8 +164,6 @@ class CompleteDelegate(QStyledItemDelegate): # {{{ all_items = list(self.db.all_custom( label=self.db.field_metadata.key_to_label(col))) editor.update_items_cache(all_items) - for item in sorted(all_items, key=sort_key): - editor.addItem(item) ct = index.data(Qt.DisplayRole).toString() editor.show_initial_value(ct) else: diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index 250d4ffad2..d2a983415f 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -246,14 +246,6 @@ class AuthorsEdit(MultiCompleteComboBox): def initialize(self, db, id_): self.books_to_refresh = set([]) - all_authors = db.all_authors() - all_authors.sort(key=lambda x : sort_key(x[1])) - self.clear() - for i in all_authors: - id, name = i - name = name.strip().replace('|', ',') - self.addItem(name) - self.set_separator('&') self.set_space_before_sep(True) self.set_add_separator(tweaks['authors_completer_append_separator']) @@ -299,7 +291,6 @@ class AuthorsEdit(MultiCompleteComboBox): self.setEditText(' & '.join([x.strip() for x in val])) self.lineEdit().setCursorPosition(0) - return property(fget=fget, fset=fset) def break_cycles(self): @@ -488,19 +479,12 @@ class SeriesEdit(MultiCompleteComboBox): all_series.sort(key=lambda x : sort_key(x[1])) self.update_items_cache([x[1] for x in all_series]) series_id = db.series_id(id_, index_is_id=True) - idx, c = None, 0 - self.clear() + inval = '' for i in all_series: - id, name = i - if id == series_id: - idx = c - self.addItem(name) - c += 1 - - self.lineEdit().setText('') - if idx is not None: - self.setCurrentIndex(idx) - self.original_val = self.current_val + if i[0] == series_id: + inval = i[1] + break + self.original_val = self.current_val = inval def commit(self, db, id_): series = self.current_val @@ -1373,17 +1357,12 @@ class PublisherEdit(MultiCompleteComboBox): # {{{ all_publishers.sort(key=lambda x : sort_key(x[1])) self.update_items_cache([x[1] for x in all_publishers]) publisher_id = db.publisher_id(id_, index_is_id=True) - idx = None - self.clear() - for i, x in enumerate(all_publishers): - id_, name = x - if id_ == publisher_id: - idx = i - self.addItem(name) - - self.setEditText('') - if idx is not None: - self.setCurrentIndex(idx) + inval = '' + for pid, name in all_publishers: + if pid == publisher_id: + inval = name + break + self.original_val = self.current_val = inval def commit(self, db, id_): self.books_to_refresh |= db.set_publisher(id_, self.current_val, From 7ace9fafb29b20a335fc41f98f65c25a2e1d57c5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 2 Jul 2012 14:15:48 +0530 Subject: [PATCH 04/15] ... --- setup/installer/windows/notes.rst | 2 +- src/calibre/gui2/convert/metadata.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/setup/installer/windows/notes.rst b/setup/installer/windows/notes.rst index 0a9c904ff7..f987f83a3b 100644 --- a/setup/installer/windows/notes.rst +++ b/setup/installer/windows/notes.rst @@ -97,7 +97,7 @@ Now, run configure and make:: -no-plugin-manifests is needed so that loading the plugins does not fail looking for the CRT assembly - configure -opensource -release -qt-zlib -qt-libmng -qt-libpng -qt-libtiff -qt-libjpeg -release -platform win32-msvc2008 -no-qt3support -webkit -xmlpatterns -no-phonon -no-style-plastique -no-style-cleanlooks -no-style-motif -no-style-cde -no-declarative -no-scripttools -no-audio-backend -no-multimedia -no-dbus -no-openvg -no-opengl -no-qt3support -confirm-license -nomake examples -nomake demos -nomake docs -no-plugin-manifests -openssl -I Q:\openssl\include -L Q:\openssl\lib && nmake + configure -ltcg -opensource -release -qt-zlib -qt-libmng -qt-libpng -qt-libtiff -qt-libjpeg -release -platform win32-msvc2008 -no-qt3support -webkit -xmlpatterns -no-phonon -no-style-plastique -no-style-cleanlooks -no-style-motif -no-style-cde -no-declarative -no-scripttools -no-audio-backend -no-multimedia -no-dbus -no-openvg -no-opengl -no-qt3support -confirm-license -nomake examples -nomake demos -nomake docs -no-plugin-manifests -openssl -I Q:\openssl\include -L Q:\openssl\lib && nmake Add the path to the bin folder inside the Qt dir to your system PATH. diff --git a/src/calibre/gui2/convert/metadata.py b/src/calibre/gui2/convert/metadata.py index 3643e5548e..6d43abdf63 100644 --- a/src/calibre/gui2/convert/metadata.py +++ b/src/calibre/gui2/convert/metadata.py @@ -162,7 +162,6 @@ class MetadataWidget(Widget, Ui_Form): if tags: mi.tags = tags - print (mi) return mi def select_cover(self): From 31e3a273927c8da0d31489b5c7db29db699bfb90 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 2 Jul 2012 14:31:19 +0530 Subject: [PATCH 05/15] Create separate driver for Pocketbook 622 rather than using Android driver --- resources/compiled_coffeescript.zip | Bin 43332 -> 43332 bytes src/calibre/customize/builtins.py | 4 ++-- src/calibre/devices/android/driver.py | 1 - src/calibre/devices/eb600/driver.py | 13 +++++++++++++ 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/resources/compiled_coffeescript.zip b/resources/compiled_coffeescript.zip index ae77254da8f689bd2542197920179ffd126ff5c8..d7fad735ba651cfe4b99775b7f301581150e1182 100644 GIT binary patch delta 32 kcmX?diRs8CCf)#VW)=|!5cpg8Xd`dn3T7ZZIdR2f0Jt;^%>V!Z delta 32 kcmX?diRs8CCf)#VW)=|!5V% Date: Mon, 2 Jul 2012 16:09:44 +0530 Subject: [PATCH 06/15] ... --- src/calibre/gui2/complete.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/complete.py b/src/calibre/gui2/complete.py index 9d78003231..e66255cc05 100644 --- a/src/calibre/gui2/complete.py +++ b/src/calibre/gui2/complete.py @@ -164,7 +164,13 @@ class MultiCompleteComboBox(EnComboBox): self.setLineEdit(self.le) def showPopup(self): - self.le._completer.complete() + c = self.le._completer + c.setCompletionPrefix('') + c.complete() + + def hidePopup(self): + self.le.update_completions() + EnComboBox.hidePopup(self) def update_items_cache(self, complete_items): self.lineEdit().update_items_cache(complete_items) From 0201e87440d9a089cd6c6f728a7fc8223837bb48 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 2 Jul 2012 16:15:22 +0530 Subject: [PATCH 07/15] ... --- src/calibre/gui2/complete.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/calibre/gui2/complete.py b/src/calibre/gui2/complete.py index e66255cc05..947493cbb9 100644 --- a/src/calibre/gui2/complete.py +++ b/src/calibre/gui2/complete.py @@ -168,10 +168,6 @@ class MultiCompleteComboBox(EnComboBox): c.setCompletionPrefix('') c.complete() - def hidePopup(self): - self.le.update_completions() - EnComboBox.hidePopup(self) - def update_items_cache(self, complete_items): self.lineEdit().update_items_cache(complete_items) From f06339513583983ef6440b45f2f5466e7d3dfd21 Mon Sep 17 00:00:00 2001 From: GRiker Date: Mon, 2 Jul 2012 05:23:23 -0600 Subject: [PATCH 08/15] Added error handler for attempt to read eBook from iDevice --- src/calibre/devices/apple/driver.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index 929c7b9ba0..fffe34ad34 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -828,7 +828,15 @@ class ITUNES(DriverBase): if DEBUG: logger().info("ITUNES.get_file(): exporting '%s'" % path) - outfile.write(open(self.cached_books[path]['lib_book'].location().path).read()) + try: + outfile.write(open(self.cached_books[path]['lib_book'].location().path).read()) + except: + # Clean up + logger().info(" unable to extract books from iDevices") + logger().info(" deleting empty ", outfile.name) + outfile.close() + os.remove(outfile.name) + raise UserFeedback("Unable to extract books from iDevices", details=None, level=UserFeedback.WARN) def open(self, connected_device, library_uuid): ''' From 6d165cbb6174a4d298e245424c3f608efa53c99a Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Mon, 2 Jul 2012 14:04:18 +0200 Subject: [PATCH 09/15] Select the first matching completion when opening a combobox completion popup --- src/calibre/gui2/complete.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/calibre/gui2/complete.py b/src/calibre/gui2/complete.py index 947493cbb9..f4aa70d1f5 100644 --- a/src/calibre/gui2/complete.py +++ b/src/calibre/gui2/complete.py @@ -164,9 +164,19 @@ class MultiCompleteComboBox(EnComboBox): self.setLineEdit(self.le) def showPopup(self): + print 'here' c = self.le._completer + v = unicode(c.currentCompletion()) + print v c.setCompletionPrefix('') c.complete() + i = 0; + while c.setCurrentRow(i): + cr = unicode(c.currentIndex().data().toString()) + if cr.startswith(v): + c.popup().setCurrentIndex(c.currentIndex()) + return + i += 1 def update_items_cache(self, complete_items): self.lineEdit().update_items_cache(complete_items) From 1d9285df7aee841823b45f12b882ba279be2865e Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Mon, 2 Jul 2012 14:19:14 +0200 Subject: [PATCH 10/15] Remove print statements --- src/calibre/gui2/complete.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/calibre/gui2/complete.py b/src/calibre/gui2/complete.py index f4aa70d1f5..86e31ee30c 100644 --- a/src/calibre/gui2/complete.py +++ b/src/calibre/gui2/complete.py @@ -164,10 +164,8 @@ class MultiCompleteComboBox(EnComboBox): self.setLineEdit(self.le) def showPopup(self): - print 'here' c = self.le._completer v = unicode(c.currentCompletion()) - print v c.setCompletionPrefix('') c.complete() i = 0; From 7b502b06f74cf445c2ccf979b164e0531ee37a9b Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Mon, 2 Jul 2012 16:00:30 +0200 Subject: [PATCH 11/15] Limit selection in completion popup to boxes containing less than completion_change_to_ascii_sorting items --- src/calibre/gui2/complete.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/calibre/gui2/complete.py b/src/calibre/gui2/complete.py index 86e31ee30c..6ce6cc2ff0 100644 --- a/src/calibre/gui2/complete.py +++ b/src/calibre/gui2/complete.py @@ -168,13 +168,14 @@ class MultiCompleteComboBox(EnComboBox): v = unicode(c.currentCompletion()) c.setCompletionPrefix('') c.complete() - i = 0; - while c.setCurrentRow(i): - cr = unicode(c.currentIndex().data().toString()) - if cr.startswith(v): - c.popup().setCurrentIndex(c.currentIndex()) - return - i += 1 + if c.model().rowCount() < tweaks['completion_change_to_ascii_sorting']: + i = 0; + while c.setCurrentRow(i): + cr = unicode(c.currentIndex().data().toString()) + if cr.startswith(v): + c.popup().setCurrentIndex(c.currentIndex()) + break + i += 1 def update_items_cache(self, complete_items): self.lineEdit().update_items_cache(complete_items) From 82367fdd9333de1b2f98d29697c8883c5484b4d0 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Mon, 2 Jul 2012 17:08:59 +0200 Subject: [PATCH 12/15] Add option to the user-defined device to swap main and card --- src/calibre/devices/user_defined/driver.py | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/calibre/devices/user_defined/driver.py b/src/calibre/devices/user_defined/driver.py index 6c4e1f77d9..296700d7c8 100644 --- a/src/calibre/devices/user_defined/driver.py +++ b/src/calibre/devices/user_defined/driver.py @@ -66,6 +66,9 @@ class USER_DEFINED(USBMS): _('Card A folder') + ':::

' + _('Enter the folder where the books are to be stored. This folder ' 'is prepended to any send_to_device template') + '

', + _('Swap main and card A') + ':::

' + + _('Check this box if the device\'s main memory is being seen as ' + 'card a and the card is being seen as main memory') + '

', ] EXTRA_CUSTOMIZATION_DEFAULT = [ '0xffff', @@ -78,16 +81,19 @@ class USER_DEFINED(USBMS): '', '', '', + False, ] OPT_USB_VENDOR_ID = 0 OPT_USB_PRODUCT_ID = 1 OPT_USB_REVISION_ID = 2 + # OPT 3 isn't used OPT_USB_WINDOWS_MM_VEN_ID = 4 OPT_USB_WINDOWS_MM_ID = 5 OPT_USB_WINDOWS_CA_VEN_ID = 6 OPT_USB_WINDOWS_CA_ID = 7 OPT_MAIN_MEM_FOLDER = 8 OPT_CARD_A_FOLDER = 9 + OPT_SWAP_MAIN_AND_CARD = 10 def initialize(self): self.plugin_needs_delayed_initialization = True @@ -113,4 +119,28 @@ class USER_DEFINED(USBMS): traceback.print_exc() self.plugin_needs_delayed_initialization = False + def windows_sort_drives(self, drives): + + if len(drives) < 2: return drives + e = self.settings().extra_customization + if not e[self.OPT_SWAP_MAIN_AND_CARD]: + return drives + main = drives.get('main', None) + carda = drives.get('carda', None) + if main and carda: + drives['main'] = carda + drives['carda'] = main + return drives + + def linux_swap_drives(self, drives): + if len(drives) < 2 or not drives[1] or not drives[2]: return drives + e = self.settings().extra_customization + if not e[self.OPT_SWAP_MAIN_AND_CARD]: + return drives + drives = list(drives) + t = drives[0] + drives[0] = drives[1] + drives[1] = t + return tuple(drives) + From 06d13aa0af39b0de7fa4e9ce7dc9e294b3b29b71 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 3 Jul 2012 07:10:39 +0530 Subject: [PATCH 13/15] ... --- src/calibre/linux.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/linux.py b/src/calibre/linux.py index f1a53603e7..90cacd0180 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -354,8 +354,8 @@ class PostInstall: check_call('xdg-icon-resource install --noupdate --context mimetypes --size 128 calibre-lrf.png text-lrs', shell=True) self.icon_resources.append(('mimetypes', 'application-lrs', '128')) - render_img('lt.png', 'calibre-gui.png') - check_call('xdg-icon-resource install --noupdate --size 128 calibre-gui.png calibre-gui', shell=True) + render_img('lt.png', 'calibre-gui.png', width=256, height=256) + check_call('xdg-icon-resource install --noupdate --size 256 calibre-gui.png calibre-gui', shell=True) self.icon_resources.append(('apps', 'calibre-gui', '128')) render_img('viewer.png', 'calibre-viewer.png') check_call('xdg-icon-resource install --size 128 calibre-viewer.png calibre-viewer', shell=True) From 18791fd822b3cedab76994590bba1f09f345765f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 3 Jul 2012 07:25:14 +0530 Subject: [PATCH 14/15] Content server: Add a navigation panel at the bottom of each page. Fixes #1020225 (improvement in server web pages) --- src/calibre/library/server/mobile.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/calibre/library/server/mobile.py b/src/calibre/library/server/mobile.py index eef131e89f..6119e29911 100644 --- a/src/calibre/library/server/mobile.py +++ b/src/calibre/library/server/mobile.py @@ -97,6 +97,7 @@ def build_index(books, num, search, sort, order, start, total, url_base, CKEYS, search_box = build_search_box(num, search, sort, order, prefix) navigation = build_navigation(start, num, total, prefix+url_base) + navigation2 = build_navigation(start, num, total, prefix+url_base) bookt = TABLE(id='listing') body = BODY( @@ -104,7 +105,8 @@ def build_index(books, num, search, sort, order, start, total, url_base, CKEYS, search_box, navigation, HR(CLASS('spacer')), - bookt + bookt, + navigation2 ) # Book list {{{ From 9ef592bc331ce25d800840dcc927d0608d255dc5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 3 Jul 2012 07:27:42 +0530 Subject: [PATCH 15/15] ... --- src/calibre/library/server/mobile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/library/server/mobile.py b/src/calibre/library/server/mobile.py index 6119e29911..aa98412d27 100644 --- a/src/calibre/library/server/mobile.py +++ b/src/calibre/library/server/mobile.py @@ -106,6 +106,7 @@ def build_index(books, num, search, sort, order, start, total, url_base, CKEYS, navigation, HR(CLASS('spacer')), bookt, + HR(CLASS('spacer')), navigation2 ) @@ -157,7 +158,6 @@ def build_index(books, num, search, sort, order, start, total, url_base, CKEYS, bookt.append(TR(thumbnail, data)) # }}} - body.append(HR()) body.append(DIV( A(_('Switch to the full interface (non-mobile interface)'), href=prefix+"/browse",