mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Move store plugins to use a GUI wrapper plugin to keep the plugin system happy. Fix segfault with cover download thread pool.
This commit is contained in:
parent
06e3186391
commit
84d8dc78c1
@ -581,60 +581,41 @@ class PreferencesPlugin(Plugin): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
class StoreBase(Plugin): # {{{
|
||||||
class StorePlugin(Plugin): # {{{
|
|
||||||
|
|
||||||
supported_platforms = ['windows', 'osx', 'linux']
|
supported_platforms = ['windows', 'osx', 'linux']
|
||||||
author = 'John Schember'
|
author = 'John Schember'
|
||||||
type = _('Store')
|
type = _('Store')
|
||||||
# This needs to be changed to (0, 8, 0)
|
# This needs to be changed to (0, 8, 0)
|
||||||
minimum_calibre_version = (0, 4, 118)
|
minimum_calibre_version = (0, 4, 118)
|
||||||
|
|
||||||
def open(self, gui, parent=None, detail_item=None, external=False):
|
actual_plugin = None
|
||||||
|
actual_plugin_object = None
|
||||||
|
|
||||||
|
def load_actual_plugin(self, gui):
|
||||||
'''
|
'''
|
||||||
Open the store.
|
This method must return the actual interface action plugin object.
|
||||||
|
|
||||||
:param gui: The main GUI. This will be used to have the job
|
|
||||||
system start downloading an item from the store.
|
|
||||||
|
|
||||||
:param parent: The parent of the store dialog. This is used
|
|
||||||
to create modal dialogs.
|
|
||||||
|
|
||||||
:param detail_item: A plugin specific reference to an item
|
|
||||||
in the store that the user should be shown.
|
|
||||||
|
|
||||||
:param external: When False open an internal dialog with the
|
|
||||||
store. When True open the users default browser to the store's
|
|
||||||
web site. :param:`detail_item` should still be respected when external
|
|
||||||
is True.
|
|
||||||
'''
|
'''
|
||||||
raise NotImplementedError()
|
mod, cls = self.actual_plugin.split(':')
|
||||||
|
self.actual_plugin_object = getattr(__import__(mod, fromlist=['1'], level=0), cls)(gui, self.name)
|
||||||
def search(self, query, max_results=10, timeout=60):
|
return self.actual_plugin_object
|
||||||
'''
|
|
||||||
Searches the store for items matching query. This should
|
def customization_help(self, gui=False):
|
||||||
return items as a generator.
|
if self.actual_plugin_object:
|
||||||
|
return self.actual_plugin_object.customization_help(gui)
|
||||||
|
else:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
:param query: The string query search with.
|
def config_widget(self):
|
||||||
:param max_results: The maximum number of results to return.
|
if self.actual_plugin_object:
|
||||||
:param timeout: The maximum amount of time in seconds to spend download the search results.
|
return self.actual_plugin_object.config_widget()
|
||||||
|
else:
|
||||||
:return: :class:`calibre.gui2.store.search_result.SearchResult` objects
|
raise NotImplementedError()
|
||||||
item_data is plugin specific and is used in :meth:`open` to open to a specifc place in the store.
|
|
||||||
'''
|
def save_settings(self, config_widget):
|
||||||
raise NotImplementedError()
|
if self.actual_plugin_object:
|
||||||
|
return self.actual_plugin_object.save_settings(config_widget)
|
||||||
def get_settings(self):
|
else:
|
||||||
'''
|
raise NotImplementedError()
|
||||||
This is only useful for plugins that implement
|
|
||||||
:attr:`config_widget` that is the only way to save
|
|
||||||
settings. This is used by plugins to get the saved
|
|
||||||
settings and apply when necessary.
|
|
||||||
|
|
||||||
:return: A dictionary filled with the settings used
|
|
||||||
by this plugin.
|
|
||||||
'''
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
import textwrap, os, glob, functools, re
|
import textwrap, os, glob, functools, re
|
||||||
from calibre import guess_type
|
from calibre import guess_type
|
||||||
from calibre.customize import FileTypePlugin, MetadataReaderPlugin, \
|
from calibre.customize import FileTypePlugin, MetadataReaderPlugin, \
|
||||||
MetadataWriterPlugin, PreferencesPlugin, InterfaceActionBase
|
MetadataWriterPlugin, PreferencesPlugin, InterfaceActionBase, StoreBase
|
||||||
from calibre.constants import numeric_version
|
from calibre.constants import numeric_version
|
||||||
from calibre.ebooks.metadata.archive import ArchiveExtract, get_cbz_metadata
|
from calibre.ebooks.metadata.archive import ArchiveExtract, get_cbz_metadata
|
||||||
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
||||||
@ -1042,12 +1042,32 @@ plugins += [GoogleBooks]
|
|||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
# Store plugins {{{
|
# Store plugins {{{
|
||||||
from calibre.gui2.store.amazon_plugin import AmazonKindleStore
|
class StoreAmazonKindleStore(StoreBase):
|
||||||
from calibre.gui2.store.gutenberg_plugin import GutenbergStore
|
name = 'Amazon Kindle'
|
||||||
from calibre.gui2.store.feedbooks_plugin import FeedbooksStore
|
description = _('Kindle books from Amazon')
|
||||||
from calibre.gui2.store.manybooks_plugin import ManyBooksStore
|
actual_plugin = 'calibre.gui2.store.amazon_plugin:AmazonKindleStore'
|
||||||
from calibre.gui2.store.smashwords_plugin import SmashwordsStore
|
|
||||||
|
|
||||||
plugins += [AmazonKindleStore, GutenbergStore, FeedbooksStore, ManyBooksStore, SmashwordsStore]
|
|
||||||
|
class StoreGutenbergStore(StoreBase):
|
||||||
|
name = 'Project Gutenberg'
|
||||||
|
description = _('The first producer of free ebooks.')
|
||||||
|
actual_plugin = 'calibre.gui2.store.gutenberg_plugin:GutenbergStore'
|
||||||
|
|
||||||
|
class StoreFeedbooksStore(StoreBase):
|
||||||
|
name = 'Feedbooks'
|
||||||
|
description = _('Read anywhere.')
|
||||||
|
actual_plugin = 'calibre.gui2.store.feedbooks_plugin:FeedbooksStore'
|
||||||
|
|
||||||
|
class StoreManyBooksStore(StoreBase):
|
||||||
|
name = 'ManyBooks'
|
||||||
|
description = _('The best ebooks at the best price: free!')
|
||||||
|
actual_plugin = 'calibre.gui2.store.manybooks_plugin:ManyBooksStore'
|
||||||
|
|
||||||
|
class StoreSmashwordsStore(StoreBase):
|
||||||
|
name = 'Smashwords'
|
||||||
|
description = _('Your ebook. Your way.')
|
||||||
|
actual_plugin = 'calibre.gui2.store.smashwords_plugin:SmashwordsStore'
|
||||||
|
|
||||||
|
plugins += [StoreAmazonKindleStore, StoreGutenbergStore, StoreFeedbooksStore, StoreManyBooksStore, StoreSmashwordsStore]
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
@ -8,7 +8,7 @@ from contextlib import closing
|
|||||||
from calibre.customize import Plugin, CatalogPlugin, FileTypePlugin, \
|
from calibre.customize import Plugin, CatalogPlugin, FileTypePlugin, \
|
||||||
MetadataReaderPlugin, MetadataWriterPlugin, \
|
MetadataReaderPlugin, MetadataWriterPlugin, \
|
||||||
InterfaceActionBase as InterfaceAction, \
|
InterfaceActionBase as InterfaceAction, \
|
||||||
PreferencesPlugin, StorePlugin
|
PreferencesPlugin, StoreBase
|
||||||
from calibre.customize.conversion import InputFormatPlugin, OutputFormatPlugin
|
from calibre.customize.conversion import InputFormatPlugin, OutputFormatPlugin
|
||||||
from calibre.customize.profiles import InputProfile, OutputProfile
|
from calibre.customize.profiles import InputProfile, OutputProfile
|
||||||
from calibre.customize.builtins import plugins as builtin_plugins
|
from calibre.customize.builtins import plugins as builtin_plugins
|
||||||
@ -283,7 +283,7 @@ def preferences_plugins():
|
|||||||
def store_plugins():
|
def store_plugins():
|
||||||
customization = config['plugin_customization']
|
customization = config['plugin_customization']
|
||||||
for plugin in _initialized_plugins:
|
for plugin in _initialized_plugins:
|
||||||
if isinstance(plugin, StorePlugin):
|
if isinstance(plugin, StoreBase):
|
||||||
if not is_disabled(plugin):
|
if not is_disabled(plugin):
|
||||||
plugin.site_customization = customization.get(plugin.name, '')
|
plugin.site_customization = customization.get(plugin.name, '')
|
||||||
yield plugin
|
yield plugin
|
||||||
|
@ -8,7 +8,6 @@ from functools import partial
|
|||||||
|
|
||||||
from PyQt4.Qt import Qt, QMenu, QToolButton, QDialog, QVBoxLayout
|
from PyQt4.Qt import Qt, QMenu, QToolButton, QDialog, QVBoxLayout
|
||||||
|
|
||||||
from calibre.customize.ui import store_plugins
|
|
||||||
from calibre.gui2.actions import InterfaceAction
|
from calibre.gui2.actions import InterfaceAction
|
||||||
|
|
||||||
class StoreAction(InterfaceAction):
|
class StoreAction(InterfaceAction):
|
||||||
@ -21,14 +20,14 @@ class StoreAction(InterfaceAction):
|
|||||||
self.store_menu = QMenu()
|
self.store_menu = QMenu()
|
||||||
self.store_menu.addAction(_('Search'), self.search)
|
self.store_menu.addAction(_('Search'), self.search)
|
||||||
self.store_menu.addSeparator()
|
self.store_menu.addSeparator()
|
||||||
for x in store_plugins():
|
for n, p in self.gui.istores.items():
|
||||||
self.store_menu.addAction(x.name, partial(self.open_store, x))
|
self.store_menu.addAction(n, partial(self.open_store, p))
|
||||||
self.qaction.setMenu(self.store_menu)
|
self.qaction.setMenu(self.store_menu)
|
||||||
|
|
||||||
def search(self):
|
def search(self):
|
||||||
from calibre.gui2.store.search import SearchDialog
|
from calibre.gui2.store.search import SearchDialog
|
||||||
sd = SearchDialog(self.gui, self.gui)
|
sd = SearchDialog(self.gui.istores, self.gui)
|
||||||
sd.exec_()
|
sd.exec_()
|
||||||
|
|
||||||
def open_store(self, store_plugin):
|
def open_store(self, store_plugin):
|
||||||
store_plugin.open(self.gui, self.gui)
|
store_plugin.open(self.gui)
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
__license__ = 'GPL 3'
|
||||||
|
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
class StorePlugin(object): # {{{
|
||||||
|
|
||||||
|
def __init__(self, gui, name):
|
||||||
|
self.gui = gui
|
||||||
|
self.name = name
|
||||||
|
self.base_plugin = None
|
||||||
|
|
||||||
|
def open(self, gui, parent=None, detail_item=None, external=False):
|
||||||
|
'''
|
||||||
|
Open the store.
|
||||||
|
|
||||||
|
:param gui: The main GUI. This will be used to have the job
|
||||||
|
system start downloading an item from the store.
|
||||||
|
|
||||||
|
:param parent: The parent of the store dialog. This is used
|
||||||
|
to create modal dialogs.
|
||||||
|
|
||||||
|
:param detail_item: A plugin specific reference to an item
|
||||||
|
in the store that the user should be shown.
|
||||||
|
|
||||||
|
:param external: When False open an internal dialog with the
|
||||||
|
store. When True open the users default browser to the store's
|
||||||
|
web site. :param:`detail_item` should still be respected when external
|
||||||
|
is True.
|
||||||
|
'''
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def search(self, query, max_results=10, timeout=60):
|
||||||
|
'''
|
||||||
|
Searches the store for items matching query. This should
|
||||||
|
return items as a generator.
|
||||||
|
|
||||||
|
:param query: The string query search with.
|
||||||
|
:param max_results: The maximum number of results to return.
|
||||||
|
:param timeout: The maximum amount of time in seconds to spend download the search results.
|
||||||
|
|
||||||
|
:return: :class:`calibre.gui2.store.search_result.SearchResult` objects
|
||||||
|
item_data is plugin specific and is used in :meth:`open` to open to a specifc place in the store.
|
||||||
|
'''
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def get_settings(self):
|
||||||
|
'''
|
||||||
|
This is only useful for plugins that implement
|
||||||
|
:attr:`config_widget` that is the only way to save
|
||||||
|
settings. This is used by plugins to get the saved
|
||||||
|
settings and apply when necessary.
|
||||||
|
|
||||||
|
:return: A dictionary filled with the settings used
|
||||||
|
by this plugin.
|
||||||
|
'''
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def do_genesis(self):
|
||||||
|
self.genesis()
|
||||||
|
|
||||||
|
def genesis(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def config_widget(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def save_settings(self, config_widget):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def customization_help(self, gui=False):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
# }}}
|
@ -14,15 +14,13 @@ from lxml import html
|
|||||||
from PyQt4.Qt import QUrl
|
from PyQt4.Qt import QUrl
|
||||||
|
|
||||||
from calibre import browser
|
from calibre import browser
|
||||||
from calibre.customize import StorePlugin
|
from calibre.gui2 import open_url
|
||||||
|
from calibre.gui2.store import StorePlugin
|
||||||
from calibre.gui2.store.search_result import SearchResult
|
from calibre.gui2.store.search_result import SearchResult
|
||||||
|
|
||||||
class AmazonKindleStore(StorePlugin):
|
class AmazonKindleStore(StorePlugin):
|
||||||
|
|
||||||
name = 'Amazon Kindle'
|
def open(self, parent=None, detail_item=None, external=False):
|
||||||
description = _('Kindle books from Amazon')
|
|
||||||
|
|
||||||
def open(self, gui, parent=None, detail_item=None):
|
|
||||||
'''
|
'''
|
||||||
Amazon comes with a number of difficulties.
|
Amazon comes with a number of difficulties.
|
||||||
|
|
||||||
@ -106,7 +104,6 @@ class AmazonKindleStore(StorePlugin):
|
|||||||
The best (I use the term lightly here) solution is to open Amazon.com
|
The best (I use the term lightly here) solution is to open Amazon.com
|
||||||
in the users default browser and set the affiliate id as part of the url.
|
in the users default browser and set the affiliate id as part of the url.
|
||||||
'''
|
'''
|
||||||
from calibre.gui2 import open_url
|
|
||||||
aff_id = {'tag': 'josbl0e-cpb-20'}
|
aff_id = {'tag': 'josbl0e-cpb-20'}
|
||||||
# Use Kovid's affiliate id 30% of the time.
|
# Use Kovid's affiliate id 30% of the time.
|
||||||
if random.randint(1, 10) in (1, 2, 3):
|
if random.randint(1, 10) in (1, 2, 3):
|
||||||
|
@ -10,18 +10,14 @@ from contextlib import closing
|
|||||||
from lxml import html
|
from lxml import html
|
||||||
|
|
||||||
from calibre import browser
|
from calibre import browser
|
||||||
from calibre.customize import StorePlugin
|
from calibre.gui2.store import StorePlugin
|
||||||
from calibre.gui2.store.search_result import SearchResult
|
from calibre.gui2.store.basic_config import BasicStoreConfig
|
||||||
|
from calibre.gui2.store.web_store_dialog import WebStoreDialog
|
||||||
|
|
||||||
class FeedbooksStore(StorePlugin):
|
class FeedbooksStore(BasicStoreConfig, StorePlugin):
|
||||||
|
|
||||||
name = 'Feedbooks'
|
def open(self, parent=None, detail_item=None, external=False):
|
||||||
description = _('Read anywhere.')
|
d = WebStoreDialog(self.gui, 'http://m.feedbooks.com/', parent, detail_item)
|
||||||
|
|
||||||
|
|
||||||
def open(self, gui, parent=None, detail_item=None):
|
|
||||||
from calibre.gui2.store.web_store_dialog import WebStoreDialog
|
|
||||||
d = WebStoreDialog(gui, 'http://m.feedbooks.com/', parent, detail_item)
|
|
||||||
d.setWindowTitle(self.name)
|
d.setWindowTitle(self.name)
|
||||||
d.set_tags(self.name + ',' + _('store'))
|
d.set_tags(self.name + ',' + _('store'))
|
||||||
d = d.exec_()
|
d = d.exec_()
|
||||||
@ -76,22 +72,3 @@ class FeedbooksStore(StorePlugin):
|
|||||||
s.detail_item = id.strip()
|
s.detail_item = id.strip()
|
||||||
|
|
||||||
yield s
|
yield s
|
||||||
|
|
||||||
def customization_help(self, gui=False):
|
|
||||||
return 'Customize the behavior of this store.'
|
|
||||||
|
|
||||||
def config_widget(self):
|
|
||||||
from calibre.gui2.store.basic_config_widget import BasicStoreConfigWidget
|
|
||||||
return BasicStoreConfigWidget(self)
|
|
||||||
|
|
||||||
def save_settings(self, config_widget):
|
|
||||||
from calibre.gui2.store.basic_config_widget import save_settings
|
|
||||||
save_settings(config_widget)
|
|
||||||
|
|
||||||
def get_settings(self):
|
|
||||||
from calibre.gui2 import gprefs
|
|
||||||
settings = {}
|
|
||||||
|
|
||||||
settings[self.name + '_tags'] = gprefs.get(self.name + '_tags', self.name + ', store, download')
|
|
||||||
|
|
||||||
return settings
|
|
||||||
|
@ -10,18 +10,16 @@ from contextlib import closing
|
|||||||
from lxml import html
|
from lxml import html
|
||||||
|
|
||||||
from calibre import browser
|
from calibre import browser
|
||||||
from calibre.customize import StorePlugin
|
from calibre.gui2.store import StorePlugin
|
||||||
|
from calibre.gui2.store.basic_config import BasicStoreConfig
|
||||||
from calibre.gui2.store.search_result import SearchResult
|
from calibre.gui2.store.search_result import SearchResult
|
||||||
|
from calibre.gui2.store.web_store_dialog import WebStoreDialog
|
||||||
|
|
||||||
class GutenbergStore(StorePlugin):
|
class GutenbergStore(BasicStoreConfig, StorePlugin):
|
||||||
|
|
||||||
name = 'Project Gutenberg'
|
|
||||||
description = _('The first producer of free ebooks.')
|
|
||||||
|
|
||||||
def open(self, gui, parent=None, detail_item=None):
|
def open(self, parent=None, detail_item=None, external=False):
|
||||||
settings = self.get_settings()
|
settings = self.get_settings()
|
||||||
from calibre.gui2.store.web_store_dialog import WebStoreDialog
|
d = WebStoreDialog(self.gui, 'http://m.gutenberg.org/', parent, detail_item)
|
||||||
d = WebStoreDialog(gui, 'http://m.gutenberg.org/', parent, detail_item)
|
|
||||||
d.setWindowTitle(self.name)
|
d.setWindowTitle(self.name)
|
||||||
d.set_tags(settings.get(self.name + '_tags', ''))
|
d.set_tags(settings.get(self.name + '_tags', ''))
|
||||||
d = d.exec_()
|
d = d.exec_()
|
||||||
@ -51,8 +49,9 @@ class GutenbergStore(StorePlugin):
|
|||||||
continue
|
continue
|
||||||
id = url.split('/')[-1]
|
id = url.split('/')[-1]
|
||||||
|
|
||||||
heading = ''.join(url_a.xpath('text()'))
|
url_a = html.fromstring(html.tostring(url_a))
|
||||||
title, _, author = heading.partition('by ')
|
heading = ''.join(url_a.xpath('//text()'))
|
||||||
|
title, _, author = heading.rpartition('by ')
|
||||||
author = author.split('-')[0]
|
author = author.split('-')[0]
|
||||||
price = '$0.00'
|
price = '$0.00'
|
||||||
|
|
||||||
@ -66,22 +65,3 @@ class GutenbergStore(StorePlugin):
|
|||||||
s.detail_item = '/ebooks/' + id.strip()
|
s.detail_item = '/ebooks/' + id.strip()
|
||||||
|
|
||||||
yield s
|
yield s
|
||||||
|
|
||||||
def customization_help(self, gui=False):
|
|
||||||
return 'Customize the behavior of this store.'
|
|
||||||
|
|
||||||
def config_widget(self):
|
|
||||||
from calibre.gui2.store.basic_config_widget import BasicStoreConfigWidget
|
|
||||||
return BasicStoreConfigWidget(self)
|
|
||||||
|
|
||||||
def save_settings(self, config_widget):
|
|
||||||
from calibre.gui2.store.basic_config_widget import save_settings
|
|
||||||
save_settings(config_widget)
|
|
||||||
|
|
||||||
def get_settings(self):
|
|
||||||
from calibre.gui2 import gprefs
|
|
||||||
settings = {}
|
|
||||||
|
|
||||||
settings[self.name + '_tags'] = gprefs.get(self.name + '_tags', self.name + ', store, download')
|
|
||||||
|
|
||||||
return settings
|
|
||||||
|
@ -11,18 +11,15 @@ from contextlib import closing
|
|||||||
from lxml import html
|
from lxml import html
|
||||||
|
|
||||||
from calibre import browser
|
from calibre import browser
|
||||||
from calibre.customize import StorePlugin
|
from calibre.gui2.store import StorePlugin
|
||||||
|
from calibre.gui2.store.basic_config import BasicStoreConfig
|
||||||
from calibre.gui2.store.search_result import SearchResult
|
from calibre.gui2.store.search_result import SearchResult
|
||||||
|
from calibre.gui2.store.web_store_dialog import WebStoreDialog
|
||||||
class ManyBooksStore(StorePlugin):
|
|
||||||
|
|
||||||
name = 'ManyBooks'
|
|
||||||
description = _('The best ebooks at the best price: free!')
|
|
||||||
|
|
||||||
|
|
||||||
def open(self, gui, parent=None, detail_item=None):
|
class ManyBooksStore(BasicStoreConfig, StorePlugin):
|
||||||
from calibre.gui2.store.web_store_dialog import WebStoreDialog
|
|
||||||
d = WebStoreDialog(gui, 'http://manybooks.net/', parent, detail_item)
|
def open(self, parent=None, detail_item=None, external=False):
|
||||||
|
d = WebStoreDialog(self.gui, 'http://manybooks.net/', parent, detail_item)
|
||||||
d.setWindowTitle(self.name)
|
d.setWindowTitle(self.name)
|
||||||
d.set_tags(self.name + ',' + _('store'))
|
d.set_tags(self.name + ',' + _('store'))
|
||||||
d = d.exec_()
|
d = d.exec_()
|
||||||
@ -55,8 +52,9 @@ class ManyBooksStore(StorePlugin):
|
|||||||
id = url.split('/')[-1]
|
id = url.split('/')[-1]
|
||||||
id = id.strip()
|
id = id.strip()
|
||||||
|
|
||||||
heading = ''.join(url_a.xpath('text()'))
|
url_a = html.fromstring(html.tostring(url_a))
|
||||||
title, _, author = heading.partition('by ')
|
heading = ''.join(url_a.xpath('//text()'))
|
||||||
|
title, _, author = heading.rpartition('by ')
|
||||||
author = author.split('-')[0]
|
author = author.split('-')[0]
|
||||||
price = '$0.00'
|
price = '$0.00'
|
||||||
|
|
||||||
@ -78,22 +76,3 @@ class ManyBooksStore(StorePlugin):
|
|||||||
s.detail_item = '/titles/' + id
|
s.detail_item = '/titles/' + id
|
||||||
|
|
||||||
yield s
|
yield s
|
||||||
|
|
||||||
def customization_help(self, gui=False):
|
|
||||||
return 'Customize the behavior of this store.'
|
|
||||||
|
|
||||||
def config_widget(self):
|
|
||||||
from calibre.gui2.store.basic_config_widget import BasicStoreConfigWidget
|
|
||||||
return BasicStoreConfigWidget(self)
|
|
||||||
|
|
||||||
def save_settings(self, config_widget):
|
|
||||||
from calibre.gui2.store.basic_config_widget import save_settings
|
|
||||||
save_settings(config_widget)
|
|
||||||
|
|
||||||
def get_settings(self):
|
|
||||||
from calibre.gui2 import gprefs
|
|
||||||
settings = {}
|
|
||||||
|
|
||||||
settings[self.name + '_tags'] = gprefs.get(self.name + '_tags', self.name + ', store, download')
|
|
||||||
|
|
||||||
return settings
|
|
||||||
|
@ -16,7 +16,6 @@ from PyQt4.Qt import Qt, QAbstractItemModel, QDialog, QTimer, QVariant, \
|
|||||||
QPushButton
|
QPushButton
|
||||||
|
|
||||||
from calibre import browser
|
from calibre import browser
|
||||||
from calibre.customize.ui import store_plugins
|
|
||||||
from calibre.gui2 import NONE
|
from calibre.gui2 import NONE
|
||||||
from calibre.gui2.store.search_ui import Ui_Dialog
|
from calibre.gui2.store.search_ui import Ui_Dialog
|
||||||
from calibre.utils.icu import sort_key
|
from calibre.utils.icu import sort_key
|
||||||
@ -29,17 +28,12 @@ COVER_DOWNLOAD_THREAD_TOTAL = 2
|
|||||||
|
|
||||||
class SearchDialog(QDialog, Ui_Dialog):
|
class SearchDialog(QDialog, Ui_Dialog):
|
||||||
|
|
||||||
def __init__(self, gui, *args):
|
def __init__(self, istores, *args):
|
||||||
QDialog.__init__(self, *args)
|
QDialog.__init__(self, *args)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
|
||||||
# We pass this on to the store plugins so they can
|
|
||||||
# tell the gui's job system to start downloading an
|
|
||||||
# item.
|
|
||||||
self.gui = gui
|
|
||||||
|
|
||||||
# We keep a cache of store plugins and reference them by name.
|
# We keep a cache of store plugins and reference them by name.
|
||||||
self.store_plugins = {}
|
self.store_plugins = istores
|
||||||
self.search_pool = SearchThreadPool(SearchThread, SEARCH_THREAD_TOTAL)
|
self.search_pool = SearchThreadPool(SearchThread, SEARCH_THREAD_TOTAL)
|
||||||
# Check for results and hung threads.
|
# Check for results and hung threads.
|
||||||
self.checker = QTimer()
|
self.checker = QTimer()
|
||||||
@ -53,12 +47,11 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
# per search basis.
|
# per search basis.
|
||||||
stores_group_layout = QVBoxLayout()
|
stores_group_layout = QVBoxLayout()
|
||||||
self.stores_group.setLayout(stores_group_layout)
|
self.stores_group.setLayout(stores_group_layout)
|
||||||
for x in store_plugins():
|
for x in self.store_plugins:
|
||||||
self.store_plugins[x.name] = x
|
cbox = QCheckBox(x)
|
||||||
cbox = QCheckBox(x.name)
|
|
||||||
cbox.setChecked(True)
|
cbox.setChecked(True)
|
||||||
stores_group_layout.addWidget(cbox)
|
stores_group_layout.addWidget(cbox)
|
||||||
setattr(self, 'store_check_' + x.name, cbox)
|
setattr(self, 'store_check_' + x, cbox)
|
||||||
stores_group_layout.addStretch()
|
stores_group_layout.addStretch()
|
||||||
|
|
||||||
self.search.clicked.connect(self.do_search)
|
self.search.clicked.connect(self.do_search)
|
||||||
@ -122,7 +115,7 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
self.checker.stop()
|
self.checker.stop()
|
||||||
else:
|
else:
|
||||||
# Stop the checker if not threads are running.
|
# Stop the checker if not threads are running.
|
||||||
if not self.search_pool.threads_running():
|
if not self.search_pool.threads_running() and not self.search_pool.has_tasks():
|
||||||
self.checker.stop()
|
self.checker.stop()
|
||||||
|
|
||||||
while self.search_pool.has_results():
|
while self.search_pool.has_results():
|
||||||
@ -132,7 +125,7 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
|
|
||||||
def open_store(self, index):
|
def open_store(self, index):
|
||||||
result = self.results_view.model().get_result(index)
|
result = self.results_view.model().get_result(index)
|
||||||
self.store_plugins[result.store_name].open(self.gui, self, result.detail_item)
|
self.store_plugins[result.store_name].open(self, result.detail_item)
|
||||||
|
|
||||||
def get_store_checks(self):
|
def get_store_checks(self):
|
||||||
'''
|
'''
|
||||||
@ -156,6 +149,10 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
def stores_select_none(self):
|
def stores_select_none(self):
|
||||||
for check in self.get_store_checks():
|
for check in self.get_store_checks():
|
||||||
check.setChecked(False)
|
check.setChecked(False)
|
||||||
|
|
||||||
|
def closeEvent(self, e):
|
||||||
|
self.model.closing()
|
||||||
|
QDialog.closeEvent(self, e)
|
||||||
|
|
||||||
|
|
||||||
class GenericDownloadThreadPool(object):
|
class GenericDownloadThreadPool(object):
|
||||||
@ -305,6 +302,9 @@ class Matches(QAbstractItemModel):
|
|||||||
self.matches = []
|
self.matches = []
|
||||||
self.cover_pool = CoverThreadPool(CoverThread, 2)
|
self.cover_pool = CoverThreadPool(CoverThread, 2)
|
||||||
self.cover_pool.start_threads()
|
self.cover_pool.start_threads()
|
||||||
|
|
||||||
|
def closing(self):
|
||||||
|
self.cover_pool.abort()
|
||||||
|
|
||||||
def clear_results(self):
|
def clear_results(self):
|
||||||
self.matches = []
|
self.matches = []
|
||||||
@ -315,7 +315,6 @@ class Matches(QAbstractItemModel):
|
|||||||
def add_result(self, result):
|
def add_result(self, result):
|
||||||
self.layoutAboutToBeChanged.emit()
|
self.layoutAboutToBeChanged.emit()
|
||||||
self.matches.append(result)
|
self.matches.append(result)
|
||||||
|
|
||||||
self.cover_pool.add_task(result, self.update_result)
|
self.cover_pool.add_task(result, self.update_result)
|
||||||
self.layoutChanged.emit()
|
self.layoutChanged.emit()
|
||||||
|
|
||||||
|
@ -12,22 +12,19 @@ from contextlib import closing
|
|||||||
from lxml import html
|
from lxml import html
|
||||||
|
|
||||||
from calibre import browser
|
from calibre import browser
|
||||||
from calibre.customize import StorePlugin
|
from calibre.gui2.store import StorePlugin
|
||||||
|
from calibre.gui2.store.basic_config import BasicStoreConfig
|
||||||
from calibre.gui2.store.search_result import SearchResult
|
from calibre.gui2.store.search_result import SearchResult
|
||||||
|
from calibre.gui2.store.web_store_dialog import WebStoreDialog
|
||||||
|
|
||||||
class SmashwordsStore(StorePlugin):
|
class SmashwordsStore(BasicStoreConfig, StorePlugin):
|
||||||
|
|
||||||
name = 'Smashwords'
|
def open(self, parent=None, detail_item=None, external=False):
|
||||||
description = _('Your ebook. Your way.')
|
|
||||||
|
|
||||||
|
|
||||||
def open(self, gui, parent=None, detail_item=None):
|
|
||||||
from calibre.gui2.store.web_store_dialog import WebStoreDialog
|
|
||||||
aff_id = 'usernone'
|
aff_id = 'usernone'
|
||||||
# Use Kovid's affiliate id 30% of the time.
|
# Use Kovid's affiliate id 30% of the time.
|
||||||
if random.randint(1, 10) in (1, 2, 3):
|
if random.randint(1, 10) in (1, 2, 3):
|
||||||
aff_id = 'kovidgoyal'
|
aff_id = 'kovidgoyal'
|
||||||
d = WebStoreDialog(gui, 'http://www.smashwords.com/?ref=%s' % aff_id, parent, detail_item)
|
d = WebStoreDialog(self.gui, 'http://www.smashwords.com/?ref=%s' % aff_id, parent, detail_item)
|
||||||
d.setWindowTitle(self.name)
|
d.setWindowTitle(self.name)
|
||||||
d.set_tags(self.name + ',' + _('store'))
|
d.set_tags(self.name + ',' + _('store'))
|
||||||
d = d.exec_()
|
d = d.exec_()
|
||||||
@ -78,22 +75,3 @@ class SmashwordsStore(StorePlugin):
|
|||||||
s.detail_item = '/books/view/' + id.strip()
|
s.detail_item = '/books/view/' + id.strip()
|
||||||
|
|
||||||
yield s
|
yield s
|
||||||
|
|
||||||
def customization_help(self, gui=False):
|
|
||||||
return 'Customize the behavior of this store.'
|
|
||||||
|
|
||||||
def config_widget(self):
|
|
||||||
from calibre.gui2.store.basic_config_widget import BasicStoreConfigWidget
|
|
||||||
return BasicStoreConfigWidget(self)
|
|
||||||
|
|
||||||
def save_settings(self, config_widget):
|
|
||||||
from calibre.gui2.store.basic_config_widget import save_settings
|
|
||||||
save_settings(config_widget)
|
|
||||||
|
|
||||||
def get_settings(self):
|
|
||||||
from calibre.gui2 import gprefs
|
|
||||||
settings = {}
|
|
||||||
|
|
||||||
settings[self.name + '_tags'] = gprefs.get(self.name + '_tags', self.name + ', store, download')
|
|
||||||
|
|
||||||
return settings
|
|
||||||
|
@ -22,7 +22,7 @@ from calibre.ptempfile import PersistentTemporaryFile
|
|||||||
from calibre.utils.config import prefs, dynamic
|
from calibre.utils.config import prefs, dynamic
|
||||||
from calibre.utils.ipc.server import Server
|
from calibre.utils.ipc.server import Server
|
||||||
from calibre.library.database2 import LibraryDatabase2
|
from calibre.library.database2 import LibraryDatabase2
|
||||||
from calibre.customize.ui import interface_actions
|
from calibre.customize.ui import interface_actions, store_plugins
|
||||||
from calibre.gui2 import error_dialog, GetMetadata, open_local_file, \
|
from calibre.gui2 import error_dialog, GetMetadata, open_local_file, \
|
||||||
gprefs, max_available_height, config, info_dialog, Dispatcher, \
|
gprefs, max_available_height, config, info_dialog, Dispatcher, \
|
||||||
question_dialog
|
question_dialog
|
||||||
@ -102,6 +102,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
self.device_connected = None
|
self.device_connected = None
|
||||||
self.gui_debug = gui_debug
|
self.gui_debug = gui_debug
|
||||||
self.iactions = OrderedDict()
|
self.iactions = OrderedDict()
|
||||||
|
# Actions
|
||||||
for action in interface_actions():
|
for action in interface_actions():
|
||||||
if opts.ignore_plugins and action.plugin_path is not None:
|
if opts.ignore_plugins and action.plugin_path is not None:
|
||||||
continue
|
continue
|
||||||
@ -114,11 +115,24 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
if action.plugin_path is None:
|
if action.plugin_path is None:
|
||||||
raise
|
raise
|
||||||
continue
|
continue
|
||||||
|
|
||||||
ac.plugin_path = action.plugin_path
|
ac.plugin_path = action.plugin_path
|
||||||
ac.interface_action_base_plugin = action
|
ac.interface_action_base_plugin = action
|
||||||
|
|
||||||
self.add_iaction(ac)
|
self.add_iaction(ac)
|
||||||
|
# Stores
|
||||||
|
self.istores = OrderedDict()
|
||||||
|
for store in store_plugins():
|
||||||
|
if opts.ignore_plugins and store.plugin_path is not None:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
st = self.init_istore(store)
|
||||||
|
self.add_istore(st)
|
||||||
|
except:
|
||||||
|
# Ignore errors in loading user supplied plugins
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
if store.plugin_path is None:
|
||||||
|
raise
|
||||||
|
continue
|
||||||
|
|
||||||
def init_iaction(self, action):
|
def init_iaction(self, action):
|
||||||
ac = action.load_actual_plugin(self)
|
ac = action.load_actual_plugin(self)
|
||||||
@ -126,6 +140,13 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
ac.interface_action_base_plugin = action
|
ac.interface_action_base_plugin = action
|
||||||
action.actual_iaction_plugin_loaded = True
|
action.actual_iaction_plugin_loaded = True
|
||||||
return ac
|
return ac
|
||||||
|
|
||||||
|
def init_istore(self, store):
|
||||||
|
st = store.load_actual_plugin(self)
|
||||||
|
st.plugin_path = store.plugin_path
|
||||||
|
st.base_plugin = store
|
||||||
|
store.actual_istore_plugin_loaded = True
|
||||||
|
return st
|
||||||
|
|
||||||
def add_iaction(self, ac):
|
def add_iaction(self, ac):
|
||||||
acmap = self.iactions
|
acmap = self.iactions
|
||||||
@ -134,6 +155,14 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
acmap[ac.name] = ac
|
acmap[ac.name] = ac
|
||||||
else:
|
else:
|
||||||
acmap[ac.name] = ac
|
acmap[ac.name] = ac
|
||||||
|
|
||||||
|
def add_istore(self, st):
|
||||||
|
stmap = self.istores
|
||||||
|
if st.name in stmap:
|
||||||
|
if st.priority >= stmap[st.name].priority:
|
||||||
|
stmap[st.name] = st
|
||||||
|
else:
|
||||||
|
stmap[st.name] = st
|
||||||
|
|
||||||
|
|
||||||
def initialize(self, library_path, db, listener, actions, show_gui=True):
|
def initialize(self, library_path, db, listener, actions, show_gui=True):
|
||||||
@ -155,6 +184,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
|
|
||||||
for ac in self.iactions.values():
|
for ac in self.iactions.values():
|
||||||
ac.do_genesis()
|
ac.do_genesis()
|
||||||
|
for st in self.istores.values():
|
||||||
|
st.do_genesis()
|
||||||
MainWindowMixin.__init__(self, db)
|
MainWindowMixin.__init__(self, db)
|
||||||
|
|
||||||
# Jobs Button {{{
|
# Jobs Button {{{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user