Start work on standalone server

This commit is contained in:
Kovid Goyal 2015-06-02 16:23:56 +05:30
parent ffa1ab1104
commit 0aa2c3349f
4 changed files with 186 additions and 2 deletions

View File

@ -81,6 +81,8 @@ Everything after the -- is passed to the script.
'calibre-debug --diff file1 file2'))
parser.add_option('--default-programs', default=None, choices=['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
@ -278,6 +280,9 @@ def main(args=sys.argv):
from calibre.utils.winreg.default_programs import unregister as func
print 'Running', func.__name__, '...'
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'}:
run_script(args[1], args[2:])
elif len(args) >= 2 and args[1].rpartition('.')[-1] in {'mobi', 'azw', 'azw3', 'docx', 'odt'}:

View 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

View File

@ -9,6 +9,7 @@ __copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
from itertools import izip_longest
from collections import namedtuple, OrderedDict
from operator import attrgetter
from functools import partial
Option = namedtuple('Option', 'name default longdoc shortdoc choices')
@ -36,7 +37,7 @@ raw_options = (
'shutdown_timeout', 5.0,
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,
None,
@ -66,11 +67,16 @@ raw_options = (
' 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.',
'Use zero copy file transfers for increased performance',
'Enable/disable zero copy file transfers for increased performance',
'use_sendfile', True,
'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'
' 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
@ -97,3 +103,36 @@ class Options(object):
def __init__(self, **kwargs):
for opt in options.itervalues():
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

View 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()