Sync to trunk.

This commit is contained in:
John Schember 2009-11-11 12:06:12 -05:00
commit 30e5e8c23e
10 changed files with 168 additions and 59 deletions

View File

@ -1,13 +1,13 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import re import re
from calibre import strftime from calibre import strftime
from calibre.ebooks.BeautifulSoup import BeautifulSoup from calibre.ebooks.BeautifulSoup import BeautifulSoup
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class Newsweek(BasicNewsRecipe): class Newsweek(BasicNewsRecipe):
title = 'Newsweek' title = 'Newsweek'
__author__ = 'Kovid Goyal and Sujata Raman' __author__ = 'Kovid Goyal and Sujata Raman'
@ -23,12 +23,12 @@ class Newsweek(BasicNewsRecipe):
.issueDate{font-family:arial,helvetica,sans-serif; color:#73726C; font-size:x-small; font-style:italic;} .issueDate{font-family:arial,helvetica,sans-serif; color:#73726C; font-size:x-small; font-style:italic;}
h5{font-family:arial,helvetica,sans-serif; color:#73726C; font-size:x-small;} h5{font-family:arial,helvetica,sans-serif; color:#73726C; font-size:x-small;}
h6{font-family:arial,helvetica,sans-serif; color:#73726C; font-size:x-small;} h6{font-family:arial,helvetica,sans-serif; color:#73726C; font-size:x-small;}
.story{font-family:georgia,sans-serif ; color:#363636;} .story{font-family:georgia,sans-serif ;color:black;}
.photoCredit{color:#999999; font-family:Arial,Helvetica,sans-serif;font-size:x-small;} .photoCredit{color:#999999; font-family:Arial,Helvetica,sans-serif;font-size:x-small;}
.photoCaption{color:#0A0A09;font-family:Arial,Helvetica,sans-serif;font-size:x-small;} .photoCaption{color:#0A0A09;font-family:Arial,Helvetica,sans-serif;font-size:x-small;}
.fwArticle{font-family:Arial,Helvetica,sans-serif;font-size:x-small;font-weight:bold;} .fwArticle{font-family:Arial,Helvetica,sans-serif;font-size:x-small;font-weight:bold;}
''' '''
encoding = 'utf-8' encoding = 'utf-8'
language = 'en' language = 'en'
@ -44,7 +44,7 @@ class Newsweek(BasicNewsRecipe):
dict(name='li', attrs={'id':['slug_bigbox']}) dict(name='li', attrs={'id':['slug_bigbox']})
] ]
keep_only_tags = [{'class':['article HorizontalHeader', 'articlecontent','photoBox']}, ] keep_only_tags = [{'class':['article HorizontalHeader', 'articlecontent','photoBox']}, ]
recursions = 1 recursions = 1
match_regexps = [r'http://www.newsweek.com/id/\S+/page/\d+'] match_regexps = [r'http://www.newsweek.com/id/\S+/page/\d+']

View File

@ -374,7 +374,8 @@ from calibre.devices.eslick.driver import ESLICK
from calibre.devices.nuut2.driver import NUUT2 from calibre.devices.nuut2.driver import NUUT2
from calibre.devices.iriver.driver import IRIVER_STORY from calibre.devices.iriver.driver import IRIVER_STORY
plugins = [HTML2ZIP] from calibre.ebooks.metadata.fetch import GoogleBooks, ISBNDB
plugins = [HTML2ZIP, GoogleBooks, ISBNDB]
plugins += [ plugins += [
ComicInput, ComicInput,
EPUBInput, EPUBInput,

View File

@ -13,8 +13,9 @@ from calibre.customize.builtins import plugins as builtin_plugins
from calibre.constants import numeric_version as version, iswindows, isosx from calibre.constants import numeric_version as version, iswindows, isosx
from calibre.devices.interface import DevicePlugin from calibre.devices.interface import DevicePlugin
from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata import MetaInformation
from calibre.ebooks.metadata.fetch import MetadataSource
from calibre.utils.config import make_config_dir, Config, ConfigProxy, \ from calibre.utils.config import make_config_dir, Config, ConfigProxy, \
plugin_dir, OptionParser plugin_dir, OptionParser, prefs
platform = 'linux' platform = 'linux'
@ -89,6 +90,33 @@ def output_profiles():
if isinstance(plugin, OutputProfile): if isinstance(plugin, OutputProfile):
yield plugin yield plugin
def metadata_sources(customize=True, isbndb_key=None):
for plugin in _initialized_plugins:
if isinstance(plugin, MetadataSource):
if is_disabled(plugin):
continue
if customize:
customization = config['plugin_customization']
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():
return config['plugin_customization'].get('IsbnDB', None)
def set_isbndb_key(key):
for plugin in _initialized_plugins:
if plugin.name == 'IsbnDB':
return customize_plugin(plugin, key)
def migrate_isbndb_key():
key = prefs['isbndb_com_key']
if key:
prefs.set('isbndb_com_key', '')
set_isbndb_key(key)
def reread_filetype_plugins(): def reread_filetype_plugins():
global _on_import global _on_import

View File

@ -8,46 +8,72 @@ from threading import Thread
from calibre import preferred_encoding from calibre import preferred_encoding
from calibre.utils.config import OptionParser from calibre.utils.config import OptionParser
from calibre.utils.logging import default_log
class FetchGoogle(Thread): from calibre.customize import Plugin
name = 'Google Books'
def __init__(self, title, author, publisher, isbn, verbose): class MetadataSource(Plugin):
author = 'Kovid Goyal'
supported_platforms = ['windows', 'osx', 'linux']
type = _('Metadata download')
def __call__(self, title, author, publisher, isbn, verbose, log=None,
extra=None):
self.worker = Thread(target=self.fetch)
self.worker.daemon = True
self.title = title self.title = title
self.verbose = verbose self.verbose = verbose
self.author = author self.author = author
self.publisher = publisher self.publisher = publisher
self.isbn = isbn self.isbn = isbn
Thread.__init__(self, None) self.log = log if log is not None else default_log
self.daemon = True self.extra = extra
self.exception, self.tb = None, None self.exception, self.tb, self.results = None, None, []
self.worker.start()
def run(self): 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()
class GoogleBooks(MetadataSource):
name = 'Google Books'
def is_ok(self):
return bool(self.site_customization)
def fetch(self):
from calibre.ebooks.metadata.google_books import search from calibre.ebooks.metadata.google_books import search
try: try:
self.results = search(self.title, self.author, self.publisher, self.results = search(self.title, self.author, self.publisher,
self.isbn, max_results=10, self.isbn, max_results=10,
verbose=self.verbose) verbose=self.verbose)
except Exception, e: except Exception, e:
self.results = []
self.exception = e self.exception = e
self.tb = traceback.format_exc() self.tb = traceback.format_exc()
class FetchISBNDB(Thread): class ISBNDB(MetadataSource):
name = 'IsbnDB'
def __init__(self, title, author, publisher, isbn, verbose, key):
self.title = title
self.author = author
self.publisher = publisher
self.isbn = isbn
self.verbose = verbose
Thread.__init__(self, None)
self.daemon = True
self.exception, self.tb = None, None
self.key = key
def run(self): name = 'IsbnDB'
def fetch(self):
if not self.site_customization:
return
from calibre.ebooks.metadata.isbndb import option_parser, create_books from calibre.ebooks.metadata.isbndb import option_parser, create_books
args = ['isbndb'] args = ['isbndb']
if self.isbn: if self.isbn:
@ -61,15 +87,23 @@ class FetchISBNDB(Thread):
args.extend(['--publisher', self.publisher]) args.extend(['--publisher', self.publisher])
if self.verbose: if self.verbose:
args.extend(['--verbose']) args.extend(['--verbose'])
args.append(self.key) args.append(self.site_customization) # IsbnDb key
try: try:
opts, args = option_parser().parse_args(args) opts, args = option_parser().parse_args(args)
self.results = create_books(opts, args) self.results = create_books(opts, args)
except Exception, e: except Exception, e:
self.results = []
self.exception = e self.exception = e
self.tb = traceback.format_exc() self.tb = traceback.format_exc()
def customization_help(self, gui=False):
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
def result_index(source, result): def result_index(source, result):
if not result.isbn: if not result.isbn:
return -1 return -1
@ -90,16 +124,14 @@ def search(title=None, author=None, publisher=None, isbn=None, isbndb_key=None,
verbose=0): verbose=0):
assert not(title is None and author is None and publisher is None and \ assert not(title is None and author is None and publisher is None and \
isbn is None) isbn is None)
from calibre.customize.ui import metadata_sources, migrate_isbndb_key
migrate_isbndb_key()
if isbn is not None: if isbn is not None:
isbn = re.sub(r'[^a-zA-Z0-9]', '', isbn).upper() isbn = re.sub(r'[^a-zA-Z0-9]', '', isbn).upper()
fetchers = [FetchGoogle(title, author, publisher, isbn, verbose)] fetchers = list(metadata_sources(isbndb_key=isbndb_key))
if isbndb_key:
fetchers.append(FetchISBNDB(title, author, publisher, isbn, verbose,
isbndb_key))
for fetcher in fetchers: for fetcher in fetchers:
fetcher.start() fetcher(title, author, publisher, isbn, verbose)
for fetcher in fetchers: for fetcher in fetchers:
fetcher.join() fetcher.join()
for fetcher in fetchers[1:]: for fetcher in fetchers[1:]:
@ -131,7 +163,8 @@ def option_parser():
help='Maximum number of results to fetch') help='Maximum number of results to fetch')
parser.add_option('-k', '--isbndb-key', parser.add_option('-k', '--isbndb-key',
help=('The access key for your ISBNDB.com account. ' help=('The access key for your ISBNDB.com account. '
'Only needed if you want to search isbndb.com')) 'Only needed if you want to search isbndb.com '
'and you haven\'t customized the IsbnDB plugin.'))
parser.add_option('-v', '--verbose', default=0, action='count', parser.add_option('-v', '--verbose', default=0, action='count',
help='Be more verbose about errors') help='Be more verbose about errors')
return parser return parser

View File

@ -6,7 +6,7 @@ from PyQt4.Qt import QDialog, QListWidgetItem, QIcon, \
QDesktopServices, QVBoxLayout, QLabel, QPlainTextEdit, \ QDesktopServices, QVBoxLayout, QLabel, QPlainTextEdit, \
QStringListModel, QAbstractItemModel, QFont, \ QStringListModel, QAbstractItemModel, QFont, \
SIGNAL, QThread, Qt, QSize, QVariant, QUrl, \ SIGNAL, QThread, Qt, QSize, QVariant, QUrl, \
QModelIndex, QInputDialog, QAbstractTableModel, \ QModelIndex, QAbstractTableModel, \
QDialogButtonBox, QTabWidget, QBrush, QLineEdit, \ QDialogButtonBox, QTabWidget, QBrush, QLineEdit, \
QProgressDialog QProgressDialog
@ -550,15 +550,16 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
info_dialog(self, _('Plugin not customizable'), info_dialog(self, _('Plugin not customizable'),
_('Plugin: %s does not need customization')%plugin.name).exec_() _('Plugin: %s does not need customization')%plugin.name).exec_()
return return
config_dialog = QDialog(self)
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
v = QVBoxLayout(config_dialog)
config_dialog.connect(button_box, SIGNAL('accepted()'), config_dialog.accept)
config_dialog.connect(button_box, SIGNAL('rejected()'), config_dialog.reject)
config_dialog.setWindowTitle(_('Customize') + ' ' + plugin.name)
if hasattr(plugin, 'config_widget'): if hasattr(plugin, 'config_widget'):
config_dialog = QDialog(self)
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
config_dialog.connect(button_box, SIGNAL('accepted()'), config_dialog.accept)
config_dialog.connect(button_box, SIGNAL('rejected()'), config_dialog.reject)
config_widget = plugin.config_widget() config_widget = plugin.config_widget()
v = QVBoxLayout(config_dialog)
v.addWidget(config_widget) v.addWidget(config_widget)
v.addWidget(button_box) v.addWidget(button_box)
config_dialog.exec_() config_dialog.exec_()
@ -567,17 +568,28 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
plugin.save_settings(config_widget) plugin.save_settings(config_widget)
self._plugin_model.refresh_plugin(plugin) self._plugin_model.refresh_plugin(plugin)
else: else:
help = plugin.customization_help() help_text = plugin.customization_help(gui=True)
help_text = QLabel(help_text, config_dialog)
help_text.setWordWrap(True)
help_text.setTextInteractionFlags(Qt.LinksAccessibleByMouse
| Qt.LinksAccessibleByKeyboard)
help_text.setOpenExternalLinks(True)
v.addWidget(help_text)
sc = plugin_customization(plugin) sc = plugin_customization(plugin)
if not sc: if not sc:
sc = '' sc = ''
sc = sc.strip() sc = sc.strip()
text, ok = QInputDialog.getText(self, _('Customize %s')%plugin.name, sc = QLineEdit(sc, config_dialog)
help, QLineEdit.Normal, sc) v.addWidget(sc)
if ok: v.addWidget(button_box)
customize_plugin(plugin, unicode(text).strip()) config_dialog.exec_()
if config_dialog.result() == QDialog.Accepted:
sc = unicode(sc.text()).strip()
customize_plugin(plugin, sc)
self._plugin_model.refresh_plugin(plugin) self._plugin_model.refresh_plugin(plugin)
if op == 'remove': elif op == 'remove':
if remove_plugin(plugin): if remove_plugin(plugin):
self._plugin_model.populate() self._plugin_model.populate()
self._plugin_model.reset() self._plugin_model.reset()

View File

@ -13,8 +13,8 @@ from PyQt4.QtGui import QDialog, QItemSelectionModel
from calibre.gui2.dialogs.fetch_metadata_ui import Ui_FetchMetadata from calibre.gui2.dialogs.fetch_metadata_ui import Ui_FetchMetadata
from calibre.gui2 import error_dialog, NONE, info_dialog from calibre.gui2 import error_dialog, NONE, info_dialog
from calibre.gui2.widgets import ProgressIndicator from calibre.gui2.widgets import ProgressIndicator
from calibre.utils.config import prefs
from calibre import strftime from calibre import strftime
from calibre.customize.ui import get_isbndb_key, set_isbndb_key
class Fetcher(QThread): class Fetcher(QThread):
@ -101,7 +101,7 @@ class FetchMetadata(QDialog, Ui_FetchMetadata):
self.timeout = timeout self.timeout = timeout
QObject.connect(self.fetch, SIGNAL('clicked()'), self.fetch_metadata) QObject.connect(self.fetch, SIGNAL('clicked()'), self.fetch_metadata)
self.key.setText(prefs['isbndb_com_key']) self.key.setText(get_isbndb_key())
self.setWindowTitle(title if title else _('Unknown')) self.setWindowTitle(title if title else _('Unknown'))
self.isbn = isbn self.isbn = isbn
@ -128,7 +128,7 @@ class FetchMetadata(QDialog, Ui_FetchMetadata):
self.warning.setVisible(False) self.warning.setVisible(False)
key = str(self.key.text()) key = str(self.key.text())
if key: if key:
prefs['isbndb_com_key'] = key set_isbndb_key(key)
else: else:
key = None key = None
title = author = publisher = isbn = pubdate = None title = author = publisher = isbn = pubdate = None

View File

@ -27,7 +27,7 @@ from calibre.ebooks.metadata.library_thing import cover_from_isbn
from calibre import islinux from calibre import islinux
from calibre.ebooks.metadata.meta import get_metadata from calibre.ebooks.metadata.meta import get_metadata
from calibre.utils.config import prefs from calibre.utils.config import prefs
from calibre.customize.ui import run_plugins_on_import from calibre.customize.ui import run_plugins_on_import, get_isbndb_key
class CoverFetcher(QThread): class CoverFetcher(QThread):
@ -50,7 +50,7 @@ class CoverFetcher(QThread):
self.needs_isbn = True self.needs_isbn = True
return return
au = self.author if self.author else None au = self.author if self.author else None
key = prefs['isbndb_com_key'] key = get_isbndb_key()
if not key: if not key:
key = None key = None
results = search(title=self.title, author=au, results = search(title=self.title, author=au,

View File

@ -11,8 +11,8 @@ from Queue import Queue, Empty
from calibre.ebooks.metadata.fetch import search from calibre.ebooks.metadata.fetch import search
from calibre.utils.config import prefs
from calibre.ebooks.metadata.library_thing import cover_from_isbn from calibre.ebooks.metadata.library_thing import cover_from_isbn
from calibre.customize.ui import get_isbndb_key
class Worker(Thread): class Worker(Thread):
@ -64,7 +64,7 @@ class DownloadMetadata(Thread):
self.tb = traceback.format_exc() self.tb = traceback.format_exc()
def _run(self): def _run(self):
self.key = prefs['isbndb_com_key'] self.key = get_isbndb_key()
if not self.key: if not self.key:
self.key = None self.key = None
self.fetched_metadata = {} self.fetched_metadata = {}

View File

@ -117,3 +117,9 @@ Metadata plugins add the ability to read/write metadata from ebook files to |app
:hidden: :hidden:
plugins plugins
Metadata download plugins
----------------------------
Metadata download plugins add various sources that |app| uses to download metadata based on title/author/isbn etc. See :ref:`pluginsMetadataSource`
for details.

View File

@ -98,3 +98,32 @@ Metadata plugins
.. automember:: MetadataWriterPlugin.file_types .. automember:: MetadataWriterPlugin.file_types
.. automethod:: MetadataWriterPlugin.set_metadata .. automethod:: MetadataWriterPlugin.set_metadata
.. _pluginsMetadataSource:
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.
When :meth:`fetch` is called, the `self` object will have the following
useful attributes (each of which may be None)::
title, author, publisher, isbn, log, verbose and extra
Use these attributes to construct the search query. extra is reserved for
future use.
The fetch method must store the results in `self.results` as a list of
:class:`MetaInformation` objects. If there is an error, it should be stored
in `self.exception` and `self.tb` (for the traceback).
.. automethod:: calibre.ebooks.metadata.fetch.MetadataSource.fetch
.. automethod:: calibre.ebooks.metadata.fetch.MetadataSource.is_ok