mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Start work on standalone server
This commit is contained in:
parent
ffa1ab1104
commit
0aa2c3349f
@ -81,6 +81,8 @@ Everything after the -- is passed to the script.
|
|||||||
'calibre-debug --diff file1 file2'))
|
'calibre-debug --diff file1 file2'))
|
||||||
parser.add_option('--default-programs', default=None, choices=['register', 'unregister'],
|
parser.add_option('--default-programs', default=None, choices=['register', 'unregister'],
|
||||||
help=_('(Un)register calibre from Windows Default Programs.') + ' --default-programs=(register|unregister)')
|
help=_('(Un)register calibre from Windows Default Programs.') + ' --default-programs=(register|unregister)')
|
||||||
|
parser.add_option('--new-server', action='store_true',
|
||||||
|
help='Run the new calibre content server. Any options specified after a -- will be passed to the server.')
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
@ -278,6 +280,9 @@ def main(args=sys.argv):
|
|||||||
from calibre.utils.winreg.default_programs import unregister as func
|
from calibre.utils.winreg.default_programs import unregister as func
|
||||||
print 'Running', func.__name__, '...'
|
print 'Running', func.__name__, '...'
|
||||||
func()
|
func()
|
||||||
|
elif opts.new_server:
|
||||||
|
from calibre.srv.standalone import main
|
||||||
|
main(args)
|
||||||
elif len(args) >= 2 and args[1].rpartition('.')[-1] in {'py', 'recipe'}:
|
elif len(args) >= 2 and args[1].rpartition('.')[-1] in {'py', 'recipe'}:
|
||||||
run_script(args[1], args[2:])
|
run_script(args[1], args[2:])
|
||||||
elif len(args) >= 2 and args[1].rpartition('.')[-1] in {'mobi', 'azw', 'azw3', 'docx', 'odt'}:
|
elif len(args) >= 2 and args[1].rpartition('.')[-1] in {'mobi', 'azw', 'azw3', 'docx', 'odt'}:
|
||||||
|
27
src/calibre/srv/handler.py
Normal file
27
src/calibre/srv/handler.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python2
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
|
print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
|
from calibre.srv.routes import Router
|
||||||
|
|
||||||
|
class LibraryBroker(object):
|
||||||
|
|
||||||
|
def __init__(self, libraries):
|
||||||
|
self.libraries = libraries
|
||||||
|
|
||||||
|
class Context(object):
|
||||||
|
|
||||||
|
def __init__(self, libraries):
|
||||||
|
self.library_broker = LibraryBroker(libraries)
|
||||||
|
|
||||||
|
class Handler(object):
|
||||||
|
|
||||||
|
def __init__(self, libraries, opts):
|
||||||
|
self.router = Router(ctx=Context(libraries), url_prefix=opts.url_prefix)
|
||||||
|
self.router.ctx.url_for = self.router.url_for
|
||||||
|
self.dispatch = self.router.dispatch
|
||||||
|
|
@ -9,6 +9,7 @@ __copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
from itertools import izip_longest
|
from itertools import izip_longest
|
||||||
from collections import namedtuple, OrderedDict
|
from collections import namedtuple, OrderedDict
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
Option = namedtuple('Option', 'name default longdoc shortdoc choices')
|
Option = namedtuple('Option', 'name default longdoc shortdoc choices')
|
||||||
|
|
||||||
@ -36,7 +37,7 @@ raw_options = (
|
|||||||
'shutdown_timeout', 5.0,
|
'shutdown_timeout', 5.0,
|
||||||
None,
|
None,
|
||||||
|
|
||||||
'Allow socket pre-allocation, for example, with systemd socket activation',
|
'Enable/disable socket pre-allocation, for example, with systemd socket activation',
|
||||||
'allow_socket_preallocation', True,
|
'allow_socket_preallocation', True,
|
||||||
None,
|
None,
|
||||||
|
|
||||||
@ -66,11 +67,16 @@ raw_options = (
|
|||||||
' example, "127.0.0.1" to only listen for connections from the local machine, or'
|
' example, "127.0.0.1" to only listen for connections from the local machine, or'
|
||||||
' to "::" to listen to all incoming IPv6 and IPv4 connections.',
|
' to "::" to listen to all incoming IPv6 and IPv4 connections.',
|
||||||
|
|
||||||
'Use zero copy file transfers for increased performance',
|
'Enable/disable zero copy file transfers for increased performance',
|
||||||
'use_sendfile', True,
|
'use_sendfile', True,
|
||||||
'This will use zero-copy in-kernel transfers when sending files over the network,'
|
'This will use zero-copy in-kernel transfers when sending files over the network,'
|
||||||
' increasing performance. However, it can cause corrupted file transfers on some'
|
' increasing performance. However, it can cause corrupted file transfers on some'
|
||||||
' broken filesystems. If you experience corrupted file transfers, turn it off.',
|
' broken filesystems. If you experience corrupted file transfers, turn it off.',
|
||||||
|
|
||||||
|
'Max. log file size (in MB)',
|
||||||
|
'max_log_size', 20,
|
||||||
|
'The maximum size of log files, generated by the server. When the log becomes larger'
|
||||||
|
' than this size, it is automatically rotated.',
|
||||||
)
|
)
|
||||||
assert len(raw_options) % 4 == 0
|
assert len(raw_options) % 4 == 0
|
||||||
|
|
||||||
@ -97,3 +103,36 @@ class Options(object):
|
|||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
for opt in options.itervalues():
|
for opt in options.itervalues():
|
||||||
setattr(self, opt.name, kwargs.get(opt.name, opt.default))
|
setattr(self, opt.name, kwargs.get(opt.name, opt.default))
|
||||||
|
|
||||||
|
def opt_to_cli_help(opt):
|
||||||
|
ans = opt.shortdoc
|
||||||
|
if not ans.endswith('.'):
|
||||||
|
ans += '.'
|
||||||
|
if opt.longdoc:
|
||||||
|
ans += '\n\t' + opt.longdoc
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def boolean_option(add_option, opt):
|
||||||
|
name = opt.name.replace('_', '-')
|
||||||
|
help = opt_to_cli_help(opt)
|
||||||
|
add_option('--enable-' + name, action='store_true', help=help)
|
||||||
|
add_option('--disable-' + name, action='store_false', help=help)
|
||||||
|
|
||||||
|
def opts_to_parser(usage):
|
||||||
|
from calibre.utils.config import OptionParser
|
||||||
|
parser = OptionParser(usage)
|
||||||
|
for opt in options.itervalues():
|
||||||
|
add_option = partial(parser.add_option, dest=opt.name, help=opt_to_cli_help(opt), default=opt.default)
|
||||||
|
if opt.default is True or opt.default is False:
|
||||||
|
boolean_option(add_option, opt)
|
||||||
|
elif opt.choices:
|
||||||
|
name = '--' + opt.name.replace('_', '-')
|
||||||
|
add_option(name, choices=opt.choices)
|
||||||
|
else:
|
||||||
|
name = '--' + opt.name.replace('_', '-')
|
||||||
|
otype = 'string'
|
||||||
|
if isinstance(opt.default, (int, long, float)):
|
||||||
|
otype = type(opt.default).__name__
|
||||||
|
add_option(name, type=otype)
|
||||||
|
|
||||||
|
return parser
|
||||||
|
113
src/calibre/srv/standalone.py
Normal file
113
src/calibre/srv/standalone.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
#!/usr/bin/env python2
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
|
print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
|
import sys, os
|
||||||
|
|
||||||
|
from calibre import as_unicode
|
||||||
|
from calibre.constants import plugins, iswindows
|
||||||
|
from calibre.srv.loop import ServerLoop
|
||||||
|
from calibre.srv.opts import opts_to_parser
|
||||||
|
from calibre.srv.http_response import create_http_handler
|
||||||
|
from calibre.srv.handler import Handler
|
||||||
|
from calibre.srv.utils import RotatingLog
|
||||||
|
from calibre.utils.config import prefs
|
||||||
|
from calibre.db.legacy import LibraryDatabase
|
||||||
|
|
||||||
|
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.
|
||||||
|
try:
|
||||||
|
plugins['speedup'][0].detach(os.devnull)
|
||||||
|
except AttributeError: # people running from source without updated binaries
|
||||||
|
si = os.open(os.devnull, os.O_RDONLY)
|
||||||
|
so = os.open(os.devnull, os.O_WRONLY)
|
||||||
|
se = os.open(os.devnull, os.O_WRONLY)
|
||||||
|
os.dup2(si, sys.stdin.fileno())
|
||||||
|
os.dup2(so, sys.stdout.fileno())
|
||||||
|
os.dup2(se, sys.stderr.fileno())
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class Server(object):
|
||||||
|
|
||||||
|
def __init__(self, libraries, opts):
|
||||||
|
self.handler = Handler(libraries, opts)
|
||||||
|
log = None
|
||||||
|
if opts.log:
|
||||||
|
log = RotatingLog(opts.log, max_size=opts.max_log_size)
|
||||||
|
self.loop = ServerLoop(create_http_handler(self.handler.dispatch), opts=opts, log=log)
|
||||||
|
self.serve_forever = self.loop.serve_forever
|
||||||
|
|
||||||
|
|
||||||
|
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, the library last opened (if any) in the main calibre
|
||||||
|
program will be used.
|
||||||
|
'''
|
||||||
|
))
|
||||||
|
parser.add_option(
|
||||||
|
'-l', '--library', dest='libraries', action='append', default=[],
|
||||||
|
help=_('Path to a calibre library folder. Can be specified multiple'
|
||||||
|
' times to serve multiple libraries'))
|
||||||
|
parser.add_option(
|
||||||
|
'--log', default=None,
|
||||||
|
help=_('Path to log file for server log'))
|
||||||
|
parser.add_option(
|
||||||
|
'--url-prefix', default=None,
|
||||||
|
help=_('A prefix to prepend to all URLs. Useful if you wish to run this server behind a reverse proxy.'))
|
||||||
|
parser.add_option('--daemonize', default=False, action='store_true',
|
||||||
|
help=_('Run process in background as a daemon. No effect on Windows.'))
|
||||||
|
parser.add_option('--pidfile', default=None,
|
||||||
|
help=_('Write process PID to the specified file'))
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def main(args=sys.argv):
|
||||||
|
opts, args = create_option_parser().parse_args(args)
|
||||||
|
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)
|
||||||
|
if not libraries:
|
||||||
|
if not prefs['library_path']:
|
||||||
|
raise SystemExit(_('You must specify at least one calibre library'))
|
||||||
|
libraries = [prefs['library_path']]
|
||||||
|
server = Server(libraries, opts)
|
||||||
|
if opts.daemonize:
|
||||||
|
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()))
|
||||||
|
server.serve_forever()
|
Loading…
x
Reference in New Issue
Block a user