# -*- coding: utf-8 -*-

# Copyright (C) 2010-2023 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
#
# Python X2Go is free software; you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Python X2Go is distributed 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
# along with this program; if not, write to the
# Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.

"""\
:class:`x2go.mimebox.X2GoMIMEboxQueue` sets up a thread that listens for incoming files that
shall be opened locally on the client.

For each file that gets dropped in the MIME box an individual
thread is started (:class:`x2go.mimebox.X2GoMIMEboxJob`) that handles the processing
of the incoming file.

"""
__NAME__ = 'x2gomimeboxqueue-pylib'

__package__ = 'x2go'
__name__    = 'x2go.mimebox'

# modules
import os
import copy
import types
import threading
import gevent

# Python X2Go modules
from . import defaults
from . import utils
from . import log
from . import mimeboxactions


class X2GoMIMEboxQueue(threading.Thread):
    """\
    If the X2Go MIME box is supported in a particaluar :class:`x2go.session.X2GoSession` instance
    this class provides a sub-thread for handling incoming files in the MIME box
    directory. The actual handling of a dropped file is handled by the classes
    :class:`x2go.mimeboxactions.X2GoMIMEboxActionOPEN`, :class:`x2go.mimeboxactions.X2GoMIMEboxActionOPENWITH` and :class:`x2go.mimeboxactions.X2GoMIMEboxActionSAVEAS`.


    """
    mimebox_action = None

    mimebox = None
    active_jobs = {}
    mimebox_history = []

    def __init__(self, profile_name='UNKNOWN', session_name='UNKNOWN',
                       mimebox_dir=None, mimebox_action=None, mimebox_extensions=[],
                       client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT):
        """\
        :param profile_name: name of the session profile this X2Go MIME box belongs to
        :type profile_name: ``str``
        :param mimebox_dir: local directory for incoming MIME box files
        :type mimebox_dir: ``str``
        :param mimebox_action: name or instance of either of the possible X2Go MIME box action classes
        :type mimebox_action: ``str`` or instance
        :param client_instance: the underlying :class:`x2go.client.X2GoClient` instance
        :type client_instance: ``obj``
        :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the
            :class:`x2go.mimebox.X2GoMIMEboxQueue` constructor
        :type logger: ``obj``
        :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be
            constructed with the given loglevel
        :type loglevel: ``int``

        """
        if logger is None:
            self.logger = log.X2GoLogger(loglevel=loglevel)
        else:
            self.logger = copy.deepcopy(logger)
        self.logger.tag = __NAME__

        self.profile_name = profile_name
        self.session_name = session_name
        self.mimebox_dir = mimebox_dir
        if self.mimebox_dir: self.mimebox_dir = os.path.normpath(self.mimebox_dir)
        self.mimebox_extensions = mimebox_extensions
        self.client_instance = client_instance
        self.client_rootdir = client_instance.get_client_rootdir()

        # this has to be set before we set the MIME box action...
        self._accept_jobs = False

        if mimebox_action is None:
            mimebox_action = mimeboxactions.X2GoMIMEboxActionOPEN(client_instance=self.client_instance, logger=self.logger)
        elif type(mimebox_action) in (bytes, str):
            mimebox_action = self.set_mimebox_action(mimebox_action, client_instance=self.client_instance, logger=self.logger)
        else:
            # hope it's already an instance...
            self.mimebox_action = mimebox_action

        threading.Thread.__init__(self)
        self.daemon = True
        self._accept_jobs = True


    def __del__(self):
        """\
        Class destructor.

        """
        self.stop_thread()

    def pause(self):
        """\
        Prevent acceptance of new incoming files. The processing of MIME box jobs that
        are currently still active will be completed, though.


        """
        if self._accept_jobs == True:
            self._accept_jobs = False
            self.logger('paused thread: %s' % repr(self), loglevel=log.loglevel_DEBUG)

    def resume(self):
        """\
        Resume operation of the X2Go MIME box queue and continue accepting new incoming
        files.


        """
        if self._accept_jobs == False:
            self._accept_jobs = True
            self.logger('resumed thread: %s' % repr(self), loglevel=log.loglevel_DEBUG)

    def stop_thread(self):
        """\
        Stops this :class:`x2go.mimebox.X2GoMIMEboxQueue` thread completely.


        """
        self.pause()
        self._keepalive = False
        self.logger('stopping thread: %s' % repr(self), loglevel=log.loglevel_DEBUG)

    @property
    def _incoming_mimebox_jobs(self):
        if os.path.exists(self.mimebox_dir):
            l = os.listdir(self.mimebox_dir)
            mimebox_jobs = []
            for _ext in self.mimebox_extensions:
                mimebox_jobs.extend([ dj for dj in l if dj.upper().endswith(_ext.upper()) ])
            else:
                mimebox_jobs = l
            return [ dj for dj in mimebox_jobs if dj not in list(self.active_jobs.keys()) ]
        else:
            return []

    def set_mimebox_action(self, mimebox_action, **kwargs):
        """\
        Modify the MIME box action of this :class:`x2go.mimebox.X2GoMIMEboxQueue` thread during runtime. The
        change of the MIME box action will be valid for the next incoming file in the MIME box
        directory.

        :param mimebox_action: the MIME box action to execute for incoming files
        :type mimebox_action: ``str`` or ``obj``
        :param kwargs: extra options for the specified MIME box action
        :type kwargs: ``dict``

        """
        if mimebox_action in list(defaults.X2GO_MIMEBOX_ACTIONS.keys()):
            mimebox_action = defaults.X2GO_MIMEBOX_ACTIONS[mimebox_action]

        if mimebox_action in list(defaults.X2GO_MIMEBOX_ACTIONS.values()):
            self.mimebox_action = eval ('mimeboxactions.%s(**kwargs)' % mimebox_action)

    def run(self):
        """\
        This method gets called once the :class:`x2go.mimebox.X2GoMIMEboxQueue` thread is started by the ``X2GoMIMEboxQueue.start()`` method.


        """
        self.logger('starting MIME box queue thread: %s' % repr(self), loglevel=log.loglevel_DEBUG)

        self._keepalive = True
        while self._keepalive:

            while self._accept_jobs:

                if self._incoming_mimebox_jobs:

                    for _job in self._incoming_mimebox_jobs:
                        self.logger('processing incoming X2Go MIME box job: %s' % _job, loglevel=log.loglevel_NOTICE)
                        _new_mimeboxjob_thread = X2GoMIMEboxJob(target=x2go_mimeboxjob_handler,
                                                                kwargs={
                                                                  'mimebox_file': _job,
                                                                  'mimebox_extensions': self.mimebox_extensions,
                                                                  'mimebox_action': self.mimebox_action,
                                                                  'parent_thread': self,
                                                                  'logger': self.logger,
                                                                }
                                                               )
                        self.active_jobs['%s' % _job] = _new_mimeboxjob_thread
                        _new_mimeboxjob_thread.start()

                gevent.sleep(3)

            gevent.sleep(1)


