Make the metadata download plugins customizable

This commit is contained in:
Kovid Goyal 2009-11-21 09:07:12 -07:00
parent 87c7c91ab2
commit 69fc173ff5
5 changed files with 159 additions and 23 deletions

View File

@ -101,8 +101,6 @@ def metadata_sources(metadata_type='basic', customize=True, isbndb_key=None):
plugin.site_customization = customization.get(plugin.name, None)
if plugin.name == 'IsbnDB' and isbndb_key is not None:
plugin.site_customization = isbndb_key
if not plugin.is_ok():
continue
yield plugin
def get_isbndb_key():

View File

@ -9,9 +9,11 @@ from threading import Thread
from calibre import prints
from calibre.utils.config import OptionParser
from calibre.utils.logging import default_log
from calibre.ebooks.metadata import MetaInformation
from calibre.customize import Plugin
metadata_config = None
class MetadataSource(Plugin):
author = 'Kovid Goyal'
@ -23,11 +25,17 @@ class MetadataSource(Plugin):
#: tags/rating/reviews/etc.
metadata_type = 'basic'
#: If not None, the customization dialog will allow for string
#: based customization as well the default customization. The
#: string customization will be saved in the site_customization
#: member.
string_customization_help = None
type = _('Metadata download')
def __call__(self, title, author, publisher, isbn, verbose, log=None,
extra=None):
self.worker = Thread(target=self.fetch)
self.worker = Thread(target=self._fetch)
self.worker.daemon = True
self.title = title
self.verbose = verbose
@ -39,23 +47,87 @@ class MetadataSource(Plugin):
self.exception, self.tb, self.results = None, None, []
self.worker.start()
def _fetch(self):
try:
self.fetch()
if self.results:
c = self.config_store().get(self.name, {})
res = self.results
if isinstance(res, MetaInformation):
res = [res]
for mi in res:
if not c.get('rating', True):
mi.rating = None
if not c.get('comments', True):
mi.comments = None
if not c.get('tags', True):
mi.tags = []
except Exception, e:
self.exception = e
self.tb = traceback.format_exc()
def fetch(self):
'''
All the actual work is done here.
'''
raise NotImplementedError
def is_ok(self):
'''
Used to check if the plugin has been correctly customized.
For example: The isbndb plugin checks to see if the site_customization
has been set with an isbndb.com access key.
'''
return True
def join(self):
return self.worker.join()
def is_customizable(self):
return True
def config_store(self):
global metadata_config
if metadata_config is None:
from calibre.utils.config import XMLConfig
metadata_config = XMLConfig('plugins/metadata_download')
return metadata_config
def config_widget(self):
from PyQt4.Qt import QWidget, QVBoxLayout, QLabel, Qt, QLineEdit, \
QCheckBox
from calibre.customize.ui import config
w = QWidget()
w._layout = QVBoxLayout(w)
w.setLayout(w._layout)
if self.string_customization_help is not None:
w._sc_label = QLabel(self.string_customization_help, w)
w._layout.addWidget(w._sc_label)
customization = config['plugin_customization']
def_sc = customization.get(self.name, '')
if not def_sc:
def_sc = ''
w._sc = QLineEdit(def_sc, w)
w._layout.addWidget(w._sc)
w._sc_label.setWordWrap(True)
w._sc_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse
| Qt.LinksAccessibleByKeyboard)
w._sc_label.setOpenExternalLinks(True)
c = self.config_store()
c = c.get(self.name, {})
for x, l in {'rating':_('ratings'), 'tags':_('tags'),
'comments':_('description/reviews')}.items():
cb = QCheckBox(_('Download %s from %s')%(l,
self.name))
setattr(w, '_'+x, cb)
cb.setChecked(c.get(x, True))
w._layout.addWidget(cb)
return w
def save_settings(self, w):
dl_settings = {}
for x in ('rating', 'tags', 'comments'):
dl_settings[x] = getattr(w, '_'+x).isChecked()
c = self.config_store()
c.set(self.name, dl_settings)
if hasattr(w, '_sc'):
sc = unicode(w._sc.text()).strip()
from calibre.customize.ui import customize_plugin
customize_plugin(self, sc)
class GoogleBooks(MetadataSource):
@ -102,14 +174,11 @@ class ISBNDB(MetadataSource):
self.exception = e
self.tb = traceback.format_exc()
def customization_help(self, gui=False):
@property
def string_customization_help(self):
ans = _('To use isbndb.com you must sign up for a %sfree account%s '
'and enter your access key below.')
if gui:
ans = '<p>'+ans%('<a href="http://www.isbndb.com">', '</a>')
else:
ans = ans.replace('%s', '')
return ans
return '<p>'+ans%('<a href="http://www.isbndb.com">', '</a>')
class Amazon(MetadataSource):

