# Python X2Go (Client) API Documentation (v0.6.1.4)

## x2go.mimebox

# Source code for x2go.mimebox

    # -*- 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