1
0
Fork 0
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:
Tuna Celik 2023-02-10 22:03:33 +01:00
parent 9b3bb2de2b
commit cf81d1f9a7
94 changed files with 5096 additions and 3560 deletions

View file

@ -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)