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


[docs] 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()
[docs] 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)
[docs] 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)
[docs] 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 []
[docs] 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)
[docs] 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)
[docs] 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:]
[docs] 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