mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Sync to trunk.
This commit is contained in:
commit
1df045e236
@ -19,6 +19,13 @@
|
||||
# new recipes:
|
||||
# - title:
|
||||
|
||||
- version: 0.7.52
|
||||
date: 2011-03-25
|
||||
|
||||
bug fixes:
|
||||
- title: "Fixes a typo in 0.7.51 that broke the downloading of some news. Apologies."
|
||||
tickets: [742840]
|
||||
|
||||
- version: 0.7.51
|
||||
date: 2011-03-25
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1291143841(BasicNewsRecipe):
|
||||
title = u'Poughkeepsipe Journal'
|
||||
title = u'Poughkeepsie Journal'
|
||||
language = 'en'
|
||||
__author__ = 'weebl'
|
||||
oldest_article = 7
|
||||
|
@ -14,7 +14,7 @@ from setup.build_environment import HOST, PROJECT
|
||||
BASE_RSYNC = ['rsync', '-avz', '--delete']
|
||||
EXCLUDES = []
|
||||
for x in [
|
||||
'src/calibre/plugins', 'src/calibre/manual', 'src/calibre/trac', 'recipes',
|
||||
'src/calibre/plugins', 'src/calibre/manual', 'src/calibre/trac',
|
||||
'.bzr', '.build', '.svn', 'build', 'dist', 'imgsrc', '*.pyc', '*.pyo', '*.swp',
|
||||
'*.swo', 'format_docs']:
|
||||
EXCLUDES.extend(['--exclude', x])
|
||||
|
@ -2,7 +2,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = 'calibre'
|
||||
__version__ = '0.7.51'
|
||||
__version__ = '0.7.52'
|
||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
|
||||
import re, importlib
|
||||
|
@ -4,9 +4,22 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import os, sys, zipfile, importlib
|
||||
|
||||
from calibre.constants import numeric_version
|
||||
from calibre.constants import numeric_version, iswindows, isosx
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
|
||||
platform = 'linux'
|
||||
if iswindows:
|
||||
platform = 'windows'
|
||||
elif isosx:
|
||||
platform = 'osx'
|
||||
|
||||
|
||||
class PluginNotFound(ValueError):
|
||||
pass
|
||||
|
||||
class InvalidPlugin(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class Plugin(object): # {{{
|
||||
'''
|
||||
|
@ -2,17 +2,16 @@ from __future__ import with_statement
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import os, shutil, traceback, functools, sys, re
|
||||
from contextlib import closing
|
||||
import os, shutil, traceback, functools, sys
|
||||
|
||||
from calibre.customize import Plugin, CatalogPlugin, FileTypePlugin, \
|
||||
MetadataReaderPlugin, MetadataWriterPlugin, \
|
||||
InterfaceActionBase as InterfaceAction, \
|
||||
PreferencesPlugin
|
||||
from calibre.customize import (CatalogPlugin, FileTypePlugin, PluginNotFound,
|
||||
MetadataReaderPlugin, MetadataWriterPlugin,
|
||||
InterfaceActionBase as InterfaceAction,
|
||||
PreferencesPlugin, platform, InvalidPlugin)
|
||||
from calibre.customize.conversion import InputFormatPlugin, OutputFormatPlugin
|
||||
from calibre.customize.zipplugin import loader
|
||||
from calibre.customize.profiles import InputProfile, OutputProfile
|
||||
from calibre.customize.builtins import plugins as builtin_plugins
|
||||
from calibre.constants import numeric_version as version, iswindows, isosx
|
||||
from calibre.devices.interface import DevicePlugin
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
from calibre.ebooks.metadata.covers import CoverDownload
|
||||
@ -22,14 +21,6 @@ from calibre.utils.config import make_config_dir, Config, ConfigProxy, \
|
||||
from calibre.ebooks.epub.fix import ePubFixer
|
||||
from calibre.ebooks.metadata.sources.base import Source
|
||||
|
||||
platform = 'linux'
|
||||
if iswindows:
|
||||
platform = 'windows'
|
||||
elif isosx:
|
||||
platform = 'osx'
|
||||
|
||||
from zipfile import ZipFile
|
||||
|
||||
def _config():
|
||||
c = Config('customize')
|
||||
c.add_opt('plugins', default={}, help=_('Installed plugins'))
|
||||
@ -42,11 +33,6 @@ def _config():
|
||||
|
||||
config = _config()
|
||||
|
||||
class InvalidPlugin(ValueError):
|
||||
pass
|
||||
|
||||
class PluginNotFound(ValueError):
|
||||
pass
|
||||
|
||||
def find_plugin(name):
|
||||
for plugin in _initialized_plugins:
|
||||
@ -60,38 +46,7 @@ def load_plugin(path_to_zip_file): # {{{
|
||||
|
||||
:return: A :class:`Plugin` instance.
|
||||
'''
|
||||
#print 'Loading plugin from', path_to_zip_file
|
||||
if not os.access(path_to_zip_file, os.R_OK):
|
||||
raise PluginNotFound
|
||||
with closing(ZipFile(path_to_zip_file)) as zf:
|
||||
for name in zf.namelist():
|
||||
if name.lower().endswith('plugin.py'):
|
||||
locals = {}
|
||||
raw = zf.read(name)
|
||||
lines, encoding = raw.splitlines(), 'utf-8'
|
||||
cr = re.compile(r'coding[:=]\s*([-\w.]+)')
|
||||
raw = []
|
||||
for l in lines[:2]:
|
||||
match = cr.search(l)
|
||||
if match is not None:
|
||||
encoding = match.group(1)
|
||||
else:
|
||||
raw.append(l)
|
||||
raw += lines[2:]
|
||||
raw = '\n'.join(raw)
|
||||
raw = raw.decode(encoding)
|
||||
raw = re.sub('\r\n', '\n', raw)
|
||||
exec raw in locals
|
||||
for x in locals.values():
|
||||
if isinstance(x, type) and issubclass(x, Plugin) and \
|
||||
x.name != 'Trivial Plugin':
|
||||
if x.minimum_calibre_version > version or \
|
||||
platform not in x.supported_platforms:
|
||||
continue
|
||||
|
||||
return x
|
||||
|
||||
raise InvalidPlugin(_('No valid plugin found in ')+path_to_zip_file)
|
||||
return loader.load(path_to_zip_file)
|
||||
|
||||
# }}}
|
||||
|
||||
|
154
src/calibre/customize/zipplugin.py
Normal file
154
src/calibre/customize/zipplugin.py
Normal file
@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, zipfile, posixpath, importlib, threading, re
|
||||
from collections import OrderedDict
|
||||
|
||||
from calibre.customize import (Plugin, numeric_version, platform,
|
||||
InvalidPlugin, PluginNotFound)
|
||||
|
||||
# PEP 302 based plugin loading mechanism, works around the bug in zipimport in
|
||||
# python 2.x that prevents importing from zip files in locations whose paths
|
||||
# have non ASCII characters
|
||||
|
||||
|
||||
class PluginLoader(object):
|
||||
|
||||
'''
|
||||
The restrictions that a zip file must obey to be a valid calibre plugin
|
||||
are:
|
||||
|
||||
* The .py file that defines the main plugin class must have a name
|
||||
that:
|
||||
* Ends in plugin.py
|
||||
* Is a valid python identifier (contains only English alphabets,
|
||||
underscores and numbers and starts with an alphabet). This
|
||||
applies to the file name minus the .py extension, obviously.
|
||||
* Try to make this name as distinct as possible, as it will be
|
||||
put into a global namespace of all plugins.
|
||||
* The zip file must contain a .py file that defines the main plugin
|
||||
class at the top level. That is, it must not be in a subdirectory.
|
||||
The filename must follow the restrictions outlined above.
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
self.loaded_plugins = {}
|
||||
self._lock = threading.RLock()
|
||||
self._identifier_pat = re.compile(r'[a-zA-Z][_0-9a-zA-Z]*')
|
||||
|
||||
def load(self, path_to_zip_file):
|
||||
if not os.access(path_to_zip_file, os.R_OK):
|
||||
raise PluginNotFound('Cannot access %r'%path_to_zip_file)
|
||||
|
||||
with zipfile.ZipFile(path_to_zip_file) as zf:
|
||||
plugin_name = self._locate_code(zf, path_to_zip_file)
|
||||
|
||||
try:
|
||||
ans = None
|
||||
m = importlib.import_module(
|
||||
'calibre_plugins.%s.__init__'%plugin_name)
|
||||
for obj in m.__dict__.itervalues():
|
||||
if isinstance(obj, type) and issubclass(obj, Plugin) and \
|
||||
obj.name != 'Trivial Plugin':
|
||||
ans = obj
|
||||
break
|
||||
if ans is None:
|
||||
raise InvalidPlugin('No plugin class found in %r:%r'%(
|
||||
path_to_zip_file, plugin_name))
|
||||
|
||||
if ans.minimum_calibre_version < numeric_version:
|
||||
raise InvalidPlugin(
|
||||
'The plugin at %r needs a version of calibre >= %r' %
|
||||
(path_to_zip_file, '.'.join(ans.minimum_calibre_version)))
|
||||
|
||||
if platform not in ans.supported_platforms:
|
||||
raise InvalidPlugin(
|
||||
'The plugin at %r cannot be used on %s' %
|
||||
(path_to_zip_file, platform))
|
||||
|
||||
return ans
|
||||
except:
|
||||
with self._lock:
|
||||
del self.loaded_plugins[plugin_name]
|
||||
raise
|
||||
|
||||
|
||||
def _locate_code(self, zf, path_to_zip_file):
|
||||
names = [x if isinstance(x, unicode) else x.decode('utf-8') for x in
|
||||
zf.namelist()]
|
||||
names = [x[1:] if x[0] == '/' else x for x in names]
|
||||
|
||||
plugin_name = None
|
||||
for name in names:
|
||||
name, ext = posixpath.splitext(name)
|
||||
if name.startswith('plugin-import-name-') and ext == '.txt':
|
||||
plugin_name = name.rpartition('-')[-1]
|
||||
|
||||
if plugin_name is None:
|
||||
c = 0
|
||||
while True:
|
||||
c += 1
|
||||
plugin_name = 'dummy%d'%c
|
||||
if plugin_name not in self.loaded_plugins:
|
||||
break
|
||||
else:
|
||||
if plugin_name in self.loaded_plugins:
|
||||
raise InvalidPlugin((
|
||||
'The plugin in %r uses an import name %r that is already'
|
||||
' used by another plugin') % (path_to_zip_file, plugin_name))
|
||||
if self._identifier_pat.match(plugin_name) is None:
|
||||
raise InvalidPlugin((
|
||||
'The plugin at %r uses an invalid import name: %r' %
|
||||
(path_to_zip_file, plugin_name)))
|
||||
|
||||
pynames = [x for x in names if x.endswith('.py')]
|
||||
|
||||
candidates = [posixpath.dirname(x) for x in pynames if
|
||||
x.endswith('/__init__.py')]
|
||||
candidates.sort(key=lambda x: x.count('/'))
|
||||
valid_packages = set()
|
||||
|
||||
for candidate in candidates:
|
||||
parts = candidate.split('/')
|
||||
parent = '.'.join(parts[:-1])
|
||||
if parent and parent not in valid_packages:
|
||||
continue
|
||||
valid_packages.add('.'.join(parts))
|
||||
|
||||
names = OrderedDict()
|
||||
|
||||
for candidate in names:
|
||||
parts = posixpath.splitext(candidate)[0].split('/')
|
||||
package = '.'.join(parts[:-1])
|
||||
if package and package not in valid_packages:
|
||||
continue
|
||||
name = '.'.join(parts)
|
||||
names[name] = zf.getinfo(candidate)
|
||||
|
||||
# Legacy plugins
|
||||
if '__init__' not in names:
|
||||
for name in list(names.iterkeys()):
|
||||
if '.' not in name and name.endswith('plugin'):
|
||||
names['__init__'] = names[name]
|
||||
break
|
||||
|
||||
if '__init__' not in names:
|
||||
raise InvalidPlugin(('The plugin in %r is invalid. It does not '
|
||||
'contain a top-level __init__.py file')
|
||||
% path_to_zip_file)
|
||||
|
||||
with self._lock:
|
||||
self.loaded_plugins[plugin_name] = (path_to_zip_file, names)
|
||||
|
||||
return plugin_name
|
||||
|
||||
|
||||
loader = PluginLoader()
|
||||
|
||||
|
@ -4,9 +4,9 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: calibre 0.7.51\n"
|
||||
"POT-Creation-Date: 2011-03-25 12:23+MDT\n"
|
||||
"PO-Revision-Date: 2011-03-25 12:23+MDT\n"
|
||||
"Project-Id-Version: calibre 0.7.52\n"
|
||||
"POT-Creation-Date: 2011-03-25 19:03+MDT\n"
|
||||
"PO-Revision-Date: 2011-03-25 19:03+MDT\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: LANGUAGE\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
Loading…
x
Reference in New Issue
Block a user