From 6c2ea46ce3a62c63cc7a3355d840ad94229f7612 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Wed, 15 Dec 2010 10:00:10 +0000
Subject: [PATCH 1/8] Disable multiple libraries while env var
CALIBRE_OVERRIDE_DATABASE_PATH is set
---
src/calibre/gui2/actions/choose_library.py | 23 ++++++++++++++-------
src/calibre/gui2/actions/copy_to_library.py | 9 +++++++-
src/calibre/manual/portable.rst | 2 ++
3 files changed, 26 insertions(+), 8 deletions(-)
diff --git a/src/calibre/gui2/actions/choose_library.py b/src/calibre/gui2/actions/choose_library.py
index eb5902be48..e789ae62e6 100644
--- a/src/calibre/gui2/actions/choose_library.py
+++ b/src/calibre/gui2/actions/choose_library.py
@@ -160,15 +160,17 @@ class ChooseLibraryAction(InterfaceAction):
self.action_choose.triggered.connect(self.choose_library,
type=Qt.QueuedConnection)
self.choose_menu = QMenu(self.gui)
- self.choose_menu.addAction(self.action_choose)
self.qaction.setMenu(self.choose_menu)
- self.quick_menu = QMenu(_('Quick switch'))
- self.quick_menu_action = self.choose_menu.addMenu(self.quick_menu)
- self.rename_menu = QMenu(_('Rename library'))
- self.rename_menu_action = self.choose_menu.addMenu(self.rename_menu)
- self.delete_menu = QMenu(_('Delete library'))
- self.delete_menu_action = self.choose_menu.addMenu(self.delete_menu)
+ if not os.environ.get('CALIBRE_OVERRIDE_DATABASE_PATH', None):
+ self.choose_menu.addAction(self.action_choose)
+
+ self.quick_menu = QMenu(_('Quick switch'))
+ self.quick_menu_action = self.choose_menu.addMenu(self.quick_menu)
+ self.rename_menu = QMenu(_('Rename library'))
+ self.rename_menu_action = self.choose_menu.addMenu(self.rename_menu)
+ self.delete_menu = QMenu(_('Delete library'))
+ self.delete_menu_action = self.choose_menu.addMenu(self.delete_menu)
self.rename_separator = self.choose_menu.addSeparator()
@@ -223,6 +225,8 @@ class ChooseLibraryAction(InterfaceAction):
self.library_changed(self.gui.library_view.model().db)
def build_menus(self):
+ if os.environ.get('CALIBRE_OVERRIDE_DATABASE_PATH', None):
+ return
db = self.gui.library_view.model().db
locations = list(self.stats.locations(db))
for ac in self.switch_actions:
@@ -387,6 +391,11 @@ class ChooseLibraryAction(InterfaceAction):
c.exec_()
def change_library_allowed(self):
+ if os.environ.get('CALIBRE_OVERRIDE_DATABASE_PATH', None):
+ warning_dialog(self.gui, _('Not allowed'),
+ _('You cannot change libraries while using the environment'
+ ' variable CALIBRE_OVERRIDE_DATABASE_PATH.'), show=True)
+ return False
if self.gui.job_manager.has_jobs():
warning_dialog(self.gui, _('Not allowed'),
_('You cannot change libraries while jobs'
diff --git a/src/calibre/gui2/actions/copy_to_library.py b/src/calibre/gui2/actions/copy_to_library.py
index 47f7904841..0668baeac6 100644
--- a/src/calibre/gui2/actions/copy_to_library.py
+++ b/src/calibre/gui2/actions/copy_to_library.py
@@ -12,7 +12,7 @@ from threading import Thread
from PyQt4.Qt import QMenu, QToolButton
from calibre.gui2.actions import InterfaceAction
-from calibre.gui2 import error_dialog, Dispatcher
+from calibre.gui2 import error_dialog, Dispatcher, warning_dialog
from calibre.gui2.dialogs.progress import ProgressDialog
from calibre.utils.config import prefs, tweaks
@@ -106,6 +106,9 @@ class CopyToLibraryAction(InterfaceAction):
def build_menus(self):
self.menu.clear()
+ if os.environ.get('CALIBRE_OVERRIDE_DATABASE_PATH', None):
+ self.menu.addAction('disabled', self.cannot_do_dialog)
+ return
db = self.gui.library_view.model().db
locations = list(self.stats.locations(db))
for name, loc in locations:
@@ -160,5 +163,9 @@ class CopyToLibraryAction(InterfaceAction):
self.gui.iactions['Remove Books'].library_ids_deleted(
self.worker.processed, row)
+ def cannot_do_dialog(self):
+ warning_dialog(self.gui, _('Not allowed'),
+ _('You cannot use other libraries while using the environment'
+ ' variable CALIBRE_OVERRIDE_DATABASE_PATH.'), show=True)
diff --git a/src/calibre/manual/portable.rst b/src/calibre/manual/portable.rst
index a2c8e323d8..76776e3603 100644
--- a/src/calibre/manual/portable.rst
+++ b/src/calibre/manual/portable.rst
@@ -72,3 +72,5 @@ Precautions
--------------
Portable media can occasionally fail so you should make periodic backups of you Calibre library. This can be done by making a copy of the CalibreLibrary folder and all its contents. There are many freely available tools around that can optimise such back processes, well known ones being RoboCopy and RichCopy. However you can simply use a Windows copy facility if you cannot be bothered to use a specialised tools.
+
+Using the environment variable CALIBRE_OVERRIDE_DATABASE_PATH disables multiple-library support in |app|. Avoid setting this variable in calibre-portable.bat unless you really need it.
\ No newline at end of file
From eaea0d2aa656630274c8b4eb18ebed7e9ae7b765 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Wed, 15 Dec 2010 12:48:42 +0000
Subject: [PATCH 2/8] Implement a 'remember current page' option in the viewer.
---
src/calibre/gui2/viewer/config.ui | 7 +++++++
src/calibre/gui2/viewer/documentview.py | 4 ++++
src/calibre/gui2/viewer/main.py | 10 +++++++++-
3 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/src/calibre/gui2/viewer/config.ui b/src/calibre/gui2/viewer/config.ui
index 09c38fc908..4066daada2 100644
--- a/src/calibre/gui2/viewer/config.ui
+++ b/src/calibre/gui2/viewer/config.ui
@@ -171,6 +171,13 @@
+ -
+
+
+ Remember the ¤t page when quitting
+
+
+
-
diff --git a/src/calibre/gui2/viewer/documentview.py b/src/calibre/gui2/viewer/documentview.py
index 8ce0b46a6d..f5d986e757 100644
--- a/src/calibre/gui2/viewer/documentview.py
+++ b/src/calibre/gui2/viewer/documentview.py
@@ -50,6 +50,8 @@ def config(defaults=None):
c.add_opt('hyphenate', default=False, help=_('Hyphenate text'))
c.add_opt('hyphenate_default_lang', default='en',
help=_('Default language for hyphenation rules'))
+ c.add_opt('remember_current_page', default=True,
+ help=_('Save the current position in the documentwhen quitting'))
fonts = c.add_group('FONTS', _('Font options'))
fonts('serif_family', default='Times New Roman' if iswindows else 'Liberation Serif',
@@ -72,6 +74,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
opts = config().parse()
self.opt_remember_window_size.setChecked(opts.remember_window_size)
+ self.opt_remember_current_page.setChecked(opts.remember_current_page)
self.serif_family.setCurrentFont(QFont(opts.serif_family))
self.sans_family.setCurrentFont(QFont(opts.sans_family))
self.mono_family.setCurrentFont(QFont(opts.mono_family))
@@ -118,6 +121,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
c.set('fit_images', self.opt_fit_images.isChecked())
c.set('max_view_width', int(self.max_view_width.value()))
c.set('hyphenate', self.hyphenate.isChecked())
+ c.set('remember_current_page', self.opt_remember_current_page.isChecked())
idx = self.hyphenate_default_lang.currentIndex()
c.set('hyphenate_default_lang',
str(self.hyphenate_default_lang.itemData(idx).toString()))
diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py
index 96fe758ca6..f7b8a8d401 100644
--- a/src/calibre/gui2/viewer/main.py
+++ b/src/calibre/gui2/viewer/main.py
@@ -328,6 +328,11 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
c = config().parse()
self.frame.setMaximumWidth(c.max_view_width)
+ def get_remember_current_page_opt(self):
+ from calibre.gui2.viewer.documentview import config
+ c = config().parse()
+ return c.remember_current_page
+
def print_book(self, preview):
Printing(self.iterator.spine, preview)
@@ -578,7 +583,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
current_page = None
self.existing_bookmarks = []
for bm in bookmarks:
- if bm[0] == 'calibre_current_page_bookmark':
+ if bm[0] == 'calibre_current_page_bookmark' and \
+ self.get_remember_current_page_opt():
current_page = bm
else:
self.existing_bookmarks.append(bm[0])
@@ -598,6 +604,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.set_bookmarks(bookmarks)
def save_current_position(self):
+ if not self.get_remember_current_page_opt():
+ return
try:
pos = self.view.bookmark()
bookmark = '%d#%s'%(self.current_index, pos)
From fb73e74ff7acde7a9458efae01d669b0b52b4f0e Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Wed, 15 Dec 2010 14:41:36 +0000
Subject: [PATCH 3/8] schema upgrade to remove commas from tags
---
src/calibre/library/schema_upgrades.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/calibre/library/schema_upgrades.py b/src/calibre/library/schema_upgrades.py
index e35c8521ce..15c6f5c800 100644
--- a/src/calibre/library/schema_upgrades.py
+++ b/src/calibre/library/schema_upgrades.py
@@ -425,3 +425,7 @@ class SchemaUpgrade(object):
ids = [(x[0],) for x in data if has_cover(x[1])]
self.conn.executemany('UPDATE books SET has_cover=1 WHERE id=?', ids)
+ def upgrade_version_15(self):
+ 'Remove commas from tags'
+ self.conn.execute('UPDATE tags SET name=REPLACE(name, \',\', \';\')')
+
From 0b57b1ae823e5a84d72c5f8075f0f6b260720d18 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 15 Dec 2010 08:36:20 -0700
Subject: [PATCH 4/8] Add a load_resource method to the InterfaceAction class
to facilitate loading of resources from plugin ZIP files
---
src/calibre/gui2/actions/__init__.py | 25 +++++++++++++++++++++++++
src/calibre/gui2/ui.py | 1 +
2 files changed, 26 insertions(+)
diff --git a/src/calibre/gui2/actions/__init__.py b/src/calibre/gui2/actions/__init__.py
index e595c53601..4ab15f6099 100644
--- a/src/calibre/gui2/actions/__init__.py
+++ b/src/calibre/gui2/actions/__init__.py
@@ -6,6 +6,7 @@ __copyright__ = '2010, Kovid Goyal '
__docformat__ = 'restructuredtext en'
from functools import partial
+from zipfile import ZipFile
from PyQt4.Qt import QToolButton, QAction, QIcon, QObject
@@ -108,6 +109,30 @@ class InterfaceAction(QObject):
setattr(self, attr, action)
return action
+ def load_resource(self, name):
+ '''
+ If this plugin comes in a ZIP file (user added plugin), this method
+ will allow you to load resources from the ZIP file.
+
+ For example to load an image::
+
+ pixmap = QPixmap()
+ pixmap.loadFromData(self.load_resource('images/icon.png'))
+ icon = QIcon(pixmap)
+
+ :param name: Path to resource in zip file using / as separator
+
+ '''
+ if self.plugin_path is None:
+ raise ValueError('This plugin was not loaded from a ZIP file')
+ with ZipFile(self.plugin_path, 'r') as zf:
+ for candidate in zf.namelist():
+ if candidate == name:
+ return zf.read(name)
+ raise ValueError('The name %r was not found in the plugin zip'
+ ' file'%name)
+
+
def genesis(self):
'''
Setup this plugin. Only called once during initialization. self.gui is
diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py
index 7279b7f8df..c3e0bcb0da 100644
--- a/src/calibre/gui2/ui.py
+++ b/src/calibre/gui2/ui.py
@@ -103,6 +103,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
acmap = OrderedDict()
for action in interface_actions():
ac = action.load_actual_plugin(self)
+ ac.plugin_path = action.plugin_path
if ac.name in acmap:
if ac.priority >= acmap[ac.name].priority:
acmap[ac.name] = ac
From ed4709f9ac827aa0171e65f324712baca08bb4b4 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 15 Dec 2010 09:02:35 -0700
Subject: [PATCH 5/8] Refactor ZIP plugin resource loading for greater
efficiency and add method to Plugin class as well for use in non GUI plugins
---
src/calibre/customize/__init__.py | 28 ++++++++++++++++++++++++++++
src/calibre/gui2/actions/__init__.py | 18 +++++++++++-------
2 files changed, 39 insertions(+), 7 deletions(-)
diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py
index b4e9b6c448..a76cb71acd 100644
--- a/src/calibre/customize/__init__.py
+++ b/src/calibre/customize/__init__.py
@@ -80,6 +80,34 @@ class Plugin(object): # {{{
'''
pass
+ def load_resources(self, names):
+ '''
+ If this plugin comes in a ZIP file (user added plugin), this method
+ will allow you to load resources from the ZIP file.
+
+ For example to load an image::
+
+ pixmap = QPixmap()
+ pixmap.loadFromData(self.load_resources(['images/icon.png']).itervalues().next())
+ icon = QIcon(pixmap)
+
+ :param names: List of paths to resources in the zip file using / as separator
+
+ :return: A dictionary of the form ``{name : file_contents}``. Any names
+ that were not found in the zip file will not be present in the
+ dictionary.
+
+ '''
+ if self.plugin_path is None:
+ raise ValueError('This plugin was not loaded from a ZIP file')
+ ans = {}
+ with zipfile.ZipFile(self.plugin_path, 'r') as zf:
+ for candidate in zf.namelist():
+ if candidate in names:
+ ans[candidate] = zf.read(candidate)
+ return ans
+
+
def customization_help(self, gui=False):
'''
Return a string giving help on how to customize this plugin.
diff --git a/src/calibre/gui2/actions/__init__.py b/src/calibre/gui2/actions/__init__.py
index 4ab15f6099..c88203593b 100644
--- a/src/calibre/gui2/actions/__init__.py
+++ b/src/calibre/gui2/actions/__init__.py
@@ -109,7 +109,7 @@ class InterfaceAction(QObject):
setattr(self, attr, action)
return action
- def load_resource(self, name):
+ def load_resources(self, names):
'''
If this plugin comes in a ZIP file (user added plugin), this method
will allow you to load resources from the ZIP file.
@@ -117,20 +117,24 @@ class InterfaceAction(QObject):
For example to load an image::
pixmap = QPixmap()
- pixmap.loadFromData(self.load_resource('images/icon.png'))
+ pixmap.loadFromData(self.load_resources(['images/icon.png']).itervalues().next())
icon = QIcon(pixmap)
- :param name: Path to resource in zip file using / as separator
+ :param names: List of paths to resources in the zip file using / as separator
+
+ :return: A dictionary of the form ``{name : file_contents}``. Any names
+ that were not found in the zip file will not be present in the
+ dictionary.
'''
if self.plugin_path is None:
raise ValueError('This plugin was not loaded from a ZIP file')
+ ans = {}
with ZipFile(self.plugin_path, 'r') as zf:
for candidate in zf.namelist():
- if candidate == name:
- return zf.read(name)
- raise ValueError('The name %r was not found in the plugin zip'
- ' file'%name)
+ if candidate in names:
+ ans[candidate] = zf.read(candidate)
+ return ans
def genesis(self):
From 5dacd76a4e89935ac3e2ce334f784aeda6d65eec Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 15 Dec 2010 09:34:33 -0700
Subject: [PATCH 6/8] Fix #7905 (calibre keeps on trying to connect to the
plugged device even error happens)
---
src/calibre/gui2/device.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py
index 07bfeccc4f..92b5932406 100644
--- a/src/calibre/gui2/device.py
+++ b/src/calibre/gui2/device.py
@@ -166,7 +166,9 @@ class DeviceManager(Thread): # {{{
report_progress=self.report_progress)
dev.open()
except OpenFeedback, e:
- self.open_feedback_msg(dev.get_gui_name(), e.feedback_msg)
+ if dev not in self.ejected_devices:
+ self.open_feedback_msg(dev.get_gui_name(), e.feedback_msg)
+ self.ejected_devices.add(dev)
continue
except:
tb = traceback.format_exc()
From 1e6ea48080a33fa9c9efe0bdc3916b5ccd1089cb Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 15 Dec 2010 13:21:00 -0700
Subject: [PATCH 7/8] Fix various minor mem leaks
---
src/calibre/gui2/__init__.py | 3 +++
src/calibre/gui2/actions/add.py | 4 +++-
src/calibre/gui2/add.py | 4 ++++
3 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py
index f96c64080d..a68372f650 100644
--- a/src/calibre/gui2/__init__.py
+++ b/src/calibre/gui2/__init__.py
@@ -540,6 +540,7 @@ def choose_dir(window, name, title, default_dir='~'):
parent=window, name=name, mode=QFileDialog.Directory,
default_dir=default_dir)
dir = fd.get_files()
+ fd.setParent(None)
if dir:
return dir[0]
@@ -560,6 +561,7 @@ def choose_files(window, name, title,
fd = FileDialog(title=title, name=name, filters=filters,
parent=window, add_all_files_filter=all_files, mode=mode,
)
+ fd.setParent(None)
if fd.accepted:
return fd.get_files()
return None
@@ -570,6 +572,7 @@ def choose_images(window, name, title, select_only_single_file=True):
filters=[('Images', ['png', 'gif', 'jpeg', 'jpg', 'svg'])],
parent=window, add_all_files_filter=False, mode=mode,
)
+ fd.setParent(None)
if fd.accepted:
return fd.get_files()
return None
diff --git a/src/calibre/gui2/actions/add.py b/src/calibre/gui2/actions/add.py
index 38c28661b7..9917c542ae 100644
--- a/src/calibre/gui2/actions/add.py
+++ b/src/calibre/gui2/actions/add.py
@@ -243,7 +243,9 @@ class AddAction(InterfaceAction):
if hasattr(self._adder, 'cleanup'):
self._adder.cleanup()
- self._adder = None
+ self._adder.setParent(None)
+ del self._adder
+ self._adder = None
def _add_from_device_adder(self, paths=[], names=[], infos=[],
on_card=None, model=None):
diff --git a/src/calibre/gui2/add.py b/src/calibre/gui2/add.py
index 5f555ef138..601c620e9c 100644
--- a/src/calibre/gui2/add.py
+++ b/src/calibre/gui2/add.py
@@ -368,6 +368,10 @@ class Adder(QObject): # {{{
shutil.rmtree(self.worker.tdir)
except:
pass
+ self._parent = None
+ if hasattr(self, 'db_adder'):
+ self.db_adder.setParent(None)
+
@property
def number_of_books_added(self):
From 54336c28414c693dde23b7eb329b6633b898fd0e Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 15 Dec 2010 13:56:50 -0700
Subject: [PATCH 8/8] ...
---
src/calibre/gui2/add.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/calibre/gui2/add.py b/src/calibre/gui2/add.py
index 601c620e9c..026fabea07 100644
--- a/src/calibre/gui2/add.py
+++ b/src/calibre/gui2/add.py
@@ -369,8 +369,13 @@ class Adder(QObject): # {{{
except:
pass
self._parent = None
+ self.pd.setParent(None)
+ del self.pd
+ self.pd = None
if hasattr(self, 'db_adder'):
self.db_adder.setParent(None)
+ del self.db_adder
+ self.db_adder = None
@property