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'))
|
||||
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'}:
|
||||
|
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 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
|
||||
|
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