View File

@ -81,7 +81,7 @@ Device Integration
What devices does |app| support?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
At the moment |app| has full support for the SONY PRS 300/500/505/600/700, Cybook Gen 3/Opus, Amazon Kindle 1/2/DX, Netronix EB600, Ectaco Jetbook, BeBook/BeBook Mini, Irex Illiad/DR1000, Foxit eSlick, Android phones and the iPhone. In addition, using the :guilabel:`Save to disk` function you can use it with any ebook reader that exports itself as a USB disk.
At the moment |app| has full support for the SONY PRS 300/500/505/600/700, Cybook Gen 3/Opus, Amazon Kindle 1/2/DX, Netronix EB600, Ectaco Jetbook, BeBook/BeBook Mini, Irex Illiad/DR1000, Foxit eSlick, PocketBook 360, Android phones and the iPhone. In addition, using the :guilabel:`Save to disk` function you can use it with any ebook reader that exports itself as a USB disk.
How can I help get my device supported in |app|?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -108,7 +108,7 @@ Metadata download plugins
.. class:: calibre.ebooks.metadata.fetch.MetadataSource
Represents a source to query for metadata. Subclasses must implement
at least the fetch method and optionally the is_ok method.
at least the fetch method.
When :meth:`fetch` is called, the `self` object will have the following
useful attributes (each of which may be None)::
@ -124,8 +124,9 @@ Metadata download plugins
.. automember:: calibre.ebooks.metadata.fetch.MetadataSource.metadata_type
.. automember:: calibre.ebooks.metadata.fetch.MetadataSource.string_customization_help
.. automethod:: calibre.ebooks.metadata.fetch.MetadataSource.fetch
.. automethod:: calibre.ebooks.metadata.fetch.MetadataSource.is_ok

View File

@ -6,7 +6,7 @@ __docformat__ = 'restructuredtext en'
'''
Manage application-wide preferences.
'''
import os, re, cPickle, textwrap, traceback
import os, re, cPickle, textwrap, traceback, plistlib
from copy import deepcopy
from functools import partial
from optparse import OptionParser as _OptionParser
@ -34,9 +34,11 @@ else:
plugin_dir = os.path.join(config_dir, 'plugins')
CONFIG_DIR_MODE = 0700
def make_config_dir():
if not os.path.exists(plugin_dir):
os.makedirs(plugin_dir, mode=448) # 0700 == 448
os.makedirs(plugin_dir, mode=CONFIG_DIR_MODE)
def check_config_write_access():
return os.access(config_dir, os.W_OK) and os.access(config_dir, os.X_OK)
@ -552,6 +554,72 @@ class DynamicConfig(dict):
dynamic = DynamicConfig()
class XMLConfig(dict):
'''
Similar to :class:`DynamicConfig`, except that it uses an XML storage
backend instead of a pickle file.
See `http://docs.python.org/dev/library/plistlib.html`_ for the supported
data types.
'''
def __init__(self, rel_path_to_cf_file):
dict.__init__(self)
self.file_path = os.path.join(config_dir,
*(rel_path_to_cf_file.split('/')))
self.file_path = os.path.abspath(self.file_path)
if not self.file_path.endswith('.plist'):
self.file_path += '.plist'
self.refresh()
def refresh(self):
d = {}
if os.path.exists(self.file_path):
with ExclusiveFile(self.file_path) as f:
raw = f.read()
try:
d = plistlib.readPlistFromString(raw) if raw.strip() else {}
except SystemError:
pass
except:
import traceback
traceback.print_exc()
d = {}
self.clear()
self.update(d)
def __getitem__(self, key):
try:
ans = dict.__getitem__(self, key)
if isinstance(ans, plistlib.Data):
ans = ans.data
return ans
except KeyError:
return None
def __setitem__(self, key, val):
if isinstance(val, (bytes, str)):
val = plistlib.Data(val)
dict.__setitem__(self, key, val)
self.commit()
def set(self, key, val):
self.__setitem__(key, val)
def commit(self):
if hasattr(self, 'file_path') and self.file_path:
dpath = os.path.dirname(self.file_path)
if not os.path.exists(dpath):
os.makedirs(dpath, mode=CONFIG_DIR_MODE)
with ExclusiveFile(self.file_path) as f:
raw = plistlib.writePlistToString(self)
f.seek(0)
f.truncate()
f.write(raw)
def _prefs():
c = Config('global', 'calibre wide preferences')
c.add_opt('database_path',