From f14c1a65fdb6aa6f4e0a7f873b8d57bd11d3f96b Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 21 May 2011 09:35:45 -0400 Subject: [PATCH 01/10] Store: Amazon UK and DE, fix search url. --- src/calibre/gui2/store/amazon_de_plugin.py | 2 +- src/calibre/gui2/store/amazon_uk_plugin.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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): From 4a8f83305f56679a3ded6f108d4475789ec1c2bd Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 21 May 2011 18:39:54 -0400 Subject: [PATCH 02/10] Store: Add config widget for search accessible via the store plugin preferences. --- src/calibre/customize/builtins.py | 11 +++++ .../gui2/store/search/download_thread.py | 13 +++--- src/calibre/gui2/store/search/models.py | 6 +-- src/calibre/gui2/store/search/search.py | 45 +++++++++++++++---- 4 files changed, 58 insertions(+), 17 deletions(-) diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index ac26544207..8490630bb8 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, 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..059c1d73ed 100644 --- a/src/calibre/gui2/store/search/models.py +++ b/src/calibre/gui2/store/search/models.py @@ -33,7 +33,7 @@ class Matches(QAbstractItemModel): 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 +51,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 diff --git a/src/calibre/gui2/store/search/search.py b/src/calibre/gui2/store/search/search.py index f9ac45e707..906ba0b4ff 100644 --- a/src/calibre/gui2/store/search/search.py +++ b/src/calibre/gui2/store/search/search.py @@ -18,9 +18,6 @@ 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 +25,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,7 +48,7 @@ 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 @@ -128,7 +134,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() @@ -202,11 +208,32 @@ 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) + + # 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 config_finished(self): + self.load_settings() + + 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: From 4610ea2e7209640f1d7667cf46cd4bd6b6a9c43f Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 21 May 2011 18:58:30 -0400 Subject: [PATCH 03/10] Store: Enchance search dialog. --- src/calibre/gui2/store/search/search.py | 10 +- src/calibre/gui2/store/search/search.ui | 169 +++++++++++++++--------- 2 files changed, 108 insertions(+), 71 deletions(-) diff --git a/src/calibre/gui2/store/search/search.py b/src/calibre/gui2/store/search/search.py index 906ba0b4ff..e76f15c58e 100644 --- a/src/calibre/gui2/store/search/search.py +++ b/src/calibre/gui2/store/search/search.py @@ -54,15 +54,15 @@ class SearchDialog(QDialog, Ui_Dialog): # 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) diff --git a/src/calibre/gui2/store/search/search.ui b/src/calibre/gui2/store/search/search.ui index 8dd423baec..42f91de735 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 + 207 + 130 - + + + 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 + + + + + + Configure + + + + + + + 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 + + + From 44366b2c97f4153d4efd4d329a9765144785246d Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 21 May 2011 19:09:14 -0400 Subject: [PATCH 04/10] Store: Show number of results. --- src/calibre/gui2/store/search/models.py | 7 ++++++- src/calibre/gui2/store/search/search.py | 7 +++++++ src/calibre/gui2/store/search/search.ui | 6 +++--- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/store/search/models.py b/src/calibre/gui2/store/search/models.py index 059c1d73ed..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,6 +31,8 @@ def comparable_price(text): class Matches(QAbstractItemModel): + total_changed = pyqtSignal(int) + HEADERS = [_('Cover'), _('Title'), _('Price'), _('DRM'), _('Store')] HTML_COLS = (1, 4) @@ -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 e76f15c58e..18ea50f125 100644 --- a/src/calibre/gui2/store/search/search.py +++ b/src/calibre/gui2/store/search/search.py @@ -78,9 +78,11 @@ class SearchDialog(QDialog, Ui_Dialog): 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) @@ -221,6 +223,9 @@ class SearchDialog(QDialog, Ui_Dialog): 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): + pass + def config_finished(self): self.load_settings() @@ -249,6 +254,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 42f91de735..905620f688 100644 --- a/src/calibre/gui2/store/search/search.ui +++ b/src/calibre/gui2/store/search/search.ui @@ -82,8 +82,8 @@ 0 0 - 207 - 130 + 102 + 129 @@ -209,7 +209,7 @@ - + 0 From 0c315cdb1ea78634031ae603ca88bedeede359b3 Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 21 May 2011 19:22:05 -0400 Subject: [PATCH 05/10] Store: Allow search to be configured in the search dialog. --- src/calibre/gui2/store/search/search.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/store/search/search.py b/src/calibre/gui2/store/search/search.py index 18ea50f125..3e4edf37a2 100644 --- a/src/calibre/gui2/store/search/search.py +++ b/src/calibre/gui2/store/search/search.py @@ -9,10 +9,12 @@ __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 @@ -224,9 +226,23 @@ class SearchDialog(QDialog, Ui_Dialog): self.details_thread_count = self.config.get('details_thread_count', 4) def do_config(self): - pass + 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_finished(self): + def config_changed(self): self.load_settings() self.search_pool.set_thread_count(self.search_thread_count) From 894affa1b40efd9be20d5fd84f0f4be4ef5b6d3f Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 21 May 2011 19:38:48 -0400 Subject: [PATCH 06/10] Store: search dialog, change button. Add open external as a config option. --- src/calibre/gui2/store/search/search.py | 10 +++++++++- src/calibre/gui2/store/search/search.ui | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/store/search/search.py b/src/calibre/gui2/store/search/search.py index 3e4edf37a2..c7c252034d 100644 --- a/src/calibre/gui2/store/search/search.py +++ b/src/calibre/gui2/store/search/search.py @@ -74,6 +74,7 @@ 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) @@ -200,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: @@ -217,7 +218,9 @@ class SearchDialog(QDialog, Ui_Dialog): 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) @@ -226,6 +229,10 @@ class SearchDialog(QDialog, Ui_Dialog): 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) @@ -245,6 +252,7 @@ class SearchDialog(QDialog, Ui_Dialog): 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) diff --git a/src/calibre/gui2/store/search/search.ui b/src/calibre/gui2/store/search/search.ui index 905620f688..0360fa5f98 100644 --- a/src/calibre/gui2/store/search/search.ui +++ b/src/calibre/gui2/store/search/search.ui @@ -164,9 +164,9 @@ - + - Configure + ... From ab55a25674d64426fa06877ec6b9c6a1bb87b8a9 Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 21 May 2011 21:17:27 -0400 Subject: [PATCH 07/10] Store: add missed files. Add more quick functions for listing stores and which ones are enabled. --- src/calibre/customize/ui.py | 23 ++- src/calibre/gui2/store/config/__init__.py | 0 .../gui2/store/config/search_widget.py | 45 +++++ .../gui2/store/config/search_widget.ui | 162 ++++++++++++++++++ src/calibre/gui2/store/config/store.py | 14 ++ src/calibre/gui2/ui.py | 4 +- 6 files changed, 243 insertions(+), 5 deletions(-) create mode 100644 src/calibre/gui2/store/config/__init__.py create mode 100644 src/calibre/gui2/store/config/search_widget.py create mode 100644 src/calibre/gui2/store/config/search_widget.ui create mode 100644 src/calibre/gui2/store/config/store.py 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/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..0e05514867 --- /dev/null +++ b/src/calibre/gui2/store/config/store.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember ' +__docformat__ = 'restructuredtext en' + +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/ui.py b/src/calibre/gui2/ui.py index 122bfc81b4..df0e20e1bc 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, enabled_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 enabled_store_plugins(): if self.opts.ignore_plugins and store.plugin_path is not None: continue try: From 8ae27a865f2d14bbe13bc30ec7ee5c1036bfe26c Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 21 May 2011 21:21:53 -0400 Subject: [PATCH 08/10] ... --- src/calibre/gui2/store/config/store.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/calibre/gui2/store/config/store.py b/src/calibre/gui2/store/config/store.py index 0e05514867..ddc24870bd 100644 --- a/src/calibre/gui2/store/config/store.py +++ b/src/calibre/gui2/store/config/store.py @@ -6,6 +6,10 @@ __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() From bc8ea972e337544d476c657f16e9328de03e5395 Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 21 May 2011 22:12:00 -0400 Subject: [PATCH 09/10] Store: Fix google books leaving out some books. Add meta info to store plugins. --- src/calibre/customize/builtins.py | 111 ++++++++++++++++++ src/calibre/gui2/store/google_books_plugin.py | 2 +- 2 files changed, 112 insertions(+), 1 deletion(-) diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 8490630bb8..c085185a6e 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -1108,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'] plugins += [ StoreArchiveOrgStore, 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 From 957155ea4b7530097b11f04bf982f4f86478be8f Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 21 May 2011 22:27:32 -0400 Subject: [PATCH 10/10] Store: Fix change to function name change. --- src/calibre/gui2/ui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index df0e20e1bc..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, enabled_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 enabled_store_plugins(): + for store in available_store_plugins(): if self.opts.ignore_plugins and store.plugin_path is not None: continue try: