#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Univention Management Console
#  handles UMC requests for a specified UMC module
#
# Like what you see? Join us!
# https://www.univention.com/about-us/careers/vacancies/
#
# Copyright 2006-2023 Univention GmbH
#
# https://www.univention.de/
#
# All rights reserved.
#
# The source code of this program is made available
# under the terms of the GNU Affero General Public License version 3
# (GNU AGPL V3) as published by the Free Software Foundation.
#
# Binary versions of this program provided by Univention to you as
# well as other copyrighted, protected or trademarked materials like
# Logos, graphics, fonts, specific documentations and configurations,
# cryptographic keys etc. are subject to a license agreement between
# you and Univention and not subject to the GNU AGPL V3.
#
# In the case you use this program under the terms of the GNU AGPL V3,
# the program is provided in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License with the Debian GNU/Linux or Univention distribution in file
# /usr/share/common-licenses/AGPL-3; if not, see
# <https://www.gnu.org/licenses/>.

from __future__ import print_function

import locale
import os
import signal
import sys
import traceback
from argparse import ArgumentParser

import notifier

# don't import univention.management.console.{modules,protocol} here as the locale is not yet set!
from univention.management.console.config import SERVER_DEBUG_LEVEL, get_int, ucr
from univention.management.console.log import CORE, log_init, log_reopen


server = None


class UMC_Daemon(object):

    def __init__(self):
        self.server = None
        self.parser = ArgumentParser()
        self.parser.add_argument(
            '-p', '--port', type=int, default=6670,
            help='defines an alternative port number [default %(default)s]')
        self.parser.add_argument(
            '-u', '--unix-socket',
            default='/run/univention-management-console/server.socket',
            help='defines an alternative UNIX socket [default %(default)s]')
        self.parser.add_argument(
            '-l', '--language',
            default=ucr.get('locale/default', 'C').split(':', 1)[0],
            help='defines the language to use [default: %(default)s]')
        self.parser.add_argument(
            '-d', '--debug', type=int, default=SERVER_DEBUG_LEVEL,
            help='if given than debugging is activated and set to the specified level [default: %(default)s]')
        self.parser.add_argument(
            '-L', '--log-file', default='management-console-server',
            help='specifies an alternative log file [default: %(default)s]')
        self.parser.add_argument(
            '-c', '--processes', default=ucr.get_int('umc/server/processes', 1), type=int,
            help='How many processes to fork. 0 means auto detection (default: %(default)s).')
        self.parser.add_argument('action', default='start', nargs='?', choices=['start', 'stop', 'restart', 'crestart', 'reload'])
        self.options = self.parser.parse_args()

        os.environ['LANG'] = locale.normalize(self.options.language)

        # init logging
        log_init(self.options.log_file, self.options.debug, self.options.processes != 1)

        # init daemon runner
        signal.signal(signal.SIGHUP, self.signal_hang_up)
        signal.signal(signal.SIGUSR1, self.signal_user1)
        signal.signal(signal.SIGTERM, self.signal_terminate)

        # set locale
        try:
            locale.setlocale(locale.LC_MESSAGES, locale.normalize(self.options.language))
            locale.setlocale(locale.LC_CTYPE, locale.normalize(self.options.language))
        except locale.Error:
            CORE.process('Specified locale is not available (%s)' % self.options.language)

    def run(self):
        from univention.management.console.protocol.server import Server

        notifier.init(notifier.GENERIC)
        notifier.dispatch.MIN_TIMER = get_int('umc/server/dispatch-interval', notifier.dispatch.MIN_TIMER)

        # make sure the directory where to place socket files exists
        if not os.path.exists('/run/univention-management-console'):
            os.mkdir('/run/univention-management-console')

        self.server = Server(port=self.options.port, unix=self.options.unix_socket, processes=self.options.processes)
        with self.server:
            CORE.process('Server started')
            notifier.loop()

    def signal_hang_up(self, signal, frame):
        if self.server is not None:
            CORE.process('Reloading configuration ...')
            self._inform_childs(signal)
            try:
                self.server.reload()
            except EnvironmentError as exc:
                CORE.error('Could not reload server: %s' % (exc,))
        try:
            log_reopen()
        except EnvironmentError as exc:
            CORE.error('Could not reopen logfile: %s' % (exc,))

    def signal_user1(self, signal, frame):
        if self.server is not None:
            CORE.process('Reloading configuration ...')
            self._inform_childs(signal)
            try:
                self.server.reload()
            except EnvironmentError as exc:
                CORE.error('Could not reload server: %s' % (exc,))

    def signal_terminate(self, signal, frame):
        print('Shutting down UMC server', file=sys.stderr)
        if self.server is not None:
            self._inform_childs(signal)
            self.server.exit()
        raise SystemExit(0)

    def _inform_childs(self, signal):
        if self.server._child_number is not None:
            return  # we are the child process
        try:
            children = list(self.server._children.items())
        except EnvironmentError:
            children = []
        for _child, pid in children:
            try:
                os.kill(pid, signal)
            except EnvironmentError as exc:
                CORE.process('Failed sending signal %d to process %d: %s' % (signal, pid, exc))


if __name__ == "__main__":
    try:
        umc_daemon = UMC_Daemon()
        umc_daemon.run()
    except Exception:
        CORE.error(traceback.format_exc())
        raise
