mirror of
https://github.com/Kozea/Radicale.git
synced 2025-08-07 18:30:54 +00:00
Synced with origin
This commit is contained in:
parent
9b3bb2de2b
commit
cf81d1f9a7
94 changed files with 5096 additions and 3560 deletions
|
@ -1,4 +1,4 @@
|
|||
# This file is part of Radicale Server - Calendar Server
|
||||
# This file is part of Radicale - CalDAV and CardDAV server
|
||||
# Copyright © 2011-2017 Guillaume Ayoub
|
||||
# Copyright © 2017-2019 Unrud <unrud@outlook.com>
|
||||
#
|
||||
|
@ -29,128 +29,176 @@ import os
|
|||
import signal
|
||||
import socket
|
||||
import sys
|
||||
from typing import List, Optional, cast
|
||||
|
||||
from radicale import VERSION, config, log, server, storage
|
||||
from radicale import VERSION, config, log, server, storage, types
|
||||
from radicale.log import logger
|
||||
from types import FrameType
|
||||
|
||||
|
||||
def run():
|
||||
def run() -> None:
|
||||
"""Run Radicale as a standalone server."""
|
||||
exit_signal_numbers = [signal.SIGTERM, signal.SIGINT]
|
||||
if sys.platform == "win32":
|
||||
exit_signal_numbers.append(signal.SIGBREAK)
|
||||
else:
|
||||
exit_signal_numbers.append(signal.SIGHUP)
|
||||
exit_signal_numbers.append(signal.SIGQUIT)
|
||||
|
||||
# Raise SystemExit when signal arrives to run cleanup code
|
||||
# (like destructors, try-finish etc.), otherwise the process exits
|
||||
# without running any of them
|
||||
def exit_signal_handler(signal_number: int,
|
||||
stack_frame: Optional[FrameType]) -> None:
|
||||
sys.exit(1)
|
||||
for signal_number in exit_signal_numbers:
|
||||
signal.signal(signal_number, exit_signal_handler)
|
||||
|
||||
log.setup()
|
||||
|
||||
# Get command-line arguments
|
||||
parser = argparse.ArgumentParser(usage="radicale [OPTIONS]")
|
||||
# Configuration options are stored in dest with format "c:SECTION:OPTION"
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="radicale", usage="%(prog)s [OPTIONS]", allow_abbrev=False)
|
||||
|
||||
parser.add_argument("--version", action="version", version=VERSION)
|
||||
parser.add_argument("--verify-storage", action="store_true",
|
||||
help="check the storage for errors and exit")
|
||||
parser.add_argument(
|
||||
"-C", "--config", help="use specific configuration files", nargs="*")
|
||||
parser.add_argument("-D", "--debug", action="store_true",
|
||||
parser.add_argument("-C", "--config",
|
||||
help="use specific configuration files", nargs="*")
|
||||
parser.add_argument("-D", "--debug", action="store_const", const="debug",
|
||||
dest="c:logging:level", default=argparse.SUPPRESS,
|
||||
help="print debug information")
|
||||
|
||||
groups = {}
|
||||
for section, values in config.DEFAULT_CONFIG_SCHEMA.items():
|
||||
for section, section_data in config.DEFAULT_CONFIG_SCHEMA.items():
|
||||
if section.startswith("_"):
|
||||
continue
|
||||
group = parser.add_argument_group(section)
|
||||
groups[group] = []
|
||||
for option, data in values.items():
|
||||
assert ":" not in section # check field separator
|
||||
assert "-" not in section and "_" not in section # not implemented
|
||||
group_description = None
|
||||
if section_data.get("_allow_extra"):
|
||||
group_description = "additional options allowed"
|
||||
if section == "headers":
|
||||
group_description += " (e.g. --headers-Pragma=no-cache)"
|
||||
elif "type" in section_data:
|
||||
group_description = "backend specific options omitted"
|
||||
group = parser.add_argument_group(section, group_description)
|
||||
for option, data in section_data.items():
|
||||
if option.startswith("_"):
|
||||
continue
|
||||
kwargs = data.copy()
|
||||
long_name = "--%s-%s" % (section, option.replace("_", "-"))
|
||||
args = kwargs.pop("aliases", [])
|
||||
args: List[str] = list(kwargs.pop("aliases", ()))
|
||||
args.append(long_name)
|
||||
kwargs["dest"] = "%s_%s" % (section, option)
|
||||
groups[group].append(kwargs["dest"])
|
||||
kwargs["dest"] = "c:%s:%s" % (section, option)
|
||||
kwargs["metavar"] = "VALUE"
|
||||
kwargs["default"] = argparse.SUPPRESS
|
||||
del kwargs["value"]
|
||||
with contextlib.suppress(KeyError):
|
||||
del kwargs["internal"]
|
||||
|
||||
if kwargs["type"] == bool:
|
||||
del kwargs["type"]
|
||||
kwargs["action"] = "store_const"
|
||||
kwargs["const"] = "True"
|
||||
opposite_args = kwargs.pop("opposite", [])
|
||||
opposite_args = list(kwargs.pop("opposite_aliases", ()))
|
||||
opposite_args.append("--no%s" % long_name[1:])
|
||||
group.add_argument(*args, **kwargs)
|
||||
|
||||
kwargs["const"] = "False"
|
||||
group.add_argument(*args, nargs="?", const="True", **kwargs)
|
||||
# Opposite argument
|
||||
kwargs["help"] = "do not %s (opposite of %s)" % (
|
||||
kwargs["help"], long_name)
|
||||
group.add_argument(*opposite_args, **kwargs)
|
||||
group.add_argument(*opposite_args, action="store_const",
|
||||
const="False", **kwargs)
|
||||
else:
|
||||
del kwargs["type"]
|
||||
group.add_argument(*args, **kwargs)
|
||||
|
||||
args = parser.parse_args()
|
||||
args_ns, remaining_args = parser.parse_known_args()
|
||||
unrecognized_args = []
|
||||
while remaining_args:
|
||||
arg = remaining_args.pop(0)
|
||||
for section, data in config.DEFAULT_CONFIG_SCHEMA.items():
|
||||
if "type" not in data and not data.get("_allow_extra"):
|
||||
continue
|
||||
prefix = "--%s-" % section
|
||||
if arg.startswith(prefix):
|
||||
arg = arg[len(prefix):]
|
||||
break
|
||||
else:
|
||||
unrecognized_args.append(arg)
|
||||
continue
|
||||
value = ""
|
||||
if "=" in arg:
|
||||
arg, value = arg.split("=", maxsplit=1)
|
||||
elif remaining_args and not remaining_args[0].startswith("-"):
|
||||
value = remaining_args.pop(0)
|
||||
option = arg
|
||||
if not data.get("_allow_extra"): # preserve dash in HTTP header names
|
||||
option = option.replace("-", "_")
|
||||
vars(args_ns)["c:%s:%s" % (section, option)] = value
|
||||
if unrecognized_args:
|
||||
parser.error("unrecognized arguments: %s" %
|
||||
" ".join(unrecognized_args))
|
||||
|
||||
# Preliminary configure logging
|
||||
if args.debug:
|
||||
args.logging_level = "debug"
|
||||
with contextlib.suppress(ValueError):
|
||||
log.set_level(config.DEFAULT_CONFIG_SCHEMA["logging"]["level"]["type"](
|
||||
args.logging_level))
|
||||
vars(args_ns).get("c:logging:level", "")))
|
||||
|
||||
# Update Radicale configuration according to arguments
|
||||
arguments_config = {}
|
||||
for group, actions in groups.items():
|
||||
section = group.title
|
||||
section_config = {}
|
||||
for action in actions:
|
||||
value = getattr(args, action)
|
||||
if value is not None:
|
||||
section_config[action.split('_', 1)[1]] = value
|
||||
if section_config:
|
||||
arguments_config[section] = section_config
|
||||
arguments_config: types.MUTABLE_CONFIG = {}
|
||||
for key, value in vars(args_ns).items():
|
||||
if key.startswith("c:"):
|
||||
_, section, option = key.split(":", maxsplit=2)
|
||||
arguments_config[section] = arguments_config.get(section, {})
|
||||
arguments_config[section][option] = value
|
||||
|
||||
try:
|
||||
configuration = config.load(config.parse_compound_paths(
|
||||
config.DEFAULT_CONFIG_PATH,
|
||||
os.environ.get("RADICALE_CONFIG"),
|
||||
os.pathsep.join(args.config) if args.config else None))
|
||||
os.pathsep.join(args_ns.config) if args_ns.config is not None
|
||||
else None))
|
||||
if arguments_config:
|
||||
configuration.update(arguments_config, "arguments")
|
||||
configuration.update(arguments_config, "command line arguments")
|
||||
except Exception as e:
|
||||
logger.fatal("Invalid configuration: %s", e, exc_info=True)
|
||||
logger.critical("Invalid configuration: %s", e, exc_info=True)
|
||||
sys.exit(1)
|
||||
|
||||
# Configure logging
|
||||
log.set_level(configuration.get("logging", "level"))
|
||||
log.set_level(cast(str, configuration.get("logging", "level")))
|
||||
|
||||
# Log configuration after logger is configured
|
||||
for source, miss in configuration.sources():
|
||||
logger.info("%s %s", "Skipped missing" if miss else "Loaded", source)
|
||||
|
||||
if args.verify_storage:
|
||||
if args_ns.verify_storage:
|
||||
logger.info("Verifying storage")
|
||||
try:
|
||||
storage_ = storage.load(configuration)
|
||||
with storage_.acquire_lock("r"):
|
||||
if not storage_.verify():
|
||||
logger.fatal("Storage verifcation failed")
|
||||
logger.critical("Storage verifcation failed")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
logger.fatal("An exception occurred during storage verification: "
|
||||
"%s", e, exc_info=True)
|
||||
logger.critical("An exception occurred during storage "
|
||||
"verification: %s", e, exc_info=True)
|
||||
sys.exit(1)
|
||||
return
|
||||
|
||||
# Create a socket pair to notify the server of program shutdown
|
||||
shutdown_socket, shutdown_socket_out = socket.socketpair()
|
||||
|
||||
# SIGTERM and SIGINT (aka KeyboardInterrupt) shutdown the server
|
||||
def shutdown(signal_number, stack_frame):
|
||||
# Shutdown server when signal arrives
|
||||
def shutdown_signal_handler(signal_number: int,
|
||||
stack_frame: Optional[FrameType]) -> None:
|
||||
shutdown_socket.close()
|
||||
signal.signal(signal.SIGTERM, shutdown)
|
||||
signal.signal(signal.SIGINT, shutdown)
|
||||
for signal_number in exit_signal_numbers:
|
||||
signal.signal(signal_number, shutdown_signal_handler)
|
||||
|
||||
try:
|
||||
server.serve(configuration, shutdown_socket_out)
|
||||
except Exception as e:
|
||||
logger.fatal("An exception occurred during server startup: %s", e,
|
||||
exc_info=True)
|
||||
logger.critical("An exception occurred during server startup: %s", e,
|
||||
exc_info=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue