%PDF- %PDF-
Direktori : /lib/calibre/calibre/srv/ |
Current File : //lib/calibre/calibre/srv/standalone.py |
#!/usr/bin/env python3 # License: GPLv3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net> import json import os import signal import sys from calibre import as_unicode from calibre.constants import is_running_from_develop, ismacos, iswindows from calibre.db.delete_service import shutdown as shutdown_delete_service from calibre.db.legacy import LibraryDatabase from calibre.srv.bonjour import BonJour from calibre.srv.handler import Handler from calibre.srv.http_response import create_http_handler from calibre.srv.library_broker import load_gui_libraries from calibre.srv.loop import BadIPSpec, ServerLoop from calibre.srv.manage_users_cli import manage_users_cli from calibre.srv.opts import opts_to_parser from calibre.srv.users import connect from calibre.srv.utils import HandleInterrupt, RotatingLog from calibre.utils.config import prefs from calibre.utils.localization import localize_user_manual_link from calibre.utils.lock import singleinstance from polyglot.builtins import error_message from calibre_extensions import speedup def daemonize(): # {{{ try: pid = os.fork() if pid > 0: # exit first parent sys.exit(0) except OSError as e: raise SystemExit('fork #1 failed: %s' % as_unicode(e)) # decouple from parent environment os.chdir("/") os.setsid() os.umask(0) # do second fork try: pid = os.fork() if pid > 0: # exit from second parent sys.exit(0) except OSError as e: raise SystemExit('fork #2 failed: %s' % as_unicode(e)) # Redirect standard file descriptors. speedup.detach(os.devnull) # }}} class Server: def __init__(self, libraries, opts): log = access_log = None log_size = opts.max_log_size * 1024 * 1024 if opts.log: log = RotatingLog(opts.log, max_size=log_size) if opts.access_log: access_log = RotatingLog(opts.access_log, max_size=log_size) self.handler = Handler(libraries, opts) if opts.custom_list_template: with lopen(os.path.expanduser(opts.custom_list_template), 'rb') as f: self.handler.router.ctx.custom_list_template = json.load(f) if opts.search_the_net_urls: with lopen(os.path.expanduser(opts.search_the_net_urls), 'rb') as f: self.handler.router.ctx.search_the_net_urls = json.load(f) plugins = [] if opts.use_bonjour: plugins.append(BonJour(wait_for_stop=max(0, opts.shutdown_timeout - 0.2))) self.loop = ServerLoop( create_http_handler(self.handler.dispatch), opts=opts, log=log, access_log=access_log, plugins=plugins) self.handler.set_log(self.loop.log) self.handler.set_jobs_manager(self.loop.jobs_manager) self.serve_forever = self.loop.serve_forever self.stop = self.loop.stop if is_running_from_develop: from calibre.utils.rapydscript import compile_srv compile_srv() def create_option_parser(): parser = opts_to_parser( '%prog ' + _( '''[options] [path to library folder...] Start the calibre Content server. The calibre Content server exposes your calibre libraries over the internet. You can specify the path to the library folders as arguments to %prog. If you do not specify any paths, all the libraries that the main calibre program knows about will be used. ''')) parser.add_option( '--log', default=None, help=_( 'Path to log file for server log. This log contains server information and errors, not access logs. By default it is written to stdout.' )) parser.add_option( '--access-log', default=None, help=_( 'Path to the access log file. This log contains information' ' about clients connecting to the server and making requests. By' ' default no access logging is done.')) parser.add_option( '--custom-list-template', help=_( 'Path to a JSON file containing a template for the custom book list mode.' ' The easiest way to create such a template file is to go to Preferences->' ' Sharing over the net-> Book list template in calibre, create the' ' template and export it.' )) parser.add_option( '--search-the-net-urls', help=_( 'Path to a JSON file containing URLs for the "Search the internet" feature.' ' The easiest way to create such a file is to go to Preferences->' ' Sharing over the net->Search the internet in calibre, create the' ' URLs and export them.' )) if not iswindows and not ismacos: # Does not work on macOS because if we fork() we cannot connect to Core # Serives which is needed by the QApplication() constructor, which in # turn is needed by ensure_app() parser.add_option( '--daemonize', default=False, action='store_true', help=_('Run process in background as a daemon (Linux only).')) parser.add_option( '--pidfile', default=None, help=_('Write process PID to the specified file')) parser.add_option( '--auto-reload', default=False, action='store_true', help=_( 'Automatically reload server when source code changes. Useful' ' for development. You should also specify a small value for the' ' shutdown timeout.')) parser.add_option( '--manage-users', default=False, action='store_true', help=_( 'Manage the database of users allowed to connect to this server.' ' You can use it in automated mode by adding a --. See {0}' ' for details. See also the {1} option.').format('calibre-server --manage-users -- help', '--userdb')) parser.get_option('--userdb').help = _( 'Path to the user database to use for authentication. The database' ' is a SQLite file. To create it use {0}. You can read more' ' about managing users at: {1}' ).format( '--manage-users', localize_user_manual_link( 'https://manual.calibre-ebook.com/server.html#managing-user-accounts-from-the-command-line-only' )) return parser option_parser = create_option_parser def ensure_single_instance(): if 'CALIBRE_NO_SI_DANGER_DANGER' not in os.environ and 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)) def main(args=sys.argv): opts, args = create_option_parser().parse_args(args) if opts.auto_reload and not opts.manage_users: if getattr(opts, 'daemonize', False): raise SystemExit( 'Cannot specify --auto-reload and --daemonize at the same time') from calibre.srv.auto_reload import NoAutoReload, auto_reload try: from calibre.utils.logging import default_log return auto_reload(default_log, listen_on=opts.listen_on) except NoAutoReload as e: raise SystemExit(error_message(e)) if opts.userdb: opts.userdb = os.path.abspath(os.path.expandvars(os.path.expanduser(opts.userdb))) connect(opts.userdb, exc_class=SystemExit).close() if opts.manage_users: try: manage_users_cli(opts.userdb, args[1:]) except (KeyboardInterrupt, EOFError): raise SystemExit(_('Interrupted by user')) raise SystemExit(0) ensure_single_instance() libraries = args[1:] for lib in libraries: if not lib or not LibraryDatabase.exists_at(lib): raise SystemExit(_('There is no calibre library at: %s') % lib) libraries = libraries or load_gui_libraries() if not libraries: if not prefs['library_path']: raise SystemExit(_('You must specify at least one calibre library')) libraries = [prefs['library_path']] opts.auto_reload_port = int(os.environ.get('CALIBRE_AUTORELOAD_PORT', 0)) opts.allow_console_print = 'CALIBRE_ALLOW_CONSOLE_PRINT' in os.environ if opts.log and os.path.isdir(opts.log): raise SystemExit('The --log option must point to a file, not a directory') if opts.access_log and os.path.isdir(opts.access_log): raise SystemExit('The --access-log option must point to a file, not a directory') try: server = Server(libraries, opts) except BadIPSpec as e: raise SystemExit(f'{e}') if getattr(opts, 'daemonize', False): if not opts.log and not iswindows: raise SystemExit( 'In order to daemonize you must specify a log file, you can use /dev/stdout to log to screen even as a daemon' ) daemonize() if opts.pidfile: with lopen(opts.pidfile, 'wb') as f: f.write(str(os.getpid()).encode('ascii')) signal.signal(signal.SIGTERM, lambda s, f: server.stop()) if not getattr(opts, 'daemonize', False) and not iswindows: signal.signal(signal.SIGHUP, lambda s, f: server.stop()) # Needed for dynamic cover generation, which uses Qt for drawing from calibre.gui2 import ensure_app, load_builtin_fonts ensure_app(), load_builtin_fonts() with HandleInterrupt(server.stop): try: server.serve_forever() finally: shutdown_delete_service()