# -*- coding: utf-8 -*-
# Copyright (C) 2010-2020 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