def x2go_mimeboxjob_handler(mimebox_file=None,
                            mimebox_extensions=[],
                            mimebox_action=None,
                            parent_thread=None, logger=None, ):
    """\
    This function is called as a handler function for each incoming X2Go MIME box file
    represented by the class :class:`x2go.mimebox.X2GoMIMEboxJob`.

    :param mimebox_file: MIME box file name as placed in to the X2Go MIME box spool directory (Default value = None)
    :type mimebox_file: ``str``
    :param mimebox_action: an instance of either of the possible ``X2GoMIMEboxActionXXX`` classes (Default value = None)
    :type mimebox_action: ``X2GoMIMEboxActionXXX`` nstance
    :param mimebox_extensions: filter out files whose file extension is not in this list (Default value = [], means: no filtering)
    :type mimebox_extensions: ``list``
    :param parent_thread: the :class:`x2go.mimebox.X2GoMIMEboxQueue` thread that actually created this handler's :class:`x2go.mimebox.X2GoMIMEboxJob` instance (Default value = None)
    :type parent_thread: ``obj``
    :param logger: the :class:`x2go.mimebox.X2GoMIMEboxQueue`'s logging instance (Default value = None)
    :type logger: ``obj``

    """
    mimebox_action.profile_name = parent_thread.profile_name
    mimebox_action.session_name = parent_thread.session_name

    logger('action for MIME box is: %s' % mimebox_action, loglevel=log.loglevel_DEBUG)

    _dotfile = mimebox_file.startswith('.')
    _blacklisted = mimebox_file.upper().split('.')[-1] in defaults.X2GO_MIMEBOX_EXTENSIONS_BLACKLIST
    _really_process = bool(not _blacklisted  and ((not mimebox_extensions) or [ ext for ext in mimebox_extensions if mimebox_file.upper().endswith('%s' % ext.upper()) ]))
    if _really_process and not _blacklisted and not _dotfile:
        mimebox_action.do_process(mimebox_file=mimebox_file,
                                  mimebox_dir=parent_thread.mimebox_dir,
                                 )
    elif not _blacklisted and not _dotfile:
        logger('file extension of MIME box file %s is prohibited by session profile configuration' % mimebox_file, loglevel=log.loglevel_NOTICE)
    elif _dotfile:
        logger('placing files starting with a dot (.<file>) into the X2Go MIME box is prohibited, ignoring the file ,,%s\'\'' % mimebox_file, loglevel=log.loglevel_WARN)
    else:
        logger('file extension of MIME box file %s has been found in Python X2Go\' hardcoded MIME box extenstions blacklist' % mimebox_file, loglevel=log.loglevel_WARN)

    logger('removing MIME box file %s' % mimebox_file, loglevel=log.loglevel_DEBUG)

    utils.patiently_remove_file(parent_thread.mimebox_dir, mimebox_file)
    logger('removed MIME box job file %s' % mimebox_file, loglevel=log.loglevel_DEBUG)

    del parent_thread.active_jobs['%s' % mimebox_file]
    parent_thread.mimebox_history.append(mimebox_file)
    # in case we do a lot of mimebox file exports we do not want to risk an
    # endlessly growing mimebox job history
    if len(parent_thread.mimebox_history) > 100:
        parent_thread.mimebox_history = parent_thread.mimebox_history[-100:]


class X2GoMIMEboxJob(threading.Thread):
    """\
    For each X2Go MIME box job we create a sub-thread that let's
    the MIME box job be processed in the background.

    As a handler for this class the function :func:`x2go_mimeboxjob_handler()`
    is used.


    """
    def __init__(self, **kwargs):
        """\
        Construct the X2Go MIME box job thread...

        All parameters (**kwargs) are passed through to the constructor
        of ``threading.Thread()``.

        """
        threading.Thread.__init__(self, **kwargs)
        self.daemon = True
