diff --git a/recipes/slashdot.recipe b/recipes/slashdot.recipe index c7c68c3f1a..b10700a749 100644 --- a/recipes/slashdot.recipe +++ b/recipes/slashdot.recipe @@ -8,23 +8,36 @@ __copyright__ = '2009, Kovid Goyal edited by Huan T' from calibre.web.feeds.news import BasicNewsRecipe class Slashdot(BasicNewsRecipe): - title = u'Slashdot.org' - description = '''Tech news. WARNING: This recipe downloads a lot - of content and may result in your IP being banned from slashdot.org''' - oldest_article = 7 - simultaneous_downloads = 1 - delay = 3 - max_articles_per_feed = 100 - language = 'en' + title = u'Slashdot.org' + description = '''Tech news. WARNING: This recipe downloads a lot + of content and may result in your IP being banned from slashdot.org''' + oldest_article = 7 + simultaneous_downloads = 1 + delay = 3 + max_articles_per_feed = 100 + language = 'en' - __author__ = 'floweros edited by Huan T' - no_stylesheets = True -# keep_only_tags = [ -# dict(name='div',attrs={'class':'article'}), -# dict(name='div',attrs={'class':'commentTop'}), -# ] + __author__ = 'floweros edited by Huan T' + no_stylesheets = True + keep_only_tags = [ + dict(name='div',attrs={'id':'article'}), + dict(name='div',attrs={'class':['postBody' 'details']}), + dict(name='footer',attrs={'class':['clearfix meta article-foot']}), + dict(name='article',attrs={'class':['fhitem fhitem-story article usermode thumbs grid_24']}), + dict(name='dl',attrs={'class':'relatedPosts'}), + dict(name='h2',attrs={'class':'story'}), + dict(name='span',attrs={'class':'comments'}), + ] - feeds = [ + + remove_tags = [ + dict(name='aside',attrs={'id':'slashboxes'}), + dict(name='div',attrs={'class':'paginate'}), + dict(name='section',attrs={'id':'comments'}), + dict(name='span',attrs={'class':'topic'}), + ] + + feeds = [ (u'Slashdot', u'http://rss.slashdot.org/Slashdot/slashdot'), (u'/. IT', @@ -37,5 +50,3 @@ class Slashdot(BasicNewsRecipe): u'http://rss.slashdot.org/Slashdot/slashdotYourRightsOnline') ] - def get_article_url(self, article): - return article.get('feedburner_origlink', None) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index c4c951f980..091aa9a34d 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -48,7 +48,7 @@ authors_completer_append_separator = False # When this tweak is changed, the author_sort values stored with each author # must be recomputed by right-clicking on an author in the left-hand tags pane, # selecting 'manage authors', and pressing 'Recalculate all author sort values'. -author_sort_copy_method = 'invert' +author_sort_copy_method = 'comma' #: Use author sort in Tag Browser # Set which author field to display in the tags pane (the list of authors, diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py index 44d9bc1e49..7fe246f450 100644 --- a/src/calibre/devices/android/driver.py +++ b/src/calibre/devices/android/driver.py @@ -108,10 +108,10 @@ class ANDROID(USBMS): 'SGH-T849', '_MB300', 'A70S', 'S_ANDROID', 'A101IT', 'A70H', 'IDEOS_TABLET', 'MYTOUCH_4G', 'UMS_COMPOSITE', 'SCH-I800_CARD', '7', 'A956', 'A955', 'A43', 'ANDROID_PLATFORM', 'TEGRA_2', - 'MB860', 'MULTI-CARD', 'MID7015A'] + 'MB860', 'MULTI-CARD', 'MID7015A', 'INCREDIBLE'] WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD', - 'A70S', 'A101IT', '7'] + 'A70S', 'A101IT', '7', 'INCREDIBLE'] OSX_MAIN_MEM = 'Android Device Main Memory' diff --git a/src/calibre/ebooks/metadata/sources/base.py b/src/calibre/ebooks/metadata/sources/base.py index 53fe9a4c2d..86a9fe1133 100644 --- a/src/calibre/ebooks/metadata/sources/base.py +++ b/src/calibre/ebooks/metadata/sources/base.py @@ -325,9 +325,8 @@ class Source(Plugin): tokens = title.split() for token in tokens: token = token.strip() - if token and token.lower() not in ('a', 'and', 'the', '&') and strip_joiners: - yield token - elif token: + if token and (not strip_joiners or token.lower() not in ('a', + 'and', 'the', '&')): yield token def split_jobs(self, jobs, num): @@ -375,7 +374,12 @@ class Source(Plugin): def get_book_url(self, identifiers): ''' Return the URL for the book identified by identifiers at this source. - If no URL is found, return None. + This URL must be browseable to by a human using a browser. It is meant + to provide a clickable link for the user to easily visit the books page + at this source. + If no URL is found, return None. This method must be quick, and + consistent, so only implement it if it is possible to construct the URL + from a known scheme given identifiers. ''' return None diff --git a/src/calibre/ebooks/metadata/sources/identify.py b/src/calibre/ebooks/metadata/sources/identify.py index 1fb1a74679..6295efa0c0 100644 --- a/src/calibre/ebooks/metadata/sources/identify.py +++ b/src/calibre/ebooks/metadata/sources/identify.py @@ -433,7 +433,7 @@ def urls_from_identifiers(identifiers): # {{{ pass isbn = identifiers.get('isbn', None) if isbn: - ans.append(('ISBN', + ans.append((isbn, 'http://www.worldcat.org/search?q=bn%%3A%s&qt=advanced'%isbn)) return ans # }}} @@ -444,13 +444,18 @@ if __name__ == '__main__': # tests {{{ from calibre.ebooks.metadata.sources.test import (test_identify, title_test, authors_test) tests = [ + ( + {'title':'Magykal Papers', + 'authors':['Sage']}, + [title_test('The Magykal Papers', exact=True)], + ), + ( # An e-book ISBN not on Amazon, one of the authors is # unknown to Amazon {'identifiers':{'isbn': '9780307459671'}, 'title':'Invisible Gorilla', 'authors':['Christopher Chabris']}, - [title_test('The Invisible Gorilla', - exact=True), authors_test(['Christopher Chabris', 'Daniel Simons'])] + [title_test('The Invisible Gorilla', exact=True)] ), diff --git a/src/calibre/ebooks/metadata/sources/overdrive.py b/src/calibre/ebooks/metadata/sources/overdrive.py index 56a905de03..26c90f08fe 100755 --- a/src/calibre/ebooks/metadata/sources/overdrive.py +++ b/src/calibre/ebooks/metadata/sources/overdrive.py @@ -1,4 +1,7 @@ #!/usr/bin/env python +from __future__ import (unicode_literals, division, absolute_import, + print_function) + __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' @@ -6,14 +9,13 @@ __docformat__ = 'restructuredtext en' ''' Fetch metadata using Overdrive Content Reserve ''' -import sys, re, random, urllib, mechanize, copy +import re, random, mechanize, copy from threading import RLock from Queue import Queue, Empty -from lxml import html, etree +from lxml import html from lxml.html import soupparser -from calibre import browser from calibre.ebooks.metadata import check_isbn from calibre.ebooks.metadata.sources.base import Source from calibre.ebooks.metadata.book.base import Metadata @@ -33,7 +35,7 @@ class OverDrive(Source): capabilities = frozenset(['identify', 'cover']) touched_fields = frozenset(['title', 'authors', 'tags', 'pubdate', - 'comments', 'publisher', 'identifier:isbn', 'series', 'series_num', + 'comments', 'publisher', 'identifier:isbn', 'series', 'series_index', 'language', 'identifier:overdrive']) has_html_comments = True supports_gzip_transfer_encoding = False @@ -43,7 +45,6 @@ class OverDrive(Source): Source.__init__(self, *args, **kwargs) self.prefs.defaults['ignore_fields'] =['tags', 'pubdate', 'comments', 'identifier:isbn', 'language'] - def identify(self, log, result_queue, abort, title=None, authors=None, # {{{ identifiers={}, timeout=30): ovrdrv_id = identifiers.get('overdrive', None) @@ -68,19 +69,6 @@ class OverDrive(Source): return None # }}} - - def get_book_url(self, identifiers): # {{{ - ovrdrv_id = identifiers.get('overdrive', None) - if ovrdrv_id is not None: - ovrdrv_data = ovrdrv_data_cache.get(ovrdrv_id, None) - if ovrdrv_data: - return ovrdrv_data[1] - else: - br = browser() - ovrdrv_data = self.to_ovrdrv_data(br, None, None, ovrdrv_id) - return ovrdrv_data[1] - # }}} - def download_cover(self, log, result_queue, abort, # {{{ title=None, authors=None, identifiers={}, timeout=30): cached_url = self.get_cached_cover_url(identifiers) @@ -136,28 +124,6 @@ class OverDrive(Source): return url # }}} - def create_query(self, title=None, authors=None, identifiers={}): - q = '' - if title or authors: - def build_term(prefix, parts): - return ' '.join('in'+prefix + ':' + x for x in parts) - title_tokens = list(self.get_title_tokens(title, False, True)) - if title_tokens: - q += build_term('title', title_tokens) - author_tokens = self.get_author_tokens(authors, - only_first_author=True) - if author_tokens: - q += ('+' if q else '') + build_term('author', - author_tokens) - - if isinstance(q, unicode): - q = q.encode('utf-8') - if not q: - return None - return BASE_URL+urlencode({ - 'q':q, - }) - def get_base_referer(self): # to be used for passing referrer headers to cover download choices = [ 'http://overdrive.chipublib.org/82DC601D-7DDE-4212-B43A-09D821935B01/10/375/en/', @@ -209,7 +175,6 @@ class OverDrive(Source): br.set_cookiejar(clean_cj) - def overdrive_search(self, br, q, title, author): # re-initialize the cookiejar to so that it's clean clean_cj = mechanize.CookieJar() @@ -275,11 +240,11 @@ class OverDrive(Source): if ovrdrv_id is not None and int(formatid) in [1, 50, 410, 900]: #print "overdrive id is not None, searching based on format type priority" return self.format_results(reserveid, od_title, subtitle, series, publisher, - creators, thumbimage, worldcatlink, formatid) + creators, thumbimage, worldcatlink, formatid) else: creators = creators.split(', ') # if an exact match in a preferred format occurs - if creators[0] == author[0] and od_title == title and int(formatid) in [1, 50, 410, 900]: + if (author and creators[0] == author[0]) and od_title == title and int(formatid) in [1, 50, 410, 900]: return self.format_results(reserveid, od_title, subtitle, series, publisher, creators, thumbimage, worldcatlink, formatid) else: @@ -312,7 +277,6 @@ class OverDrive(Source): else: return '' - def overdrive_get_record(self, br, q, ovrdrv_id): search_url = q+'SearchResults.aspx?ReserveID={'+ovrdrv_id+'}' results_url = q+'SearchResults.svc/GetResults?sEcho=1&iColumns=18&sColumns=ReserveID%2CTitle%2CSubtitle%2CEdition%2CSeries%2CPublisher%2CFormat%2CFormatID%2CCreators%2CThumbImage%2CShortDescription%2CWorldCatLink%2CExcerptLink%2CCreatorFile%2CSortTitle%2CAvailableToLibrary%2CAvailableToRetailer%2CRelevancyRank&iDisplayStart=0&iDisplayLength=10&sSearch=&bEscapeRegex=true&iSortingCols=1&iSortCol_0=17&sSortDir_0=asc' @@ -390,7 +354,10 @@ class OverDrive(Source): if len(ovrdrv_data[3]) > 1: mi.series = ovrdrv_data[3] if ovrdrv_data[4]: - mi.series_index = ovrdrv_data[4] + try: + mi.series_index = float(ovrdrv_data[4]) + except: + pass mi.publisher = ovrdrv_data[5] mi.authors = ovrdrv_data[6] mi.title = ovrdrv_data[8] @@ -407,7 +374,7 @@ class OverDrive(Source): if callable(getattr(e, 'getcode', None)) and \ e.getcode() == 404: return False - raise + raise raw = xml_to_unicode(raw, strip_encoding_pats=True, resolve_entities=True)[0] try: @@ -427,12 +394,12 @@ class OverDrive(Source): if lang: mi.language = lang[0].strip() - #if ebook_isbn: - # print "ebook isbn is "+str(ebook_isbn[0]) - # isbn = check_isbn(ebook_isbn[0].strip()) - # if isbn: - # self.cache_isbn_to_identifier(isbn, ovrdrv_id) - # mi.isbn = isbn + if ebook_isbn: + #print "ebook isbn is "+str(ebook_isbn[0]) + isbn = check_isbn(ebook_isbn[0].strip()) + if isbn: + self.cache_isbn_to_identifier(isbn, ovrdrv_id) + mi.isbn = isbn if subjects: mi.tags = [tag.strip() for tag in subjects[0].split(',')] @@ -448,8 +415,25 @@ class OverDrive(Source): return None -def main(args=sys.argv): - return 0 - if __name__ == '__main__': - sys.exit(main()) + # To run these test use: + # calibre-debug -e src/calibre/ebooks/metadata/sources/overdrive.py + from calibre.ebooks.metadata.sources.test import (test_identify_plugin, + title_test, authors_test) + test_identify_plugin(OverDrive.name, + [ + + ( + {'title':'Foundation and Earth', + 'authors':['Asimov']}, + [title_test('Foundation and Earth', exact=True), + authors_test(['Isaac Asimov'])] + ), + + ( + {'title': 'Elephants', 'authors':['Agatha']}, + [title_test('Elephants Can Remember', exact=False), + authors_test(['Agatha Christie'])] + ), + ]) + diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 4d4f66eab1..99cb5848ba 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -29,8 +29,7 @@ from calibre.ebooks.metadata.meta import set_metadata from calibre.constants import DEBUG from calibre.utils.config import prefs, tweaks from calibre.utils.magick.draw import thumbnail -from calibre.library.save_to_disk import plugboard_any_device_value, \ - plugboard_any_format_value +from calibre.library.save_to_disk import find_plugboard # }}} class DeviceJob(BaseJob): # {{{ @@ -93,23 +92,6 @@ class DeviceJob(BaseJob): # {{{ # }}} -def find_plugboard(device_name, format, plugboards): - cpb = None - if format in plugboards: - cpb = plugboards[format] - elif plugboard_any_format_value in plugboards: - cpb = plugboards[plugboard_any_format_value] - if cpb is not None: - if device_name in cpb: - cpb = cpb[device_name] - elif plugboard_any_device_value in cpb: - cpb = cpb[plugboard_any_device_value] - else: - cpb = None - if DEBUG: - prints('Device using plugboard', format, device_name, cpb) - return cpb - def device_name_for_plugboards(device_class): if hasattr(device_class, 'DEVICE_PLUGBOARD_NAME'): return device_class.DEVICE_PLUGBOARD_NAME @@ -607,6 +589,16 @@ class DeviceMenu(QMenu): # {{{ class DeviceMixin(object): # {{{ + #: This signal is emitted once, after metadata is downloaded from the + #: connected device. + #: The sequence: gui.device_manager.is_device_connected will become True, + #: then sometime later gui.device_metadata_available will be signaled. + #: This does not mean that there are no more jobs running. Automatic metadata + #: management might have kicked off a sync_booklists to write new metadata onto + #: the device, and that job might still be running when the signal is emitted. + device_metadata_available = pyqtSignal() + device_connection_changed = pyqtSignal(object) + def __init__(self): self.device_error_dialog = error_dialog(self, _('Error'), _('Error communicating with device'), ' ') @@ -753,6 +745,7 @@ class DeviceMixin(object): # {{{ self.location_manager.update_devices() self.library_view.set_device_connected(self.device_connected) self.refresh_ondevice() + self.device_connection_changed.emit(connected) def info_read(self, job): ''' @@ -791,6 +784,7 @@ class DeviceMixin(object): # {{{ self.sync_news() self.sync_catalogs() self.refresh_ondevice() + self.device_metadata_available.emit() def refresh_ondevice(self, reset_only = False): ''' @@ -892,7 +886,7 @@ class DeviceMixin(object): # {{{ sub_dest_parts.append('') to = sub_dest_parts[0] fmts = sub_dest_parts[1] - subject = ';'.join(sub_dest_parts[2:]) + subject = ';'.join(sub_dest_parts[2:]) fmts = [x.strip().lower() for x in fmts.split(',')] self.send_by_mail(to, fmts, delete, subject=subject) diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py index 7e30f02420..a3ac777115 100644 --- a/src/calibre/gui2/metadata/single_download.py +++ b/src/calibre/gui2/metadata/single_download.py @@ -30,7 +30,6 @@ from calibre.ebooks.metadata.book.base import Metadata from calibre.gui2 import error_dialog, NONE from calibre.utils.date import utcnow, fromordinal, format_date from calibre.library.comments import comments_to_html -from calibre.constants import islinux from calibre import force_unicode # }}} @@ -117,12 +116,10 @@ class CoverDelegate(QStyledItemDelegate): # {{{ def paint(self, painter, option, index): QStyledItemDelegate.paint(self, painter, option, index) - if islinux: - # On linux for some reason the selected color is drawn on top of - # the decoration - style = QApplication.style() - style.drawItemPixmap(painter, option.rect, Qt.AlignTop|Qt.AlignHCenter, - QPixmap(index.data(Qt.DecorationRole))) + # Ensure the cover is rendered over any selection rect + style = QApplication.style() + style.drawItemPixmap(painter, option.rect, Qt.AlignTop|Qt.AlignHCenter, + QPixmap(index.data(Qt.DecorationRole))) if self.timer.isActive() and index.data(Qt.UserRole).toBool(): rect = QRect(0, 0, self.spinner_width, self.spinner_width) rect.moveCenter(option.rect.center()) @@ -952,7 +949,7 @@ class CoverFetch(QDialog): # {{{ # }}} if __name__ == '__main__': - DEBUG_DIALOG = True + #DEBUG_DIALOG = True app = QApplication([]) d = FullFetch() d.start(title='great gatsby', authors=['fitzgerald']) diff --git a/src/calibre/gui2/preferences/__init__.py b/src/calibre/gui2/preferences/__init__.py index 649a58448d..5b0a05ba40 100644 --- a/src/calibre/gui2/preferences/__init__.py +++ b/src/calibre/gui2/preferences/__init__.py @@ -337,7 +337,13 @@ def show_config_widget(category, name, gui=None, show_restart_msg=False, bb.button(bb.RestoreDefaults).setEnabled(w.supports_restoring_to_defaults) bb.button(bb.Apply).setEnabled(False) bb.button(bb.Apply).clicked.connect(d.accept) - w.changed_signal.connect(lambda : bb.button(bb.Apply).setEnabled(True)) + def onchange(): + b = bb.button(bb.Apply) + b.setEnabled(True) + b.setDefault(True) + b.setAutoDefault(True) + w.changed_signal.connect(onchange) + bb.button(bb.Cancel).setFocus(True) l = QVBoxLayout() d.setLayout(l) l.addWidget(w) diff --git a/src/calibre/gui2/preferences/plugboard.py b/src/calibre/gui2/preferences/plugboard.py index 8f2b084d76..1ce4f89dfd 100644 --- a/src/calibre/gui2/preferences/plugboard.py +++ b/src/calibre/gui2/preferences/plugboard.py @@ -7,6 +7,7 @@ __docformat__ = 'restructuredtext en' from PyQt4.Qt import Qt, QLineEdit, QComboBox, SIGNAL, QListWidgetItem +from calibre.customize.ui import is_disabled from calibre.gui2 import error_dialog from calibre.gui2.device import device_name_for_plugboards from calibre.gui2.dialogs.template_dialog import TemplateDialog @@ -15,6 +16,8 @@ from calibre.gui2.preferences.plugboard_ui import Ui_Form from calibre.customize.ui import metadata_writers, device_plugins from calibre.library.save_to_disk import plugboard_any_format_value, \ plugboard_any_device_value, plugboard_save_to_disk_value +from calibre.library.server.content import plugboard_content_server_value, \ + plugboard_content_server_formats from calibre.utils.formatter import validation_formatter @@ -68,19 +71,26 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.device_label.setText(_('Device currently connected: None')) self.devices = ['', 'APPLE', 'FOLDER_DEVICE'] + self.device_to_formats_map = {} for device in device_plugins(): n = device_name_for_plugboards(device) + self.device_to_formats_map[n] = device.FORMATS if n not in self.devices: self.devices.append(n) self.devices.sort(cmp=lambda x, y: cmp(x.lower(), y.lower())) self.devices.insert(1, plugboard_save_to_disk_value) - self.devices.insert(2, plugboard_any_device_value) + self.devices.insert(1, plugboard_content_server_value) + self.device_to_formats_map[plugboard_content_server_value] = \ + plugboard_content_server_formats + self.devices.insert(1, plugboard_any_device_value) self.new_device.addItems(self.devices) self.formats = [''] for w in metadata_writers(): - for f in w.file_types: - self.formats.append(f) + if not is_disabled(w): + for f in w.file_types: + if not f in self.formats: + self.formats.append(f) self.formats.append('device_db') self.formats.sort() self.formats.insert(1, plugboard_any_format_value) @@ -230,6 +240,15 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): show=True) self.new_device.setCurrentIndex(0) return + if self.current_device in self.device_to_formats_map: + allowable_formats = self.device_to_formats_map[self.current_device] + if self.current_format not in allowable_formats: + error_dialog(self, '', + _('The {0} device does not support the {1} format.'). + format(self.current_device, self.current_format), + show=True) + self.new_device.setCurrentIndex(0) + return self.set_fields() def new_format_changed(self, txt): diff --git a/src/calibre/gui2/store/search.py b/src/calibre/gui2/store/search.py index 1d263959ef..ce74d52547 100644 --- a/src/calibre/gui2/store/search.py +++ b/src/calibre/gui2/store/search.py @@ -8,6 +8,7 @@ __docformat__ = 'restructuredtext en' import re import time +import traceback from contextlib import closing from random import shuffle from threading import Thread @@ -20,9 +21,12 @@ from calibre import browser from calibre.gui2 import NONE from calibre.gui2.progress_indicator import ProgressIndicator from calibre.gui2.store.search_ui import Ui_Dialog +from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \ + REGEXP_MATCH from calibre.utils.config import DynamicConfig from calibre.utils.icu import sort_key from calibre.utils.magick.draw import thumbnail +from calibre.utils.search_query_parser import SearchQueryParser HANG_TIME = 75000 # milliseconds seconds TIMEOUT = 75 # seconds @@ -290,11 +294,15 @@ class SearchThread(Thread): while self._run and not self.tasks.empty(): try: query, store_name, store_plugin, timeout = self.tasks.get() - for res in store_plugin.search(query, timeout=timeout): + squery = query + for loc in SearchFilter.USABLE_LOCATIONS: + squery = re.sub(r'%s:"?(?P[^\s"]+)"?' % loc, '\g', squery) + for res in store_plugin.search(squery, timeout=timeout): if not self._run: return res.store_name = store_name - self.results.put(res) + if SearchFilter(res).parse(query): + self.results.put(res) self.tasks.task_done() except: pass @@ -450,3 +458,82 @@ class Matches(QAbstractItemModel): if reset: self.reset() + +class SearchFilter(SearchQueryParser): + + USABLE_LOCATIONS = [ + 'all', + 'author', + 'authors', + 'cover', + 'price', + 'title', + 'store', + ] + + def __init__(self, search_result): + SearchQueryParser.__init__(self, locations=self.USABLE_LOCATIONS) + self.search_result = search_result + + def universal_set(self): + return set([self.search_result]) + + def get_matches(self, location, query): + location = location.lower().strip() + if location == 'authors': + location = 'author' + + matchkind = CONTAINS_MATCH + if len(query) > 1: + if query.startswith('\\'): + query = query[1:] + elif query.startswith('='): + matchkind = EQUALS_MATCH + query = query[1:] + elif query.startswith('~'): + matchkind = REGEXP_MATCH + query = query[1:] + if matchkind != REGEXP_MATCH: ### leave case in regexps because it can be significant e.g. \S \W \D + query = query.lower() + + if location not in self.USABLE_LOCATIONS: + return set([]) + matches = set([]) + all_locs = set(self.USABLE_LOCATIONS) - set(['all']) + locations = all_locs if location == 'all' else [location] + q = { + 'author': self.search_result.author.lower(), + 'cover': self.search_result.cover_url, + 'format': '', + 'price': self.search_result.price, + 'store': self.search_result.store_name.lower(), + 'title': self.search_result.title.lower(), + } + for x in ('author', 'format'): + q[x+'s'] = q[x] + for locvalue in locations: + ac_val = q[locvalue] + if query == 'true': + if ac_val is not None: + matches.add(self.search_result) + continue + if query == 'false': + if ac_val is None: + matches.add(self.search_result) + continue + try: + ### Can't separate authors because comma is used for name sep and author sep + ### Exact match might not get what you want. For that reason, turn author + ### exactmatch searches into contains searches. + if locvalue == 'author' and matchkind == EQUALS_MATCH: + m = CONTAINS_MATCH + else: + m = matchkind + + vals = [ac_val] + if _match(query, vals, m): + matches.add(self.search_result) + break + except ValueError: # Unicode errors + traceback.print_exc() + return matches diff --git a/src/calibre/gui2/store/web_control.py b/src/calibre/gui2/store/web_control.py index 874328f872..0b79c526a8 100644 --- a/src/calibre/gui2/store/web_control.py +++ b/src/calibre/gui2/store/web_control.py @@ -31,10 +31,14 @@ class NPWebView(QWebView): proxy_parts = urlparse(http_proxy) proxy = QNetworkProxy() proxy.setType(QNetworkProxy.HttpProxy) - proxy.setUser(proxy_parts.username) - proxy.setPassword(proxy_parts.password) - proxy.setHostName(proxy_parts.hostname) - proxy.setPort(proxy_parts.port) + if proxy_parts.username: + proxy.setUser(proxy_parts.username) + if proxy_parts.password: + proxy.setPassword(proxy_parts.password) + if proxy_parts.hostname: + proxy.setHostName(proxy_parts.hostname) + if proxy_parts.port: + proxy.setPort(proxy_parts.port) self.page().networkAccessManager().setProxy(proxy) self.page().setForwardUnsupportedContent(True) diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py index 96c42e6e0e..3c57af40a8 100644 --- a/src/calibre/library/save_to_disk.py +++ b/src/calibre/library/save_to_disk.py @@ -51,6 +51,23 @@ for x in FORMAT_ARG_DESCS: FORMAT_ARGS[x] = '' +def find_plugboard(device_name, format, plugboards): + cpb = None + if format in plugboards: + cpb = plugboards[format] + elif plugboard_any_format_value in plugboards: + cpb = plugboards[plugboard_any_format_value] + if cpb is not None: + if device_name in cpb: + cpb = cpb[device_name] + elif plugboard_any_device_value in cpb: + cpb = cpb[plugboard_any_device_value] + else: + cpb = None + if DEBUG: + prints('Device using plugboard', format, device_name, cpb) + return cpb + def config(defaults=None): if defaults is None: c = Config('save_to_disk', _('Options to control saving to disk')) @@ -279,20 +296,7 @@ def do_save_book_to_disk(id_, mi, cover, plugboards, written = False for fmt in formats: global plugboard_save_to_disk_value, plugboard_any_format_value - dev_name = plugboard_save_to_disk_value - cpb = None - if fmt in plugboards: - cpb = plugboards[fmt] - if dev_name in cpb: - cpb = cpb[dev_name] - else: - cpb = None - if cpb is None and plugboard_any_format_value in plugboards: - cpb = plugboards[plugboard_any_format_value] - if dev_name in cpb: - cpb = cpb[dev_name] - else: - cpb = None + cpb = find_plugboard(plugboard_save_to_disk_value, fmt, plugboards) # Leave this here for a while, in case problems arise. if cpb is not None: prints('Save-to-disk using plugboard:', fmt, cpb) diff --git a/src/calibre/library/server/content.py b/src/calibre/library/server/content.py index 0c3edd1627..08de4faecd 100644 --- a/src/calibre/library/server/content.py +++ b/src/calibre/library/server/content.py @@ -12,9 +12,14 @@ import cherrypy from calibre import fit_image, guess_type from calibre.utils.date import fromtimestamp from calibre.library.caches import SortKeyGenerator +from calibre.library.save_to_disk import find_plugboard + from calibre.utils.magick.draw import save_cover_data_to, Image, \ thumbnail as generate_thumbnail +plugboard_content_server_value = 'content_server' +plugboard_content_server_formats = ['epub'] + class CSSortKeyGenerator(SortKeyGenerator): def __init__(self, fields, fm, db_prefs): @@ -183,16 +188,30 @@ class ContentServer(object): if fmt is None: raise cherrypy.HTTPError(404, 'book: %d does not have format: %s'%(id, format)) if format == 'EPUB': + # Get the original metadata + mi = self.db.get_metadata(id, index_is_id=True) + + # Get any EPUB plugboards for the content server + plugboards = self.db.prefs.get('plugboards', {}) + cpb = find_plugboard(plugboard_content_server_value, + 'epub', plugboards) + if cpb: + # Transform the metadata via the plugboard + newmi = mi.deepcopy_metadata() + newmi.template_to_attribute(mi, cpb) + else: + newmi = mi + + # Write the updated file from tempfile import TemporaryFile from calibre.ebooks.metadata.meta import set_metadata raw = fmt.read() fmt = TemporaryFile() fmt.write(raw) fmt.seek(0) - set_metadata(fmt, self.db.get_metadata(id, index_is_id=True, - get_cover=True), - 'epub') + set_metadata(fmt, newmi, 'epub') fmt.seek(0) + mt = guess_type('dummy.'+format.lower())[0] if mt is None: mt = 'application/octet-stream'