mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
Merge upstream changes.
This commit is contained in:
commit
d81ed46acd
11
setup.py
11
setup.py
@ -380,13 +380,16 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
class build(_build):
|
class build(_build):
|
||||||
|
|
||||||
sub_commands = \
|
sub_commands = [
|
||||||
[
|
|
||||||
('resources', lambda self : 'CALIBRE_BUILDBOT' not in os.environ.keys()),
|
('resources', lambda self : 'CALIBRE_BUILDBOT' not in os.environ.keys()),
|
||||||
('translations', lambda self : 'CALIBRE_BUILDBOT' not in os.environ.keys()),
|
('translations', lambda self : 'CALIBRE_BUILDBOT' not in os.environ.keys()),
|
||||||
('gui', lambda self : 'CALIBRE_BUILDBOT' not in os.environ.keys()),
|
('gui', lambda self : 'CALIBRE_BUILDBOT' not in os.environ.keys()),
|
||||||
] + _build.sub_commands
|
('build_ext', lambda self: True),
|
||||||
|
('build_py', lambda self: True),
|
||||||
|
('build_clib', _build.has_c_libraries),
|
||||||
|
('build_scripts', _build.has_scripts),
|
||||||
|
]
|
||||||
|
|
||||||
entry_points['console_scripts'].append('calibre_postinstall = calibre.linux:post_install')
|
entry_points['console_scripts'].append('calibre_postinstall = calibre.linux:post_install')
|
||||||
ext_modules = [
|
ext_modules = [
|
||||||
Extension('calibre.plugins.lzx',
|
Extension('calibre.plugins.lzx',
|
||||||
|
@ -13,7 +13,8 @@ from calibre.startup import plugins, winutil, winutilerror
|
|||||||
from calibre.constants import iswindows, isosx, islinux, isfrozen, \
|
from calibre.constants import iswindows, isosx, islinux, isfrozen, \
|
||||||
terminal_controller, preferred_encoding, \
|
terminal_controller, preferred_encoding, \
|
||||||
__appname__, __version__, __author__, \
|
__appname__, __version__, __author__, \
|
||||||
win32event, win32api, winerror, fcntl
|
win32event, win32api, winerror, fcntl, \
|
||||||
|
filesystem_encoding
|
||||||
import mechanize
|
import mechanize
|
||||||
|
|
||||||
mimetypes.add_type('application/epub+zip', '.epub')
|
mimetypes.add_type('application/epub+zip', '.epub')
|
||||||
@ -41,6 +42,25 @@ def osx_version():
|
|||||||
return int(m.group(1)), int(m.group(2)), int(m.group(3))
|
return int(m.group(1)), int(m.group(2)), int(m.group(3))
|
||||||
|
|
||||||
|
|
||||||
|
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+\[\]/]')
|
||||||
|
|
||||||
|
def sanitize_file_name(name, substitute='_'):
|
||||||
|
'''
|
||||||
|
Sanitize the filename `name`. All invalid characters are replaced by `substitute`.
|
||||||
|
The set of invalid characters is the union of the invalid characters in Windows,
|
||||||
|
OS X and Linux. Also removes leading an trailing whitespace.
|
||||||
|
**WARNING:** This function also replaces path separators, so only pass file names
|
||||||
|
and not full paths to it.
|
||||||
|
*NOTE:* This function always returns byte strings, not unicode objects. The byte strings
|
||||||
|
are encoded in the filesystem encoding of the platform, or UTF-8.
|
||||||
|
'''
|
||||||
|
if isinstance(name, unicode):
|
||||||
|
name = name.encode(filesystem_encoding, 'ignore')
|
||||||
|
one = _filename_sanitize.sub(substitute, name)
|
||||||
|
one = re.sub(r'\s', ' ', one).strip()
|
||||||
|
return re.sub(r'^\.+$', '_', one)
|
||||||
|
|
||||||
|
|
||||||
class CommandLineError(Exception):
|
class CommandLineError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -201,13 +221,6 @@ class CurrentDir(object):
|
|||||||
def __exit__(self, *args):
|
def __exit__(self, *args):
|
||||||
os.chdir(self.cwd)
|
os.chdir(self.cwd)
|
||||||
|
|
||||||
def sanitize_file_name(name):
|
|
||||||
'''
|
|
||||||
Remove characters that are illegal in filenames from name.
|
|
||||||
Also remove path separators. All illegal characters are replaced by
|
|
||||||
underscores.
|
|
||||||
'''
|
|
||||||
return re.sub(r'\s', ' ', re.sub(r'[\xae"\'\|\~\:\?\\\/]|^-', '_', name.strip()))
|
|
||||||
|
|
||||||
def detect_ncpus():
|
def detect_ncpus():
|
||||||
"""Detects the number of effective CPUs in the system"""
|
"""Detects the number of effective CPUs in the system"""
|
||||||
|
@ -29,6 +29,10 @@ winerror = __import__('winerror') if iswindows else None
|
|||||||
win32api = __import__('win32api') if iswindows else None
|
win32api = __import__('win32api') if iswindows else None
|
||||||
fcntl = None if iswindows else __import__('fcntl')
|
fcntl = None if iswindows else __import__('fcntl')
|
||||||
|
|
||||||
|
filesystem_encoding = sys.getfilesystemencoding()
|
||||||
|
if filesystem_encoding is None: filesystem_encoding = 'utf-8'
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
plugins = None
|
plugins = None
|
||||||
if plugins is None:
|
if plugins is None:
|
||||||
|
@ -185,6 +185,20 @@ def add_plugin(path_to_zip_file):
|
|||||||
initialize_plugins()
|
initialize_plugins()
|
||||||
return plugin
|
return plugin
|
||||||
|
|
||||||
|
def remove_plugin(plugin_or_name):
|
||||||
|
name = getattr(plugin_or_name, 'name', plugin_or_name)
|
||||||
|
plugins = config['plugins']
|
||||||
|
removed = False
|
||||||
|
if name in plugins.keys():
|
||||||
|
removed = True
|
||||||
|
zfp = plugins[name]
|
||||||
|
if os.path.exists(zfp):
|
||||||
|
os.remove(zfp)
|
||||||
|
plugins.pop(name)
|
||||||
|
config['plugins'] = plugins
|
||||||
|
initialize_plugins()
|
||||||
|
return removed
|
||||||
|
|
||||||
def is_disabled(plugin):
|
def is_disabled(plugin):
|
||||||
return plugin.name in config['disabled_plugins']
|
return plugin.name in config['disabled_plugins']
|
||||||
|
|
||||||
@ -237,6 +251,8 @@ def option_parser():
|
|||||||
'''))
|
'''))
|
||||||
parser.add_option('-a', '--add-plugin', default=None,
|
parser.add_option('-a', '--add-plugin', default=None,
|
||||||
help=_('Add a plugin by specifying the path to the zip file containing it.'))
|
help=_('Add a plugin by specifying the path to the zip file containing it.'))
|
||||||
|
parser.add_option('-r', '--remove-plugin', default=None,
|
||||||
|
help=_('Remove a custom plugin by name. Has no effect on builtin plugins'))
|
||||||
parser.add_option('--customize-plugin', default=None,
|
parser.add_option('--customize-plugin', default=None,
|
||||||
help=_('Customize plugin. Specify name of plugin and customization string separated by a comma.'))
|
help=_('Customize plugin. Specify name of plugin and customization string separated by a comma.'))
|
||||||
parser.add_option('-l', '--list-plugins', default=False, action='store_true',
|
parser.add_option('-l', '--list-plugins', default=False, action='store_true',
|
||||||
@ -267,6 +283,11 @@ def main(args=sys.argv):
|
|||||||
if opts.add_plugin is not None:
|
if opts.add_plugin is not None:
|
||||||
plugin = add_plugin(opts.add_plugin)
|
plugin = add_plugin(opts.add_plugin)
|
||||||
print 'Plugin added:', plugin.name, plugin.version
|
print 'Plugin added:', plugin.name, plugin.version
|
||||||
|
if opts.remove_plugin is not None:
|
||||||
|
if remove_plugin(opts.remove_plugin):
|
||||||
|
print 'Plugin removed'
|
||||||
|
else:
|
||||||
|
print 'No custom pluginnamed', opts.remove_plugin
|
||||||
if opts.customize_plugin is not None:
|
if opts.customize_plugin is not None:
|
||||||
name, custom = opts.customize_plugin.split(',')
|
name, custom = opts.customize_plugin.split(',')
|
||||||
plugin = find_plugin(name.strip())
|
plugin = find_plugin(name.strip())
|
||||||
|
@ -1906,6 +1906,8 @@ def process_file(path, options, logger=None):
|
|||||||
fpb = re.compile(options.force_page_break, re.IGNORECASE) if options.force_page_break else \
|
fpb = re.compile(options.force_page_break, re.IGNORECASE) if options.force_page_break else \
|
||||||
re.compile('$')
|
re.compile('$')
|
||||||
cq = options.chapter_attr.split(',')
|
cq = options.chapter_attr.split(',')
|
||||||
|
if len(cq) < 3:
|
||||||
|
raise ValueError('The --chapter-attr setting must have 2 commas.')
|
||||||
options.chapter_attr = [re.compile(cq[0], re.IGNORECASE), cq[1],
|
options.chapter_attr = [re.compile(cq[0], re.IGNORECASE), cq[1],
|
||||||
re.compile(cq[2], re.IGNORECASE)]
|
re.compile(cq[2], re.IGNORECASE)]
|
||||||
options.force_page_break = fpb
|
options.force_page_break = fpb
|
||||||
|
@ -22,6 +22,7 @@ from calibre.ebooks.mobi.langcodes import main_language, sub_language
|
|||||||
from calibre.ebooks.metadata import MetaInformation
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
from calibre.ebooks.metadata.opf import OPFCreator
|
from calibre.ebooks.metadata.opf import OPFCreator
|
||||||
from calibre.ebooks.metadata.toc import TOC
|
from calibre.ebooks.metadata.toc import TOC
|
||||||
|
from calibre import sanitize_file_name
|
||||||
|
|
||||||
class EXTHHeader(object):
|
class EXTHHeader(object):
|
||||||
|
|
||||||
@ -200,7 +201,8 @@ class MobiReader(object):
|
|||||||
guide = soup.find('guide')
|
guide = soup.find('guide')
|
||||||
for elem in soup.findAll(['metadata', 'guide']):
|
for elem in soup.findAll(['metadata', 'guide']):
|
||||||
elem.extract()
|
elem.extract()
|
||||||
htmlfile = os.path.join(output_dir, self.name+'.html')
|
htmlfile = os.path.join(output_dir,
|
||||||
|
sanitize_file_name(self.name)+'.html')
|
||||||
try:
|
try:
|
||||||
for ref in guide.findAll('reference', href=True):
|
for ref in guide.findAll('reference', href=True):
|
||||||
ref['href'] = os.path.basename(htmlfile)+ref['href']
|
ref['href'] = os.path.basename(htmlfile)+ref['href']
|
||||||
|
@ -20,7 +20,7 @@ from calibre.ebooks.epub.iterator import is_supported
|
|||||||
from calibre.library import server_config
|
from calibre.library import server_config
|
||||||
from calibre.customize.ui import initialized_plugins, is_disabled, enable_plugin, \
|
from calibre.customize.ui import initialized_plugins, is_disabled, enable_plugin, \
|
||||||
disable_plugin, customize_plugin, \
|
disable_plugin, customize_plugin, \
|
||||||
plugin_customization, add_plugin
|
plugin_customization, add_plugin, remove_plugin
|
||||||
|
|
||||||
class PluginModel(QAbstractItemModel):
|
class PluginModel(QAbstractItemModel):
|
||||||
|
|
||||||
@ -242,6 +242,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
self.plugin_view.setModel(self._plugin_model)
|
self.plugin_view.setModel(self._plugin_model)
|
||||||
self.connect(self.toggle_plugin, SIGNAL('clicked()'), lambda : self.modify_plugin(op='toggle'))
|
self.connect(self.toggle_plugin, SIGNAL('clicked()'), lambda : self.modify_plugin(op='toggle'))
|
||||||
self.connect(self.customize_plugin, SIGNAL('clicked()'), lambda : self.modify_plugin(op='customize'))
|
self.connect(self.customize_plugin, SIGNAL('clicked()'), lambda : self.modify_plugin(op='customize'))
|
||||||
|
self.connect(self.remove_plugin, SIGNAL('clicked()'), lambda : self.modify_plugin(op='remove'))
|
||||||
self.connect(self.button_plugin_browse, SIGNAL('clicked()'), self.find_plugin)
|
self.connect(self.button_plugin_browse, SIGNAL('clicked()'), self.find_plugin)
|
||||||
self.connect(self.button_plugin_add, SIGNAL('clicked()'), self.add_plugin)
|
self.connect(self.button_plugin_add, SIGNAL('clicked()'), self.add_plugin)
|
||||||
|
|
||||||
@ -287,6 +288,13 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
if ok:
|
if ok:
|
||||||
customize_plugin(plugin, unicode(text))
|
customize_plugin(plugin, unicode(text))
|
||||||
self._plugin_model.refresh_plugin(plugin)
|
self._plugin_model.refresh_plugin(plugin)
|
||||||
|
if op == 'remove':
|
||||||
|
if remove_plugin(plugin):
|
||||||
|
self._plugin_model.populate()
|
||||||
|
self._plugin_model.reset()
|
||||||
|
else:
|
||||||
|
error_dialog(self, _('Cannot remove builtin plugin'),
|
||||||
|
plugin.name + _(' cannot be removed. It is a builtin plugin. Try disabling it instead.')).exec_()
|
||||||
|
|
||||||
|
|
||||||
def up_column(self):
|
def up_column(self):
|
||||||
|
@ -72,7 +72,7 @@
|
|||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<property name="currentIndex" >
|
<property name="currentIndex" >
|
||||||
<number>0</number>
|
<number>4</number>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="page_3" >
|
<widget class="QWidget" name="page_3" >
|
||||||
<layout class="QVBoxLayout" name="verticalLayout" >
|
<layout class="QVBoxLayout" name="verticalLayout" >
|
||||||
@ -811,6 +811,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="remove_plugin" >
|
||||||
|
<property name="text" >
|
||||||
|
<string>&Remove plugin</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
@ -187,8 +187,8 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
self.metadata_menu = md
|
self.metadata_menu = md
|
||||||
self.add_menu = QMenu()
|
self.add_menu = QMenu()
|
||||||
self.add_menu.addAction(_('Add books from a single directory'))
|
self.add_menu.addAction(_('Add books from a single directory'))
|
||||||
self.add_menu.addAction(_('Add books recursively (One book per directory, assumes every ebook file is the same book in a different format)'))
|
self.add_menu.addAction(_('Add books from directories, including sub-directories (One book per directory, assumes every ebook file is the same book in a different format)'))
|
||||||
self.add_menu.addAction(_('Add books recursively (Multiple books per directory, assumes every ebook file is a different book)'))
|
self.add_menu.addAction(_('Add books from directories, including sub directories (Multiple books per directory, assumes every ebook file is a different book)'))
|
||||||
self.action_add.setMenu(self.add_menu)
|
self.action_add.setMenu(self.add_menu)
|
||||||
QObject.connect(self.action_add, SIGNAL("triggered(bool)"), self.add_books)
|
QObject.connect(self.action_add, SIGNAL("triggered(bool)"), self.add_books)
|
||||||
QObject.connect(self.add_menu.actions()[0], SIGNAL("triggered(bool)"), self.add_books)
|
QObject.connect(self.add_menu.actions()[0], SIGNAL("triggered(bool)"), self.add_books)
|
||||||
|
@ -199,7 +199,10 @@ class StatusBar(QStatusBar):
|
|||||||
ret = QStatusBar.showMessage(self, msg, timeout)
|
ret = QStatusBar.showMessage(self, msg, timeout)
|
||||||
if self.systray is not None:
|
if self.systray is not None:
|
||||||
if isosx and isinstance(msg, unicode):
|
if isosx and isinstance(msg, unicode):
|
||||||
msg = msg.encode(preferred_encoding)
|
try:
|
||||||
|
msg = msg.encode(preferred_encoding)
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
msg = msg.encode('utf-8')
|
||||||
self.systray.showMessage('calibre', msg, self.systray.Information, 10000)
|
self.systray.showMessage('calibre', msg, self.systray.Information, 10000)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
@ -21,13 +21,12 @@ from calibre.library.sqlite import connect, IntegrityError
|
|||||||
from calibre.utils.search_query_parser import SearchQueryParser
|
from calibre.utils.search_query_parser import SearchQueryParser
|
||||||
from calibre.ebooks.metadata import string_to_authors, authors_to_string
|
from calibre.ebooks.metadata import string_to_authors, authors_to_string
|
||||||
from calibre.ebooks.metadata.meta import get_metadata
|
from calibre.ebooks.metadata.meta import get_metadata
|
||||||
from calibre.constants import preferred_encoding, iswindows, isosx
|
from calibre.constants import preferred_encoding, iswindows, isosx, filesystem_encoding
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.customize.ui import run_plugins_on_import
|
from calibre.customize.ui import run_plugins_on_import
|
||||||
|
from calibre import sanitize_file_name
|
||||||
|
|
||||||
copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
|
copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
|
||||||
filesystem_encoding = sys.getfilesystemencoding()
|
|
||||||
if filesystem_encoding is None: filesystem_encoding = 'utf-8'
|
|
||||||
iscaseinsensitive = iswindows or isosx
|
iscaseinsensitive = iswindows or isosx
|
||||||
|
|
||||||
def normpath(x):
|
def normpath(x):
|
||||||
@ -37,23 +36,6 @@ def normpath(x):
|
|||||||
x = x.lower()
|
x = x.lower()
|
||||||
return x
|
return x
|
||||||
|
|
||||||
_filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+\[\]/]')
|
|
||||||
|
|
||||||
def sanitize_file_name(name, substitute='_'):
|
|
||||||
'''
|
|
||||||
Sanitize the filename `name`. All invalid characters are replaced by `substitute`.
|
|
||||||
The set of invalid characters is the union of the invalid characters in Windows,
|
|
||||||
OS X and Linux. Also removes leading an trailing whitespace.
|
|
||||||
**WARNING:** This function also replaces path separators, so only pass file names
|
|
||||||
and not full paths to it.
|
|
||||||
*NOTE:* This function always returns byte strings, not unicode objects. The byte strings
|
|
||||||
are encoded in the filesystem encoding of the platform, or UTF-8.
|
|
||||||
'''
|
|
||||||
if isinstance(name, unicode):
|
|
||||||
name = name.encode(filesystem_encoding, 'ignore')
|
|
||||||
one = _filename_sanitize.sub(substitute, name)
|
|
||||||
one = re.sub(r'\s', ' ', one).strip()
|
|
||||||
return re.sub(r'^\.+$', '_', one)
|
|
||||||
|
|
||||||
FIELD_MAP = {'id':0, 'title':1, 'authors':2, 'publisher':3, 'rating':4, 'timestamp':5,
|
FIELD_MAP = {'id':0, 'title':1, 'authors':2, 'publisher':3, 'rating':4, 'timestamp':5,
|
||||||
'size':6, 'tags':7, 'comments':8, 'series':9, 'series_index':10,
|
'size':6, 'tags':7, 'comments':8, 'series':9, 'series_index':10,
|
||||||
|
@ -199,7 +199,7 @@ class Server(object):
|
|||||||
x = list(range(days-1, -1, -1))
|
x = list(range(days-1, -1, -1))
|
||||||
y = stats.daily_totals
|
y = stats.daily_totals
|
||||||
ax.plot(x, y)#, align='center', width=20, color='g')
|
ax.plot(x, y)#, align='center', width=20, color='g')
|
||||||
ax.set_xlabel('Day')
|
ax.set_xlabel('Days ago')
|
||||||
ax.set_ylabel('Income ($)')
|
ax.set_ylabel('Income ($)')
|
||||||
ax.hlines([stats.daily_average], 0, days-1)
|
ax.hlines([stats.daily_average], 0, days-1)
|
||||||
ax.set_xlim([0, days-1])
|
ax.set_xlim([0, days-1])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user