diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index fd80f14b60..783a5e7f94 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -854,6 +854,17 @@ class ActionStore(InterfaceActionBase): name = 'Store' author = 'John Schember' actual_plugin = 'calibre.gui2.actions.store:StoreAction' + + def customization_help(self, gui=False): + return 'Customize the behavior of the store search.' + + def config_widget(self): + from calibre.gui2.store.config.store import config_widget as get_cw + return get_cw() + + def save_settings(self, config_widget): + from calibre.gui2.store.config.store import save_settings as save + save(config_widget) plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog, ActionConvert, ActionDelete, ActionEditMetadata, ActionView, @@ -1097,144 +1108,255 @@ class StoreAmazonKindleStore(StoreBase): name = 'Amazon Kindle' description = _('Kindle books from Amazon.') actual_plugin = 'calibre.gui2.store.amazon_plugin:AmazonKindleStore' + + drm_free_only = False + location = 'US' + formats = ['KINDLE'] class StoreAmazonDEKindleStore(StoreBase): name = 'Amazon DE Kindle' description = _('Kindle books from Amazon.de.') actual_plugin = 'calibre.gui2.store.amazon_de_plugin:AmazonDEKindleStore' + + drm_free_only = False + location = 'DE' + formats = ['KINDLE'] class StoreAmazonUKKindleStore(StoreBase): name = 'Amazon UK Kindle' description = _('Kindle books from Amazon.uk.') actual_plugin = 'calibre.gui2.store.amazon_uk_plugin:AmazonUKKindleStore' + + drm_free_only = False + location = 'UK' + formats = ['KINDLE'] class StoreArchiveOrgStore(StoreBase): name = 'Archive.org' description = _('Free Books : Download & Streaming : Ebook and Texts Archive : Internet Archive.') actual_plugin = 'calibre.gui2.store.archive_org_plugin:ArchiveOrgStore' + drm_free_only = True + location = 'US' + formats = ['DAISY', 'DJVU', 'EPUB', 'MOBI', 'PDF', 'TXT'] class StoreBaenWebScriptionStore(StoreBase): name = 'Baen WebScription' description = _('Ebooks for readers.') actual_plugin = 'calibre.gui2.store.baen_webscription_plugin:BaenWebScriptionStore' + + drm_free_only = True + location = 'US' + formats = ['EPUB', 'LIT', 'LRF', 'MOBI', 'RB', 'RTF', 'ZIP'] class StoreBNStore(StoreBase): name = 'Barnes and Noble' description = _('Books, Textbooks, eBooks, Toys, Games and More.') actual_plugin = 'calibre.gui2.store.bn_plugin:BNStore' + + drm_free_only = False + location = 'US' + formats = ['NOOK'] class StoreBeamEBooksDEStore(StoreBase): name = 'Beam EBooks DE' description = _('Der eBook Shop.') actual_plugin = 'calibre.gui2.store.beam_ebooks_de_plugin:BeamEBooksDEStore' + + drm_free_only = False + location = 'DE' + formats = ['MOBI', 'PDF'] class StoreBeWriteStore(StoreBase): name = 'BeWrite Books' description = _('Publishers of fine books.') actual_plugin = 'calibre.gui2.store.bewrite_plugin:BeWriteStore' + + drm_free_only = True + location = 'US' + formats = ['EPUB', 'MOBI', 'PDF'] class StoreDieselEbooksStore(StoreBase): name = 'Diesel eBooks' description = _('World Famous eBook Store.') actual_plugin = 'calibre.gui2.store.diesel_ebooks_plugin:DieselEbooksStore' + + drm_free_only = False + location = 'US' + formats = ['EPUB', 'PDF'] class StoreEbookscomStore(StoreBase): name = 'eBooks.com' description = _('The digital bookstore.') actual_plugin = 'calibre.gui2.store.ebooks_com_plugin:EbookscomStore' + + drm_free_only = False + location = 'US' + formats = ['EPUB', 'LIT', 'MOBI', 'PDF'] class StoreEPubBuyDEStore(StoreBase): name = 'EPUBBuy DE' description = _('EPUBReaders eBook Shop.') actual_plugin = 'calibre.gui2.store.epubbuy_de_plugin:EPubBuyDEStore' + + drm_free_only = True + location = 'DE' + formats = ['EPUB'] class StoreEHarlequinStore(StoreBase): name = 'eHarlequin' description = _('Entertain, enrich, inspire.') actual_plugin = 'calibre.gui2.store.eharlequin_plugin:EHarlequinStore' + + drm_free_only = False + location = 'US' + formats = ['EPUB', 'PDF'] class StoreFeedbooksStore(StoreBase): name = 'Feedbooks' description = _('Read anywhere.') actual_plugin = 'calibre.gui2.store.feedbooks_plugin:FeedbooksStore' + + drm_free_only = False + location = 'FR' + formats = ['EPUB', 'MOBI', 'PDF'] class StoreFoylesUKStore(StoreBase): name = 'Foyles UK' description = _('Foyles of London, online.') actual_plugin = 'calibre.gui2.store.foyles_uk_plugin:FoylesUKStore' + drm_free_only = False + location = 'UK' + formats = ['EPUB', 'PDF'] + class StoreGandalfStore(StoreBase): name = 'Gandalf' author = 'Tomasz Długosz' description = _('Zaczarowany świat książek') actual_plugin = 'calibre.gui2.store.gandalf_plugin:GandalfStore' + drm_free_only = False + location = 'PL' + formats = ['EPUB', 'PDF'] + class StoreGoogleBooksStore(StoreBase): name = 'Google Books' description = _('Google Books') actual_plugin = 'calibre.gui2.store.google_books_plugin:GoogleBooksStore' + + drm_free_only = False + location = 'US' + formats = ['EPUB', 'PDF', 'TXT'] class StoreGutenbergStore(StoreBase): name = 'Project Gutenberg' description = _('The first producer of free ebooks.') actual_plugin = 'calibre.gui2.store.gutenberg_plugin:GutenbergStore' + + drm_free_only = True + location = 'US' + formats = ['EPUB', 'HTML', 'MOBI', 'PDB', 'TXT'] class StoreKoboStore(StoreBase): name = 'Kobo' description = _('eReading: anytime. anyplace.') actual_plugin = 'calibre.gui2.store.kobo_plugin:KoboStore' + + drm_free_only = False + location = 'US' + formats = ['EPUB'] class StoreManyBooksStore(StoreBase): name = 'ManyBooks' description = _('The best ebooks at the best price: free!') actual_plugin = 'calibre.gui2.store.manybooks_plugin:ManyBooksStore' + + drm_free_only = True + location = 'US' + formats = ['EPUB', 'FB2', 'JAR', 'LIT', 'LRF', 'MOBI', 'PDB', 'PDF', 'RB', 'RTF', 'TCR', 'TXT', 'ZIP'] class StoreMobileReadStore(StoreBase): name = 'MobileRead' description = _('Ebooks handcrafted with the utmost care.') actual_plugin = 'calibre.gui2.store.mobileread.mobileread_plugin:MobileReadStore' + drm_free_only = True + location = 'CH' + formats = ['EPUB', 'IMP', 'LRF', 'LIT', 'MOBI', 'PDF'] + class StoreNextoStore(StoreBase): name = 'Nexto' author = 'Tomasz Długosz' description = _('Audiobooki mp3, ebooki, prasa - księgarnia internetowa.') actual_plugin = 'calibre.gui2.store.nexto_plugin:NextoStore' + + drm_free_only = False + location = 'PL' + formats = ['EPUB', 'PDF'] class StoreOpenLibraryStore(StoreBase): name = 'Open Library' description = _('One web page for every book.') actual_plugin = 'calibre.gui2.store.open_library_plugin:OpenLibraryStore' + + drm_free_only = True + location = ['US'] + formats = ['DAISY', 'DJVU', 'EPUB', 'MOBI', 'PDF', 'TXT'] class StoreOReillyStore(StoreBase): name = 'OReilly' description = _('DRM-Free tech ebooks.') actual_plugin = 'calibre.gui2.store.oreilly_plugin:OReillyStore' + + drm_free_only = True + location = 'US' + formats = ['APK', 'DAISY', 'EPUB', 'MOBI', 'PDF'] class StorePragmaticBookshelfStore(StoreBase): name = 'Pragmatic Bookshelf' description = _('The Pragmatic Bookshelf') actual_plugin = 'calibre.gui2.store.pragmatic_bookshelf_plugin:PragmaticBookshelfStore' + drm_free_only = True + location = 'US' + formats = ['EPUB', 'MOBI', 'PDF'] + class StoreSmashwordsStore(StoreBase): name = 'Smashwords' description = _('Your ebook. Your way.') actual_plugin = 'calibre.gui2.store.smashwords_plugin:SmashwordsStore' + + drm_free_only = True + location = 'US' + formats = ['EPUB', 'HTML', 'LRF', 'MOBI', 'PDB', 'RTF', 'TXT'] class StoreWaterstonesUKStore(StoreBase): name = 'Waterstones UK' description = _('Feel every word.') actual_plugin = 'calibre.gui2.store.waterstones_uk_plugin:WaterstonesUKStore' + + drm_free_only = False + location = 'UK' + formats = ['EPUB', 'PDF'] class StoreWeightlessBooksStore(StoreBase): name = 'Weightless Books' description = '(e)Books That Don\'t Weigh You Down.' actual_plugin = 'calibre.gui2.store.weightless_books_plugin:WeightlessBooksStore' + drm_free_only = True + location = 'US' + formats = ['EPUB', 'HTML', 'LIT', 'MOBI', 'PDF'] + class StoreWizardsTowerBooksStore(StoreBase): name = 'Wizards Tower Books' description = 'Wizard\'s Tower Press.' actual_plugin = 'calibre.gui2.store.wizards_tower_books_plugin:WizardsTowerBooksStore' + + drm_free_only = True + location = 'UK' + formats = ['EPUB', 'MOBI'] class StoreWoblinkStore(StoreBase): name = 'Woblink' diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py index e955336d3f..0a21b0b42e 100644 --- a/src/calibre/customize/ui.py +++ b/src/calibre/customize/ui.py @@ -216,9 +216,26 @@ def store_plugins(): customization = config['plugin_customization'] for plugin in _initialized_plugins: if isinstance(plugin, Store): - if not is_disabled(plugin): - plugin.site_customization = customization.get(plugin.name, '') - yield plugin + plugin.site_customization = customization.get(plugin.name, '') + yield plugin + +def available_store_plugins(): + for plugin in store_plugins(): + if not is_disabled(plugin): + yield plugin + +def stores(): + stores = set([]) + for plugin in store_plugins(): + stores.add(plugin.name) + return stores + +def available_stores(): + stores = set([]) + for plugin in available_store_plugins(): + stores.add(plugin.name) + return stores + # }}} # Metadata read/write {{{ diff --git a/src/calibre/gui2/store/amazon_de_plugin.py b/src/calibre/gui2/store/amazon_de_plugin.py index 782d6bf0ed..f7b17a2e83 100644 --- a/src/calibre/gui2/store/amazon_de_plugin.py +++ b/src/calibre/gui2/store/amazon_de_plugin.py @@ -16,7 +16,7 @@ class AmazonDEKindleStore(AmazonKindleStore): For comments on the implementation, please see amazon_plugin.py ''' - search_url = 'http://www.amazon.de/s/url=search-alias%3Ddigital-text&field-keywords=' + search_url = 'http://www.amazon.de/s/?url=search-alias%3Ddigital-text&field-keywords=' details_url = 'http://amazon.de/dp/' drm_search_text = u'Gleichzeitige Verwendung von Geräten' drm_free_text = u'Keine Einschränkung' diff --git a/src/calibre/gui2/store/amazon_uk_plugin.py b/src/calibre/gui2/store/amazon_uk_plugin.py index 770db6cfd9..9544add17c 100644 --- a/src/calibre/gui2/store/amazon_uk_plugin.py +++ b/src/calibre/gui2/store/amazon_uk_plugin.py @@ -17,7 +17,7 @@ class AmazonUKKindleStore(AmazonKindleStore): For comments on the implementation, please see amazon_plugin.py ''' - search_url = 'http://www.amazon.co.uk/s/url=search-alias%3Ddigital-text&field-keywords=' + search_url = 'http://www.amazon.co.uk/s/?url=search-alias%3Ddigital-text&field-keywords=' details_url = 'http://amazon.co.uk/dp/' def open(self, parent=None, detail_item=None, external=False): diff --git a/src/calibre/gui2/store/config/__init__.py b/src/calibre/gui2/store/config/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/calibre/gui2/store/config/search_widget.py b/src/calibre/gui2/store/config/search_widget.py new file mode 100644 index 0000000000..43e911a432 --- /dev/null +++ b/src/calibre/gui2/store/config/search_widget.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember ' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import QWidget + +from calibre.gui2 import JSONConfig +from calibre.gui2.store.config.search_widget_ui import Ui_Form + +class StoreConfigWidget(QWidget, Ui_Form): + + def __init__(self, config=None): + QWidget.__init__(self) + self.setupUi(self) + + self.config = JSONConfig('store/search') if not config else config + + # These default values should be the same as in + # calibre.gui2.store.search.search:SearchDialog.load_settings + # Seconds + self.opt_timeout.setValue(self.config.get('timeout', 75)) + self.opt_hang_time.setValue(self.config.get('hang_time', 75)) + + self.opt_max_results.setValue(self.config.get('max_results', 10)) + self.opt_open_external.setChecked(self.config.get('open_external', True)) + + # Number of threads to run for each type of operation + self.opt_search_thread_count.setValue(self.config.get('search_thread_count', 4)) + self.opt_cache_thread_count.setValue(self.config.get('cache_thread_count', 2)) + self.opt_cover_thread_count.setValue(self.config.get('cover_thread_count', 2)) + self.opt_details_thread_count.setValue(self.config.get('details_thread_count', 4)) + + def save_settings(self): + self.config['timeout'] = self.opt_timeout.value() + self.config['hang_time'] = self.opt_hang_time.value() + self.config['max_results'] = self.opt_max_results.value() + self.config['open_external'] = self.opt_open_external.isChecked() + self.config['search_thread_count'] = self.opt_search_thread_count.value() + self.config['cache_thread_count'] = self.opt_cache_thread_count.value() + self.config['cover_thread_count'] = self.opt_cover_thread_count.value() + self.config['details_thread_count'] = self.opt_details_thread_count.value() diff --git a/src/calibre/gui2/store/config/search_widget.ui b/src/calibre/gui2/store/config/search_widget.ui new file mode 100644 index 0000000000..a73aae3ea5 --- /dev/null +++ b/src/calibre/gui2/store/config/search_widget.ui @@ -0,0 +1,162 @@ + + + Form + + + + 0 + 0 + 465 + 396 + + + + Form + + + + + + Time + + + + + + Number of seconds to wait for a store to respond + + + + + + + 1 + + + + + + + Number of seconds to let a store process results + + + + + + + 1 + + + 99 + + + 1 + + + 1 + + + + + + + + + + Display + + + + + + Maximum number of results to show per store + + + + + + + 1 + + + + + + + Open search result in system browser + + + + + + + + + + Threads + + + + + + Number of search threads to use + + + + + + + 1 + + + + + + + Number of cache update threads to use + + + + + + + 1 + + + + + + + Number of conver download threads to use + + + + + + + 1 + + + + + + + Number of details threads to use + + + + + + + 1 + + + + + + + + + + + diff --git a/src/calibre/gui2/store/config/store.py b/src/calibre/gui2/store/config/store.py new file mode 100644 index 0000000000..ddc24870bd --- /dev/null +++ b/src/calibre/gui2/store/config/store.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember ' +__docformat__ = 'restructuredtext en' + +''' +Config widget access functions for configuring the store action. +''' + +def config_widget(): + from calibre.gui2.store.config.search_widget import StoreConfigWidget + return StoreConfigWidget() + +def save_settings(config_widget): + config_widget.save_settings() diff --git a/src/calibre/gui2/store/google_books_plugin.py b/src/calibre/gui2/store/google_books_plugin.py index 6db0cc10b8..938ca70664 100644 --- a/src/calibre/gui2/store/google_books_plugin.py +++ b/src/calibre/gui2/store/google_books_plugin.py @@ -51,7 +51,7 @@ class GoogleBooksStore(BasicStoreConfig, StorePlugin): title = ''.join(data.xpath('.//h3/a//text()')) authors = data.xpath('.//span[@class="gl"]//a//text()') - if authors[-1].strip().lower() == 'preview': + if authors[-1].strip().lower() in ('preview', 'read'): authors = authors[:-1] else: continue diff --git a/src/calibre/gui2/store/search/download_thread.py b/src/calibre/gui2/store/search/download_thread.py index 97279d7773..1fc74a5748 100644 --- a/src/calibre/gui2/store/search/download_thread.py +++ b/src/calibre/gui2/store/search/download_thread.py @@ -22,7 +22,7 @@ class GenericDownloadThreadPool(object): at the end of the function. ''' - def __init__(self, thread_type, thread_count): + def __init__(self, thread_type, thread_count=1): self.thread_type = thread_type self.thread_count = thread_count @@ -30,6 +30,9 @@ class GenericDownloadThreadPool(object): self.results = Queue() self.threads = [] + def set_thread_count(self, thread_count): + self.thread_count = thread_count + def add_task(self): ''' This must be implemented in a sub class and this function @@ -92,8 +95,8 @@ class SearchThreadPool(GenericDownloadThreadPool): def __init__(self, thread_count): GenericDownloadThreadPool.__init__(self, SearchThread, thread_count) - def add_task(self, query, store_name, store_plugin, timeout): - self.tasks.put((query, store_name, store_plugin, timeout)) + def add_task(self, query, store_name, store_plugin, max_results, timeout): + self.tasks.put((query, store_name, store_plugin, max_results, timeout)) GenericDownloadThreadPool.add_task(self) @@ -112,8 +115,8 @@ class SearchThread(Thread): def run(self): 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): + query, store_name, store_plugin, max_results, timeout = self.tasks.get() + for res in store_plugin.search(query, max_results=max_results, timeout=timeout): if not self._run: return res.store_name = store_name diff --git a/src/calibre/gui2/store/search/models.py b/src/calibre/gui2/store/search/models.py index adc90e3b14..d7941480cc 100644 --- a/src/calibre/gui2/store/search/models.py +++ b/src/calibre/gui2/store/search/models.py @@ -9,7 +9,8 @@ __docformat__ = 'restructuredtext en' import re from operator import attrgetter -from PyQt4.Qt import (Qt, QAbstractItemModel, QVariant, QPixmap, QModelIndex, QSize) +from PyQt4.Qt import (Qt, QAbstractItemModel, QVariant, QPixmap, QModelIndex, QSize, + pyqtSignal) from calibre.gui2 import NONE from calibre.gui2.store.search_result import SearchResult @@ -30,10 +31,12 @@ def comparable_price(text): class Matches(QAbstractItemModel): + total_changed = pyqtSignal(int) + HEADERS = [_('Cover'), _('Title'), _('Price'), _('DRM'), _('Store')] HTML_COLS = (1, 4) - def __init__(self): + def __init__(self, cover_thread_count=2, detail_thread_count=4): QAbstractItemModel.__init__(self) self.DRM_LOCKED_ICON = QPixmap(I('drm-locked.png')).scaledToHeight(64, @@ -51,8 +54,8 @@ class Matches(QAbstractItemModel): self.matches = [] self.query = '' self.search_filter = SearchFilter() - self.cover_pool = CoverThreadPool(2) - self.details_pool = DetailsThreadPool(4) + self.cover_pool = CoverThreadPool(cover_thread_count) + self.details_pool = DetailsThreadPool(detail_thread_count) self.sort_col = 2 self.sort_order = Qt.AscendingOrder @@ -69,6 +72,7 @@ class Matches(QAbstractItemModel): self.query = '' self.cover_pool.abort() self.details_pool.abort() + self.total_changed.emit(self.rowCount()) self.reset() def add_result(self, result, store_plugin): @@ -101,6 +105,7 @@ class Matches(QAbstractItemModel): self.matches = list(self.search_filter.parse(self.query)) else: self.matches = list(self.search_filter.universal_set()) + self.total_changed.emit(self.rowCount()) self.sort(self.sort_col, self.sort_order, False) self.layoutChanged.emit() diff --git a/src/calibre/gui2/store/search/search.py b/src/calibre/gui2/store/search/search.py index f9ac45e707..c7c252034d 100644 --- a/src/calibre/gui2/store/search/search.py +++ b/src/calibre/gui2/store/search/search.py @@ -9,18 +9,17 @@ __docformat__ = 'restructuredtext en' import re from random import shuffle -from PyQt4.Qt import (Qt, QDialog, QTimer, QCheckBox, QVBoxLayout, QIcon, QWidget) +from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QTimer, QCheckBox, + QVBoxLayout, QIcon, QWidget) from calibre.gui2 import JSONConfig, info_dialog from calibre.gui2.progress_indicator import ProgressIndicator +from calibre.gui2.store.config.search_widget import StoreConfigWidget from calibre.gui2.store.search.adv_search_builder import AdvSearchBuilderDialog from calibre.gui2.store.search.download_thread import SearchThreadPool, \ CacheUpdateThreadPool from calibre.gui2.store.search.search_ui import Ui_Dialog -HANG_TIME = 75000 # milliseconds seconds -TIMEOUT = 75 # seconds - class SearchDialog(QDialog, Ui_Dialog): def __init__(self, istores, parent=None, query=''): @@ -28,13 +27,22 @@ class SearchDialog(QDialog, Ui_Dialog): self.setupUi(self) self.config = JSONConfig('store/search') - self.search_edit.initialize('store_search_search') + + # Loads variables that store various settings. + # This needs to be called soon in __init__ because + # the variables it sets up are used later. + self.load_settings() # We keep a cache of store plugins and reference them by name. self.store_plugins = istores - self.search_pool = SearchThreadPool(4) - self.cache_pool = CacheUpdateThreadPool(2) + + # Setup our worker threads. + self.search_pool = SearchThreadPool(self.search_thread_count) + self.cache_pool = CacheUpdateThreadPool(self.cache_thread_count) + self.results_view.model().cover_pool.set_thread_count(self.cover_thread_count) + self.results_view.model().details_pool.set_thread_count(self.details_thread_count) + # Check for results and hung threads. self.checker = QTimer() self.progress_checker = QTimer() @@ -42,21 +50,21 @@ class SearchDialog(QDialog, Ui_Dialog): # Update store caches silently. for p in self.store_plugins.values(): - self.cache_pool.add_task(p, 30) + self.cache_pool.add_task(p, self.timeout) # Add check boxes for each store so the user # can disable searching specific stores on a # per search basis. stores_check_widget = QWidget() - stores_group_layout = QVBoxLayout() - stores_check_widget.setLayout(stores_group_layout) + store_list_layout = QVBoxLayout() + stores_check_widget.setLayout(store_list_layout) for x in sorted(self.store_plugins.keys(), key=lambda x: x.lower()): cbox = QCheckBox(x) cbox.setChecked(False) - stores_group_layout.addWidget(cbox) + store_list_layout.addWidget(cbox) setattr(self, 'store_check_' + x, cbox) - stores_group_layout.addStretch() - self.stores_group.setWidget(stores_check_widget) + store_list_layout.addStretch() + self.store_list.setWidget(stores_check_widget) # Set the search query self.search_edit.setText(query) @@ -66,15 +74,18 @@ class SearchDialog(QDialog, Ui_Dialog): self.top_layout.addWidget(self.pi) self.adv_search_button.setIcon(QIcon(I('search.png'))) + self.configure.setIcon(QIcon(I('config.png'))) self.adv_search_button.clicked.connect(self.build_adv_search) self.search.clicked.connect(self.do_search) self.checker.timeout.connect(self.get_results) self.progress_checker.timeout.connect(self.check_progress) self.results_view.activated.connect(self.open_store) + self.results_view.model().total_changed.connect(self.update_book_total) self.select_all_stores.clicked.connect(self.stores_select_all) self.select_invert_stores.clicked.connect(self.stores_select_invert) self.select_none_stores.clicked.connect(self.stores_select_none) + self.configure.clicked.connect(self.do_config) self.finished.connect(self.dialog_closed) self.progress_checker.start(100) @@ -128,7 +139,7 @@ class SearchDialog(QDialog, Ui_Dialog): # Add plugins that the user has checked to the search pool's work queue. for n in store_names: if getattr(self, 'store_check_' + n).isChecked(): - self.search_pool.add_task(query, n, self.store_plugins[n], TIMEOUT) + self.search_pool.add_task(query, n, self.store_plugins[n], self.max_results, self.timeout) self.hang_check = 0 self.checker.start(100) self.pi.startAnimation() @@ -190,7 +201,7 @@ class SearchDialog(QDialog, Ui_Dialog): else: self.resize_columns() - self.open_external.setChecked(self.config.get('open_external', True)) + self.open_external.setChecked(self.should_open_external) store_check = self.config.get('store_checked', None) if store_check: @@ -202,11 +213,56 @@ class SearchDialog(QDialog, Ui_Dialog): self.results_view.model().sort_order = self.config.get('sort_order', Qt.AscendingOrder) self.results_view.header().setSortIndicator(self.results_view.model().sort_col, self.results_view.model().sort_order) + def load_settings(self): + # Seconds + self.timeout = self.config.get('timeout', 75) + # Milliseconds + self.hang_time = self.config.get('hang_time', 75) * 1000 + + self.max_results = self.config.get('max_results', 10) + self.should_open_external = self.config.get('open_external', True) + + # Number of threads to run for each type of operation + self.search_thread_count = self.config.get('search_thread_count', 4) + self.cache_thread_count = self.config.get('cache_thread_count', 2) + self.cover_thread_count = self.config.get('cover_thread_count', 2) + self.details_thread_count = self.config.get('details_thread_count', 4) + + def do_config(self): + # Save values that need to be synced between the dialog and the + # search widget. + self.config['open_external'] = self.open_external.isChecked() + + d = QDialog(self) + button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + v = QVBoxLayout(d) + button_box.accepted.connect(d.accept) + button_box.rejected.connect(d.reject) + d.setWindowTitle(_('Customize get books search')) + config_widget = StoreConfigWidget(self.config) + v.addWidget(config_widget) + v.addWidget(button_box) + + d.exec_() + + if d.result() == QDialog.Accepted: + config_widget.save_settings() + self.config_changed() + + def config_changed(self): + self.load_settings() + + self.open_external.setChecked(self.should_open_external) + self.search_pool.set_thread_count(self.search_thread_count) + self.cache_pool.set_thread_count(self.cache_thread_count) + self.results_view.model().cover_pool.set_thread_count(self.cover_thread_count) + self.results_view.model().details_pool.set_thread_count(self.details_thread_count) + def get_results(self): # We only want the search plugins to run # a maximum set amount of time before giving up. self.hang_check += 1 - if self.hang_check >= HANG_TIME: + if self.hang_check >= self.hang_time: self.search_pool.abort() self.checker.stop() else: @@ -222,6 +278,8 @@ class SearchDialog(QDialog, Ui_Dialog): if not self.search_pool.threads_running() and not self.results_view.model().has_results(): info_dialog(self, _('No matches'), _('Couldn\'t find any books matching your query.'), show=True, show_copy_button=False) + def update_book_total(self, total): + self.total.setText('%s' % total) def open_store(self, index): result = self.results_view.model().get_result(index) diff --git a/src/calibre/gui2/store/search/search.ui b/src/calibre/gui2/store/search/search.ui index 8dd423baec..0360fa5f98 100644 --- a/src/calibre/gui2/store/search/search.ui +++ b/src/calibre/gui2/store/search/search.ui @@ -6,8 +6,8 @@ 0 0 - 937 - 669 + 584 + 533 @@ -20,7 +20,7 @@ true - + @@ -66,8 +66,14 @@ Stores + + 0 + + + 0 + - + true @@ -76,15 +82,18 @@ 0 0 - 215 - 93 + 102 + 129 - + + + 0 + @@ -108,76 +117,104 @@ + + + + - - - Open a selected book in the system's web browser + + + + 1 + 0 + - - Open in &external browser + + + 0 + 0 + + + + true + + + + 32 + 32 + + + + false + + + false + + + false + + + true + + + false + + + + + + ... + + + + + + + Open a selected book in the system's web browser + + + Open in &external browser + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + - - - - 2 - 0 - - - - Qt::Horizontal - - - - Qt::Horizontal - - - - - 1 - 0 - - - - - 0 - 0 - - - - true - - - - 32 - 32 - - - - false - - - false - - - false - - - true - - - false - - - - + + + + Books: + + + + + + + 0 + + + diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 122bfc81b4..cf9f6ee610 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -23,7 +23,7 @@ from calibre.constants import __appname__, isosx from calibre.utils.config import prefs, dynamic from calibre.utils.ipc.server import Server from calibre.library.database2 import LibraryDatabase2 -from calibre.customize.ui import interface_actions, store_plugins +from calibre.customize.ui import interface_actions, available_store_plugins from calibre.gui2 import error_dialog, GetMetadata, open_url, \ gprefs, max_available_height, config, info_dialog, Dispatcher, \ question_dialog @@ -144,7 +144,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ def load_store_plugins(self): self.istores = OrderedDict() - for store in store_plugins(): + for store in available_store_plugins(): if self.opts.ignore_plugins and store.plugin_path is not None: continue try: