#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Univention Management Console
#  Mixins for UMC 2.0 modules
#
# Copyright 2013-2022 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/>.
"""
Mixins for UMC module classes
=============================
This module provides some mixins that can be incorporated in existing UMC
modules. These mixins extend the functionality of the module in an easy-to-use
way. Just let your module derive from
:class:`~univention.management.console.module.Base` (as before) and the mixin
classes.
"""
import random
import six
from univention.lib.i18n import Translation
from univention.management.console.error import BadRequest
from univention.management.console.modules.decorators import simple_response
_ = Translation('univention-management-console').translate
[docs]class Progress(object):
	"""
	Class to keep track of the progress during execution of a function.
	Used internally.
	"""
	def __init__(self, progress_id, title, total):
		self.id = progress_id
		self.title = title
		self.message = ''
		self.current = 0.0
		self.total = total
		self.intermediate = []
		self.finished = False
		self.exc_info = None
		self.retry_after = 200
		self.location = None
		# there is another variable named
		#   "result". it is only set if explicitly
		#   calling finish_with_result
[docs]	def progress(self, detail=None, message=None):
		self.current += 1
		self.intermediate.append(detail)
		if message is not None:
			self.message = message 
[docs]	def finish(self):
		if self.finished:
			return False
		self.finished = True
		return True 
[docs]	def finish_with_result(self, result):
		if self.finish():
			self.result = result 
[docs]	def initialised(self):
		return {'id': self.id, 'title': self.title} 
[docs]	def exception(self, exc_info):
		self.exc_info = exc_info 
[docs]	def poll(self):
		if self.exc_info:
			self.finish()
			six.reraise(*self.exc_info)
		ret = {
			'title': self.title,
			'finished': self.finished,
			'intermediate': self.intermediate[:],
			'message': self.message,
			'retry_after': self.retry_after,
		}
		try:
			ret['percentage'] = self.current / self.total * 100
		except ZeroDivisionError:
			# ret['percentage'] = float('Infinity') FIXME: JSON cannot handle Infinity
			ret['percentage'] = 'Infinity'
		if self.location is not None:
			ret['location'] = self.location
		if hasattr(self, 'result'):
			ret['result'] = self.result
		del self.intermediate[:]
		return ret  
[docs]class ProgressMixin(object):
	"""
	Mixin to provide two new functions:
	* *new_progress* to create a new :class:`~univention.management.console.modules.mixins.Progress`.
	* *progress* to let the client fetch the progress made up to this moment.
	The *progress* function needs to be made public by the XML definition of the module. To use this mixin, just do::
		class Instance(Base, ProgressMixin):
			pass
	"""
[docs]	def new_progress(self, title=None, total=0):
		if not hasattr(self, '_progress_id'):
			self._progress_id = 0
		if not hasattr(self, '_progress_objs'):
			self._progress_objs = {}
		if title is None:
			title = _('Please wait for operation to finish')
		self._progress_id += random.randint(1, 100000)
		self._progress_objs[self._progress_id] = progress = Progress(self._progress_id, title, total)
		return progress 
[docs]	@simple_response
	def progress(self, progress_id):
		if not hasattr(self, '_progress_objs'):
			self._progress_objs = {}
		try:
			progress_obj = self._progress_objs[progress_id]
		except KeyError:
			raise BadRequest(_('Invalid progress ID'))
		else:
			ret = progress_obj.poll()
			if ret['finished']:
				del self._progress_objs[progress_id]
			return ret