#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
#
# Univention Management Console
#  handles UMC requests for a specified UMC module
#
# Copyright 2006-2014 Univention GmbH
#
# http://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
# <http://www.gnu.org/licenses/>.

import locale
import os
import sys
import string
import socket
import signal
import time

from optparse import OptionParser

import notifier
import notifier.signals as signals
from daemon.runner import DaemonRunner, DaemonRunnerStopFailureError, DaemonRunnerStartFailureError
from daemon.daemon import set_signal_handlers

from univention.management.console.log import CORE, log_init
from univention.management.console.config import SERVER_DEBUG_LEVEL, ucr

server = None

class UMC_Daemon( DaemonRunner ):
	def __init__( self ):
		default_debug = SERVER_DEBUG_LEVEL
		default_locale = ucr.get( 'locale/default', 'C' )
		colon = default_locale.find( ':' )
		if colon > 0:
			default_locale = default_locale[ : colon ]
		self.parser = OptionParser()
		self.parser.add_option( '-n', '--no-daemon', action = 'store_false',
						   dest = 'daemon_mode', default = True,
						   help = 'if set the process will not fork into the background' )
		self.parser.add_option( '-p', '--port', action = 'store', type = 'int',
						   dest = 'port', default = 6670,
						   help = 'defines an alternative port number [default %default]' )
		self.parser.add_option( '-l', '--language', type = 'string', action = 'store',
						   dest = 'language', default = default_locale,
						   help = 'defines the language to use [default: %default]' )
		self.parser.add_option( '-d', '--debug', action = 'store', type = 'int', dest = 'debug', default = default_debug,
						   help = 'if given than debugging is activated and set to the specified level [default: %default]' )
		self.parser.add_option( '-L', '--log-file', action = 'store',  dest = 'logfile', default = 'management-console-server',
						   help = 'specifies an alternative log file [default: %default]' )
		( self.options, self.arguments ) = self.parser.parse_args()

		# cleanup environment
		os.environ.clear() # supported since python 2.6
		os.environ[ 'PATH' ] = '/bin:/sbin:/usr/bin:/usr/sbin'
		os.environ[ 'LANG' ] = default_locale

		# init logging
		if not self.options.daemon_mode:
			debug_fd = log_init( '/dev/stderr', self.options.debug )
		else:
			debug_fd = log_init( self.options.logfile, self.options.debug )

		# default action: start
		if not self.arguments:
			sys.argv[ 1 ] = 'start'
		elif self.arguments:
			sys.argv[ 1 ] = self.arguments[ 0 ]

		# for daemon runner
		if self.options.daemon_mode:
			self.stdin_path = os.path.devnull
			self.stdout_path = os.path.devnull
			self.stderr_path = os.path.devnull
		else:
			self.stdin_path = '/dev/stdin'
			self.stdout_path = '/dev/stderr'
			self.stderr_path = '/dev/stderr'
		self.pidfile_path = '/var/run/umc-server.pid'
		self.pidfile_timeout = 3

		# init daemon runner
		DaemonRunner.__init__( self, self )
		self.daemon_context.detach_process = self.options.daemon_mode
		self.daemon_context.umask = 0077
		self.daemon_context.files_preserve = [ debug_fd ]
		self.daemon_context.signal_map = {
			signal.SIGHUP : self.signal_hang_up,
			signal.SIGUSR1 : self.signal_user1,
			signal.SIGTERM : self.signal_terminate
			}
		# set_signal_handlers( signal_map )

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

	def _reload( self ):
		"""Handler for the reload action"""
		if self.pidfile.is_locked():
			CORE.process( 'Reloading configuration ...' )
			pid = self.pidfile.read_pid()
			os.kill( pid, signal.SIGUSR1 )
		else:
			CORE.process( 'Reload failed: server ist not running' )

	DaemonRunner.action_funcs[ 'reload' ] = _reload

	def _restart( self ):
		"""Handler for the restart action. """
		if self.pidfile.is_locked():
			CORE.process( 'Stopping UMC server ...' )
			self._stop()

		CORE.process( 'Starting UMC server ...' )
		self._start()

	def _crestart( self ):
		"""Handler for the crestart action. """
		if not self.pidfile.is_locked():
			CORE.process( 'The UMC server will not be restarted as it is not running currently' )
			return

		CORE.process( 'Stopping UMC server ...' )
		self._stop()
		CORE.process( 'Starting UMC server ...' )
		self._start()

	def _terminate_daemon_process( self ):
		""" Terminate the daemon process specified in the current PID file.
			"""
		pid = self.pidfile.read_pid()
		try:
			os.kill( pid, signal.SIGTERM )
		except OSError, exc:
			raise DaemonRunnerStopFailureError(	"Failed to terminate %(pid)d: %(exc)s" % vars() )

		if self.pidfile.is_locked():
			CORE.process( 'The UMC server is still running. Will wait for 5 seconds' )
			count = 10
			while count:
				time.sleep( 0.5 )
				if not self.pidfile.is_locked():
					break
				count -= 1
			if self.pidfile.is_locked():
				CORE.process( 'The UMC server is still running. Kill it!' )
				os.kill( pid, signal.SIGKILL )
				self.pidfile.break_lock()

	DaemonRunner.action_funcs[ 'restart' ] = _restart
	DaemonRunner.action_funcs[ 'crestart' ] = _crestart

	def _usage_exit( self, argv ):
		self.parser.error( 'invalid action' )
		sys.exit( 1 )

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

		notifier.init( notifier.GENERIC )

		self.server = Server( port = self.options.port )

		CORE.process( 'Server started' )
		notifier.loop()

	def signal_hang_up( self, signal, frame ):
		if hasattr( self, 'server' ):
			self.server.reload()

	def signal_user1( self, signal, frame ):
		if hasattr( self, 'server' ):
			self.server.reload()

	def signal_terminate( self, signal, frame ):
		self.server.exit()
		raise SystemExit( 'Shutting down UMC server' )

if __name__ == "__main__":
	try:
		umc_daemon = UMC_Daemon()
		umc_daemon.do_action()
	except DaemonRunnerStopFailureError, e:
		CORE.process( 'Failed to shutdown server gracefully (may be its already dead): %s' % str( e ) )
	except DaemonRunnerStartFailureError, e:
		CORE.process( 'Failed to start server: %s' % str( e ) )
	except (SystemExit, KeyboardInterrupt):
		raise
	except:
		import traceback
		CORE.error(traceback.format_exc())
		raise
