Prevent any of calibre, calibre-server, calibredb from running simultaneously

This commit is contained in:
Kovid Goyal 2017-05-04 17:35:48 +05:30
parent 3e70294279
commit 9965600b3b
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 68 additions and 22 deletions

View File

@ -90,7 +90,32 @@ def generate_calibredb_help(preamble, app):
preamble += textwrap.dedent(''' preamble += textwrap.dedent('''
:command:`calibredb` is the command line interface to the calibre database. It has :command:`calibredb` is the command line interface to the calibre database. It has
several sub-commands, documented below: several sub-commands, documented below.
:command:`calibredb` can be used to manipulate either a calibre database
specified by path or a calibre :guilabel:`Content server` running either on
the local machine or over the internet. You can start a calibre
:guilabel:`Content server` using either the :command:`calibre-server`
program or in the main calibre program click :guilabel:`Connect/share ->
Start Content server`. Since :command:`calibredb` can make changes to your
calibre libraries, you must setup authentication on the server first. There
are two ways to do that:
* If you plan to connect only to a server running on the same computer,
you can simply use the ``--enable-local-write`` option of the
content server, to allow any program, including calibredb, running on
the local computer to make changes to your calibre data. When running
the server from the main calibre program, this option is in
:guilabel:`Preferences->Sharing over the net->Advanced`.
* If you want to enable access over the internet, then you should setup
user accounts on the server and use the :option:`--username` and :option:`--password`
options to :command:`calibredb` to give it access. You can setup
user authentication for :command:`calibre-server` by using the ``--enable-auth``
option and using ``--manage-users`` to create the user accounts.
If you are running the server from the main calibre program, use
:guilabel:`Preferences->Sharing over the net->Require username/password`.
''') ''')

View File

@ -12,10 +12,12 @@ from urllib import urlencode
from urlparse import urlparse, urlunparse from urlparse import urlparse, urlunparse
from calibre import browser, prints from calibre import browser, prints
from calibre.constants import __appname__, __version__ from calibre.constants import __appname__, __version__, iswindows
from calibre.db.cli import module_for_cmd from calibre.db.cli import module_for_cmd
from calibre.db.legacy import LibraryDatabase from calibre.db.legacy import LibraryDatabase
from calibre.utils.config import OptionParser, prefs from calibre.utils.config import OptionParser, prefs
from calibre.utils.localization import localize_user_manual_link
from calibre.utils.lock import singleinstance
from calibre.utils.serialize import MSGPACK_MIME from calibre.utils.serialize import MSGPACK_MIME
COMMANDS = ( COMMANDS = (
@ -51,8 +53,8 @@ def run_cmd(cmd, opts, args, dbctx):
if dbctx.is_remote and getattr(m, 'no_remote', False): if dbctx.is_remote and getattr(m, 'no_remote', False):
raise SystemExit(_('The {} command is not supported with remote (server based) libraries').format(cmd)) raise SystemExit(_('The {} command is not supported with remote (server based) libraries').format(cmd))
ret = m.main(opts, args, dbctx) ret = m.main(opts, args, dbctx)
if not dbctx.is_remote and not opts.dont_notify_gui and not getattr(m, 'readonly', False): # if not dbctx.is_remote and not opts.dont_notify_gui and not getattr(m, 'readonly', False):
send_message() # send_message()
return ret return ret
@ -71,17 +73,11 @@ def get_parser(usage):
' for example, http://localhost:8080/#mylibrary. library_id is the library id' ' for example, http://localhost:8080/#mylibrary. library_id is the library id'
' of the library you want to connect to on the Content server. You can use' ' of the library you want to connect to on the Content server. You can use'
' the special library_id value of - to get a list of library ids available' ' the special library_id value of - to get a list of library ids available'
' on the server.' ' on the server. For details on how to setup access via a Content server, see'
) ' {}.'
) ).format(localize_user_manual_link(
go.add_option( 'https://manual.calibre-ebook.com/generated/en/calibredb.html'
'--dont-notify-gui', ))
default=False,
action='store_true',
help=_(
'Do not notify the running calibre GUI (if any) that the database has'
' changed. Use with care, as it can lead to database corruption!'
)
) )
go.add_option( go.add_option(
'-h', '--help', help=_('show this help message and exit'), action='help' '-h', '--help', help=_('show this help message and exit'), action='help'
@ -107,7 +103,7 @@ def get_parser(usage):
def option_parser(): def option_parser():
parser = OptionParser( return get_parser(
_( _(
'''\ '''\
%%prog command [options] [arguments] %%prog command [options] [arguments]
@ -121,7 +117,6 @@ For help on an individual command: %%prog command --help
''' '''
) % '\n '.join(COMMANDS) ) % '\n '.join(COMMANDS)
) )
return parser
def read_credetials(opts): def read_credetials(opts):
@ -163,6 +158,16 @@ class DBCtx(object):
raise SystemExit() raise SystemExit()
else: else:
self.library_path = os.path.expanduser(self.library_path) self.library_path = os.path.expanduser(self.library_path)
if not singleinstance('db'):
ext = '.exe' if iswindows else ''
raise SystemExit(_(
'Another calibre program such as {} or the main calibre program is running.'
' Having multiple programs that can make changes to a calibre library'
' running at the same time is a bad idea. calibredb can connect directly'
' to a running calibre content server, to make changes through it, instead.'
' See the documentation of the {} option for details.'
).format('calibre-server' + ext, '--with-library')
)
self._db = None self._db = None
self.is_remote = False self.is_remote = False

View File

@ -26,6 +26,7 @@ from calibre.gui2.main_window import option_parser as _option_parser
from calibre.gui2.splash_screen import SplashScreen from calibre.gui2.splash_screen import SplashScreen
from calibre.utils.config import dynamic, prefs from calibre.utils.config import dynamic, prefs
from calibre.utils.ipc import RC, gui_socket_address from calibre.utils.ipc import RC, gui_socket_address
from calibre.utils.lock import singleinstance
if iswindows: if iswindows:
winutil = plugins['winutil'][0] winutil = plugins['winutil'][0]
@ -364,6 +365,16 @@ def shellquote(s):
def run_gui(opts, args, listener, app, gui_debug=None): def run_gui(opts, args, listener, app, gui_debug=None):
si = singleinstance('db')
if not si:
ext = '.exe' if iswindows else ''
error_dialog(None, _('Cannot start calibre'), _(
'Another calibre program that can modify calibre libraries, such as,'
' {} or {} is already running. You must first shut it down, before'
' starting the main calibre program. If you are sure no such'
' program is running, try restarting your computer.').format(
'calibre-server' + ext, 'calibredb' + ext), show=True)
return 1
initialize_file_icon_provider() initialize_file_icon_provider()
app.load_builtin_fonts(scan_for_fonts=True) app.load_builtin_fonts(scan_for_fonts=True)
if not dynamic.get('welcome_wizard_was_run', False): if not dynamic.get('welcome_wizard_was_run', False):
@ -462,7 +473,6 @@ def shutdown_other(rc=None):
if rc.conn is None: if rc.conn is None:
prints(_('No running calibre found')) prints(_('No running calibre found'))
return # No running instance found return # No running instance found
from calibre.utils.lock import singleinstance
rc.conn.send('shutdown:') rc.conn.send('shutdown:')
prints(_('Shutdown command sent, waiting for shutdown...')) prints(_('Shutdown command sent, waiting for shutdown...'))
for i in xrange(50): for i in xrange(50):
@ -518,7 +528,6 @@ def main(args=sys.argv):
except AbortInit: except AbortInit:
return 1 return 1
try: try:
from calibre.utils.lock import singleinstance
si = singleinstance(singleinstance_name) si = singleinstance(singleinstance_name)
except Exception: except Exception:
error_dialog(None, _('Cannot start calibre'), _( error_dialog(None, _('Cannot start calibre'), _(

View File

@ -1,6 +1,3 @@
Prevent standalone and embedded servers from running simultaneously
Prevent more than a single instance of the standalone server from running
Fix search completion popups Fix search completion popups
Read progress Read progress
Bookmarks/annotations Bookmarks/annotations

View File

@ -18,6 +18,7 @@ from calibre.srv.http_response import create_http_handler
from calibre.srv.handler import Handler from calibre.srv.handler import Handler
from calibre.srv.utils import RotatingLog from calibre.srv.utils import RotatingLog
from calibre.utils.config import prefs from calibre.utils.config import prefs
from calibre.utils.lock import singleinstance
from calibre.db.legacy import LibraryDatabase from calibre.db.legacy import LibraryDatabase
@ -297,6 +298,15 @@ def main(args=sys.argv):
raise SystemExit(_('You must specify at least one calibre library')) raise SystemExit(_('You must specify at least one calibre library'))
libraries=[prefs['library_path']] libraries=[prefs['library_path']]
if not singleinstance('db'):
ext = '.exe' if iswindows else ''
raise SystemExit(_(
'Another calibre program such as another instance of {} or the main'
' calibre program is running. Having multiple programs that can make'
' changes to a calibre library running at the same time is not supported.'
).format('calibre-server' + ext)
)
if opts.auto_reload: if opts.auto_reload:
if opts.daemonize: if opts.daemonize:
raise SystemExit('Cannot specify --auto-reload and --daemonize at the same time') raise SystemExit('Cannot specify --auto-reload and --daemonize at the same time')