The branch, twofactorauth has been updated via b76266a559f5a8aad7470cd5abc7c38b94bdbf70 (commit) from e3fa36a46adeb8516579f9e38c6d0d5aa00d1415 (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- ----------------------------------------------------------------------- Summary of changes: x2go/backends/terminal/_stdout.py | 49 ++++++- x2go/client.py | 11 +- x2go/defaults.py | 20 ++- x2go/dropbox.py | 258 +++++++++++++++++++++++++++++++++++ x2go/dropboxactions.py | 266 +++++++++++++++++++++++++++++++++++++ x2go/printactions.py | 44 +++--- x2go/printqueue.py | 7 +- x2go/registry.py | 23 ---- x2go/session.py | 40 +++++- x2go/utils.py | 17 +-- 10 files changed, 669 insertions(+), 66 deletions(-) create mode 100644 x2go/dropbox.py create mode 100644 x2go/dropboxactions.py The diff of changes is: diff --git a/x2go/backends/terminal/_stdout.py b/x2go/backends/terminal/_stdout.py index 03d83d1..000980e 100644 --- a/x2go/backends/terminal/_stdout.py +++ b/x2go/backends/terminal/_stdout.py @@ -40,6 +40,7 @@ import copy import x2go.rforward as rforward import x2go.sftpserver as sftpserver import x2go.printqueue as printqueue +import x2go.dropbox as dropbox import x2go.log as log import x2go.defaults as defaults import x2go.utils as utils @@ -487,7 +488,6 @@ class X2goTerminalSessionSTDOUT(object): """ self.print_queue.set_print_action(print_action, logger=self.logger, **kwargs) - def stop_printing(self): """\ Shutdown (pause) the X2go Print Queue thread. @@ -496,6 +496,41 @@ class X2goTerminalSessionSTDOUT(object): if self.print_queue is not None: self.print_queue.pause() + def start_dropbox(self, dropbox_extensions=[], dropbox_action=None): + """\ + Initialize X2go dropbox handling. + + """ + dropbox_dir = os.path.join(self.session_info.local_container, 'dropbox') + if not os.path.exists(dropbox_dir): + os.mkdir(dropbox_dir) + self.share_local_folder(folder_name=dropbox_dir, folder_type='dropbox') + self.dropbox_queue = dropbox.X2goDropboxQueue(profile_name=self.profile_name, + session_name=self.session_info.name, + dropbox_dir=dropbox_dir, + dropbox_extensions=dropbox_extensions, + dropbox_action=dropbox_action, + client_instance=self.client_instance, + logger=self.logger, + ) + self.dropbox_queue.start() + self.active_threads.append(self.dropbox_queue) + + def set_dropbox_action(self, dropbox_action, **kwargs): + """\ + STILL UNDOCUMENTED + + """ + self.dropbox_queue.set_dropbox_action(dropbox_action, logger=self.logger, **kwargs) + + def stop_dropbox(self): + """\ + Shutdown (pause) the X2go Dropbox Queue thread. + + """ + if self.dropbox_queue is not None: + self.dropbox_queue.pause() + def share_local_folder(self, folder_name=None, folder_type='disk'): """\ Share a local folder with the X2go session. @@ -583,6 +618,18 @@ class X2goTerminalSessionSTDOUT(object): 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), ] + elif folder_type is 'dropbox': + + cmd_line = [ '%s export HOSTNAME &&' % export_iconv_settings, + 'x2gomountdirs', + 'dir', + str(self.session_info.name), + _CURRENT_LOCAL_USER, + _x2go_key_fname, + '%s__REVERSESSH_PORT__%s; ' % (folder_name, self.session_info.sshfs_port), + 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), + ] + (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) self.logger('x2gomountdirs output is : %s' % stdout.read().split('\n'), log.loglevel_NOTICE) diff --git a/x2go/client.py b/x2go/client.py index a9fb1b3..f1db57a 100644 --- a/x2go/client.py +++ b/x2go/client.py @@ -277,6 +277,8 @@ class X2goClient(object): self.logger('the Python X2go module could not find any usable XServer application, you will not be able to start X2go sessions without XServer', loglevel=log.loglevel_WARN) def HOOK_open_print_dialog(self, filename, profile_name='UNKNOWN', session_name='UNKNOWN'): self.logger('HOOK_open_print_dialog: incoming print job ,, %s'' detected by X2goClient hook method' % filename, loglevel=log.loglevel_WARN) + def HOOK_open_dropbox_saveas_dialog(self, filename, profile_name='UNKNOWN', session_name='UNKNOWN'): + self.logger('HOOK_open_dropbox_saveas_dialog: incoming dropbox job ,, %s'' detected by X2goClient hook method' % filename, loglevel=log.loglevel_WARN) def HOOK_printaction_error(self, filename, profile_name='UNKNOWN', session_name='UNKNOWN', err_msg='GENERIC_ERROR'): self.logger('HOOK_printaction_error: incoming print job ,, %s'' caused error: %s' % (filename, err_msg), loglevel=log.loglevel_ERROR) def HOOK_on_control_session_death(self, profile_name): @@ -437,9 +439,11 @@ class X2goClient(object): return sessions def register_session(self, server=None, profile_id=None, profile_name=None, session_name=None, - printing=False, allow_share_local_folders=False, share_local_folders=[], return_object=False, + allow_printing=False, + allow_share_local_folders=False, share_local_folders=[], + allow_dropbox=False, dropbox_extensions=[], dropbox_action='OPEN', add_to_known_hosts=False, known_hosts=None, - force=False, **kwargs): + return_object=False, **kwargs): """\ Register a new X2go client session. Within one X2goClient instance you can manage several sessions on serveral @@ -529,6 +533,9 @@ class X2goClient(object): _params['printing'] = printing _params['allow_share_local_folders'] = allow_share_local_folders _params['share_local_folders'] = share_local_folders + _params['allow_dropbox'] = allow_dropbox + _params['dropbox_extensions'] = dropbox_extensions + _params['dropbox_action'] = dropbox_action _params['client_instance'] = self session_uuid = self.session_registry.register(server=server, diff --git a/x2go/defaults.py b/x2go/defaults.py index dd567fe..959d7c0 100644 --- a/x2go/defaults.py +++ b/x2go/defaults.py @@ -48,10 +48,11 @@ if X2GOCLIENT_OS == "Windows": import win32api CURRENT_LOCAL_USER = win32api.GetUserName() X2GO_SSH_ROOTDIR = '.ssh' - SUPPORTED_SOUND = False + SUPPORTED_SOUND = True SUPPORTED_PRINTING = True SUPPORTED_FOLDERSHARING = True - + SUPPORTED_DROPBOX = True + elif X2GOCLIENT_OS == "Linux": ROOT_DIR = '/' ETC_DIR = os.path.join(ROOT_DIR, 'etc', 'x2goclient') @@ -61,6 +62,7 @@ elif X2GOCLIENT_OS == "Linux": SUPPORTED_SOUND = True SUPPORTED_PRINTING = True SUPPORTED_FOLDERSHARING = True + SUPPORTED_DROPBOX = True elif X2GOCLIENT_OS == "Mac": ROOT_DIR = '/' @@ -71,6 +73,7 @@ elif X2GOCLIENT_OS == "Mac": SUPPORTED_SOUND = True SUPPORTED_PRINTING = True SUPPORTED_FOLDERSHARING = True + SUPPORTED_DROPBOX = True else: import exceptions @@ -235,9 +238,8 @@ X2GO_SESSIONPROFILE_DEFAULTS = { 'speed': 2, 'pack': '16m-jpeg', 'quality': 9, 'link':'ADSL', 'iconvto': 'UTF-8', 'iconvfrom': 'UTF-8', 'useiconv': False, 'usesshproxy': False, 'sshproxyhost': '', 'sshproxyuser': '', 'sshproxytunnel': '', 'sshproxykeyfile': '', - 'useexports': True, - 'fstunnel': True, - 'export': '', + 'useexports': True, 'fstunnel': True, 'export': '', + 'usedropbox': False, 'dropboxextensions': '', 'dropboxaction': 'OPEN', 'fullscreen': False, 'width': 800,'height': 600,'dpi': 96,'setdpi': False, 'usekbd':True, 'layout': 'us', 'type': 'pc105/us', @@ -340,3 +342,11 @@ DEFAULT_PDFSAVE_LOCATION = '~/PDF' """Default location for saving PDF files (PDFSAVE print action).""" DEFAULT_PRINTCMD_CMD = 'lpr' """Default command for the PRINTCMD print action.""" + +X2GO_DROPBOX_ACTIONS = { + 'OPEN': 'X2goDropboxActionOPEN', + 'OPENWITH': 'X2goDropboxActionOPENWITH', + 'SAVEAS': 'X2goDropboxActionSAVEAS', +} +"""Relating dropbox action names and classes.""" + diff --git a/x2go/dropbox.py b/x2go/dropbox.py new file mode 100644 index 0000000..858fcaa --- /dev/null +++ b/x2go/dropbox.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2011 by Mike Gabriel <m.gabriel@das-netzwerkteam.de> +# +# Python X2go is free software; you can redistribute it and/or modify +# it under the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU 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. + +"""\ +L{X2goDropboxQueue} 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 drop-box an individual +thread is started (L{X2goDropboxJob}) that handles the processing +of the incoming file. + +""" +__NAME__ = 'x2godropboxqueue-pylib' + +# modules +import os +import copy +import types +import threading +import gevent + +# Python X2go modules +import defaults +import utils +import log +import dropboxactions + +if defaults.X2GOCLIENT_OS != 'Windows': + from x2go_exceptions import WindowsError + + +class X2goDropboxQueue(threading.Thread): + """\ + If the X2go drop-box is supported in a particaluar L{X2goSession} instance + this class provides a sub-thread for handling incoming files in the drop-box + directory. The actual handling of a dropped file is handled by the classes + L{X2goDropboxActionOPEN}, L{X2goDropboxActionOPENWITH} and L{X2goDropboxActionSAVEAS}. + + """ + dropbox_action = None + + dropbox = None + active_jobs = {} + dropbox_history = [] + + def __init__(self, profile_name='UNKNOWN', session_name='UNKNOWN', + dropbox_dir=None, dropbox_action=None, dropbox_extensions=[], + client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): + """\ + @param profile_name: name of the session profile this print queue belongs to + @type profile_name: C{str} + @param dropbox_dir: local directory for incoming dropbox files + @type dropbox_dir: C{str} + @param dropbox_action: name or instance of either of the possible X2go print action classes + @type dropbox_action: C{str} or instance + @param client_instance: the underlying L{X2goClient} instance + @type client_instance: C{instance} + @param logger: you can pass an L{X2goLogger} object to the + L{X2goPrintQueue} constructor + @type logger: C{instance} + @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + constructed with the given loglevel + @type loglevel: C{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.dropbox_dir = dropbox_dir + self.dropbox_extensions = dropbox_extensions + self.client_instance = client_instance + self.client_rootdir = client_instance.get_client_rootdir() + + if dropbox_action is None: + dropbox_action = dropbox_actions.X2goDropboxActionOPEN(client_instance=self.client_instance, logger=self.logger, **dropbox_action_args) + elif type(dropbox_action) is types.StringType: + dropbox_action = self.set_dropbox_action(dropbox_action) + else: + # hope it's already an instance... + self.dropbox_action = dropbox_action + + threading.Thread.__init__(self) + self.daemon = True + self._accept_jobs = True + + def __del__(self): + self.stop_thread() + + def pause(self): + """\ + Prevent acceptance of new incoming files. The processing of dropbox 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 dropbox 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 L{X2goDropboxQueue} thread completely. + + """ + self.pause() + self._keepalive = False + self.logger('stopping thread: %s' % repr(self), loglevel=log.loglevel_DEBUG) + + @property + def _incoming_dropbox_jobs(self): + l = os.listdir(self.dropbox_dir) + dropbox_jobs = [] + for _ext in self.dropbox_extensions: + dropbox_jobs.extend([ dj for dj in l if dj.upper().endswith(_ext.upper()) ]) + else: + dropbox_jobs = l + return [ dj for dj in dropbox_jobs if dj not in self.active_jobs.keys() ] + + def set_dropbox_action(self, dropbox_action, **kwargs): + """\ + Modify the dropbox action of this L{X2goDropboxQueue} thread during runtime. The + change of the dropbox action will be valid for the next incoming file in the dropbox + directory. + + """ + if dropbox_action in defaults.X2GO_DROPBOX_ACTIONS.keys(): + dropbox_action = defaults.X2GO_DROPBOX_ACTIONS[dropbox_action] + + if dropbox_action in defaults.X2GO_DROPBOX_ACTIONS.values(): + self.dropbox_action = eval ('dropboxactions.%s(**kwargs)' % dropbox_action) + + def run(self): + """\ + Start this L{X2goDropboxQueue} thread... + + """ + self.logger('starting dropbox queue thread: %s' % repr(self), loglevel=log.loglevel_DEBUG) + + self._keepalive = True + while self._keepalive: + + while self._accept_jobs: + + if self._incoming_dropbox_jobs: + + for _job in self._incoming_dropbox_jobs: + self.logger('processing incoming X2go dropbox job: %s' % _job, loglevel=log.loglevel_NOTICE) + _new_dropboxjob_thread = X2goDropboxJob(target=x2go_dropboxjob_handler, + kwargs={ + 'dropbox_file': _job, + 'dropbox_extensions': self.dropbox_extensions, + 'dropbox_action': self.dropbox_action, + 'parent_thread': self, + 'logger': self.logger, + } + ) + self.active_jobs['%s' % _job] = _new_dropboxjob_thread + _new_dropboxjob_thread.start() + + gevent.sleep(3) + + gevent.sleep(1) + + +def x2go_dropboxjob_handler(dropbox_file=None, + dropbox_extensions=[], + dropbox_action=None, + parent_thread=None, logger=None, ): + """\ + This function is called as a handler function for each incoming X2go print job + represented by the class L{X2goPrintJob}. + + @param dropbox_file: PDF file name as placed in to the X2go spool directory + @type dropbox_file: C{str} + @param dropbox_action: an instance of either of the possible C{X2goPrintActionXXX} classes + @type dropbox_action: C{X2goPrintActionXXX} nstance + @param parent_thread: the L{X2goPrintQueue} thread that actually created this handler's L{X2goPrintJob} instance + @type parent_thread: C{instance} + @param logger: the L{X2goPrintQueue}'s logging instance + @type logger: C{instance} + + """ + dropbox_action.profile_name = parent_thread.profile_name + dropbox_action.session_name = parent_thread.session_name + + logger('action for printing is: %s' % dropbox_action, loglevel=log.loglevel_DEBUG) + + _really_process = bool((not dropbox_extensions) or [ ext for ext in dropbox_extensions if dropbox_file.upper().endswith('.%s' % ext.upper()) ]) + if _really_process: + dropbox_action.do_process(dropbox_file=dropbox_file, + dropbox_dir=parent_thread.dropbox_dir, + ) + else: + logger('file extension of dropbox file %s is prohibited by session profile configuration' % dropbox_file, loglevel=log.loglevel_WARN) + + logger('removing dropbox file %s' % dropbox_file, loglevel=log.loglevel_DEBUG) + + utils.patiently_remove_file(parent_thread.dropbox_dir, dropbox_file) + logger('removed print job file %s' % dropbox_file, loglevel=log.loglevel_DEBUG) + + del parent_thread.active_jobs['%s' % dropbox_file] + parent_thread.dropbox_history.append(dropbox_file) + # in case we do a lot of dropbox file exports we do not want to risk an + # endlessly growing dropbox job history + if len(parent_thread.dropbox_history) > 100: + parent_thread.dropbox_history = parent_thread.dropbox_history[-100:] + + +class X2goDropboxJob(threading.Thread): + """\ + For each X2go dropbox job we create a sub-thread that let's + the dropbox job be processed in the background. + + As a handler for this class the function L{x2go_dropboxjob_handler()} + is used. + + """ + def __init__(self, **kwargs): + """\ + Construct the X2go dropbox job thread... + + All parameters (**kwargs) are passed through to the constructor + of C{threading.Thread()}. + + """ + threading.Thread.__init__(self, **kwargs) + self.daemon = True diff --git a/x2go/dropboxactions.py b/x2go/dropboxactions.py new file mode 100644 index 0000000..2fb149b --- /dev/null +++ b/x2go/dropboxactions.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2011 by Mike Gabriel <m.gabriel@das-netzwerkteam.de> +# +# Python X2go is free software; you can redistribute it and/or modify +# it under the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU 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. + +"""\ +For dropbox jobs there are currently two handling actions available: +L{X2goDropboxActionOPEN}, L{X2goDropboxActionOPENWITH} and L{X2goDropboxActionSAVEAS}. + +""" +__NAME__ = 'x2godropboxactions-pylib' + +# modules +import os +import sys +import shutil +import copy +import types +import threading +import time + +from defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS +if _X2GOCLIENT_OS in ("Windows"): + import subprocess + import win32api +else: + import gevent_subprocess as subprocess + +# Python X2go modules +import log +import defaults +# we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) +import utils +import x2go_exceptions + +_DROPBOX_ENV = os.environ.copy() + + +class X2goDropboxAction(object): + + __name__ = 'NAME' + __description__ = 'DESCRIPTION' + + def __init__(self, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): + """\ + This is a meta class and has no functionality as such. It is used as parent + class by »real« X2go dropbox actions. + + @param client_instance: the underlying L{X2goClient} instance + @type client_instance: C{instance} + @param logger: you can pass an L{X2goLogger} object to the + L{X2goDropboxAction} constructor + @type logger: C{instance} + @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + constructed with the given loglevel + @type loglevel: C{int} + + """ + if logger is None: + self.logger = log.X2goLogger(loglevel=loglevel) + else: + self.logger = copy.deepcopy(logger) + self.logger.tag = __NAME__ + + # these get set from within the X2goDropboxQueue class + self.profile_name = 'UNKNOWN' + self.session_name = 'UNKNOWN' + + self.client_instance = client_instance + + @property + def name(): + """\ + Return the X2go dropbox action's name. + + """ + return self.__name__ + + @property + def description(): + """\ + Return the X2go dropbox action's description text. + + """ + return self.__description__ + + def do_process(self, dropbox_file, dropbox_dir, ): + """\ + Perform the defined dropbox action (doing nothing in L{X2goDropboxAction} parent class). + + @param dropbox_file: file name as placed in to the X2go dropbox directory + @type dropbox_file: C{str} + @param dropbox_dir: location of the X2go sessions's dropbox directory + @type dropbox_dir: C{str} + + """ + pass + + +class X2goDropboxActionOPEN(X2goDropboxAction): + """\ + Dropbox action that opens incoming files in the default application. + + """ + __name__= 'OPEN' + __decription__= 'Open incoming file with local system\'s default application.' + + def __init__(self, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): + """\ + @param client_instance: the underlying L{X2goClient} instance + @type client_instance: C{instance} + @param logger: you can pass an L{X2goLogger} object to the + L{X2goDropboxActionOPEN} constructor + @type logger: C{instance} + @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + constructed with the given loglevel + @type loglevel: C{int} + + """ + self.client_instance = client_instance + X2goDropboxAction.__init__(self, logger=logger, loglevel=loglevel) + + def do_process(self, dropbox_file, dropbox_dir, ): + """\ + Open an incoming dropbox file in the system's default application. + + @param dropbox_file: file name as placed in to the dropbox directory + @type dropbox_file: C{str} + @param dropbox_dir: location of the X2go session's dropbox directory + @type dropbox_dir: C{str} + + """ + if _X2GOCLIENT_OS == "Windows": + self.logger('opening incoming dropbox file with Python\'s os.startfile() command: %s' % dropbox_file, loglevel=log.loglevel_DEBUG) + try: + os.startfile(os.path.join(dropbox_dir, dropbox_file)) + except WindowsError, win_err: + if self.client_instance: + self.client_instance.HOOK_dropboxaction_error(dropbox_file, + profile_name=self.profile_name, + session_name=self.session_name, + err_msg=str(win_err) + ) + else: + self.logger('Encountered WindowsError: %s' % str(win_err), loglevel=log.loglevel_ERROR) + time.sleep(20) + else: + cmd_line = [ 'xdg-open', os.path.join(dropbox_dir, dropbox_file), ] + self.logger('opening dropbox file with command: %s' % ' '.join(cmd_line), loglevel=log.loglevel_DEBUG) + p = subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_DROPBOX_ENV) + time.sleep(20) + + +class X2goDropboxActionOPENWITH(X2goDropboxAction): + """\ + Dropbox action that calls the system's ,,Open with...'' dialog on incoming files. Currently only + properly implementable on Windows platforms. + + """ + __name__= 'OPENWITH' + __decription__= 'Evoke ,,Open with...\'\' dialog on incoming dropbox files.' + + def __init__(self, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): + """\ + @param client_instance: the underlying L{X2goClient} instance + @type client_instance: C{instance} + @param logger: you can pass an L{X2goLogger} object to the + L{X2goDropboxActionOPENWITH} constructor + @type logger: C{instance} + @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + constructed with the given loglevel + @type loglevel: C{int} + + """ + self.client_instance = client_instance + X2goDropboxAction.__init__(self, logger=logger, loglevel=loglevel) + + def do_process(self, dropbox_file, dropbox_dir, ): + """\ + Open an incoming dropbox file in the system's default application. + + @param dropbox_file: file name as placed in to the dropbox directory + @type dropbox_file: C{str} + @param dropbox_dir: location of the X2go session's dropbox directory + @type dropbox_dir: C{str} + + """ + if _X2GOCLIENT_OS == "Windows": + self.logger('evoking Open-with dialog on incoming dropbox file: %s' % dropbox_file, loglevel=log.loglevel_DEBUG) + win32api.ShellExecute ( + 0, + "open", + "rundll32.exe", + "shell32.dll,OpenAs_RunDLL %s" % os.path.join(dropbox_dir, dropbox_file), + None, + 0, + ) + time.sleep(20) + else: + self.logger('the evocation of the Open-with dialog box is currently not available on Linux, falling back to dropbox action OPEN', loglevel=log.loglevel_WARN) + cmd_line = [ 'xdg-open', os.path.join(dropbox_dir, dropbox_file), ] + self.logger('opening dropbox file with command: %s' % ' '.join(cmd_line), loglevel=log.loglevel_DEBUG) + p = subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_DROPBOX_ENV) + time.sleep(20) + + +class X2goDropboxActionSAVEAS(X2goDropboxAction): + """\ + Dropbox action that allows saving incoming dropbox files to a local folder. What this + dropbox actually does is calling a hook method in the L{X2goClient} instance that + can be hi-jacked by one of your application's methods which then can handle the ,,Save as...'' + request. + + """ + __name__ = 'SAVEAS' + __decription__= 'Save incoming file as...' + + def __init__(self, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): + """\ + @param client_instance: an L{X2goClient} instance, within your customized L{X2goClient} make sure + you have a C{HOOK_open_dropbox_saveas_dialog(filename=<str>)} method defined that will actually + handle the incoming dropbox file. + @type client_instance: C{instance} + @param logger: you can pass an L{X2goLogger} object to the + L{X2goDropboxActionSAVEAS} constructor + @type logger: C{instance} + @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + constructed with the given loglevel + @type loglevel: C{int} + + """ + if client_instance is None: + raise x2go_exceptions.X2goDropboxActionException('the SAVEAS dropbox action needs to know the X2goClient instance (client=<instance>)') + X2goDropboxAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel) + + def do_process(self, dropbox_file, dropbox_dir): + """\ + Call an L{X2goClient} hook method (C{HOOK_open_dropbox_saveas_dialog}) that + can handle the dropbox's SAVEAS action. + + @param dropbox_file: file name as placed in to the dropbox directory + @type dropbox_file: C{str} + @param dropbox_dir: location of the X2go session's dropbox directory + @type dropbox_dir: C{str} + @param dropbox_file: PDF file name as placed in to the X2go spool directory + + """ + self.logger('Session %s (%s) is calling X2goClient class hook method <client_instance>.HOOK_open_dropbox_saveas_dialog(%s)' % (self.session_name, self.profile_name, self.dropbox_file), loglevel=log.loglevel_NOTICE) + self.client_instance.HOOK_open_dropbox_saveas_dialog(os.path.join(dropbox_dir, dropbox_file), profile_name=self.profile_name, session_name=self.session_name) + time.sleep(60) + diff --git a/x2go/printactions.py b/x2go/printactions.py index 4b8c66b..3f084e5 100644 --- a/x2go/printactions.py +++ b/x2go/printactions.py @@ -59,11 +59,13 @@ class X2goPrintAction(object): __name__ = 'NAME' __description__ = 'DESCRIPTION' - def __init__(self, logger=None, loglevel=log.loglevel_DEFAULT): + def __init__(self, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ - This is a meta class has no functionality. It is used as parent class by »real« - X2go print actions. + This is a meta class and has no functionality as such. It is used as parent + class by »real« X2go print actions. + @param client_instance: the underlying L{X2goClient} instance + @type client_instance: C{instance} @param logger: you can pass an L{X2goLogger} object to the L{X2goPrintAction} constructor @type logger: C{instance} @@ -82,6 +84,8 @@ class X2goPrintAction(object): self.profile_name = 'UNKNOWN' self.session_name = 'UNKNOWN' + self.client_instance = client_instance + @property def name(): """\ @@ -139,6 +143,8 @@ class X2goPrintActionPDFVIEW(X2goPrintAction): def __init__(self, client_instance=None, pdfview_cmd=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ + @param client_instance: the underlying L{X2goClient} instance + @type client_instance: C{instance} @param pdfview_cmd: command that starts the external PDF viewer application @type pdfview_cmd: C{str} @param logger: you can pass an L{X2goLogger} object to the @@ -152,8 +158,7 @@ class X2goPrintActionPDFVIEW(X2goPrintAction): if pdfview_cmd is None: pdfview_cmd = defaults.DEFAULT_PDFVIEW_CMD self.pdfview_cmd = pdfview_cmd - self.client_instance = client_instance - X2goPrintAction.__init__(self, logger=logger, loglevel=loglevel) + X2goPrintAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel) def do_print(self, pdf_file, job_title, spool_dir, ): """\ @@ -204,6 +209,8 @@ class X2goPrintActionPDFSAVE(X2goPrintAction): def __init__(self, client_instance=None, save_to_folder=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ + @param client_instance: the underlying L{X2goClient} instance + @type client_instance: C{instance} @param save_to_folder: saving location for incoming print jobs (PDF files) @type save_to_folder: C{str} @param logger: you can pass an L{X2goLogger} object to the @@ -217,8 +224,7 @@ class X2goPrintActionPDFSAVE(X2goPrintAction): if save_to_folder is None: save_to_folder = os.path.expanduser(defaults.DEFAULT_PDFSAVE_LOCATION) self.save_to_folder = save_to_folder - self.client_instance = client_instance - X2goPrintAction.__init__(self, logger=None, loglevel=loglevel) + X2goPrintAction.__init__(self, client_instance=client_instance, logger=None, loglevel=loglevel) def do_print(self, pdf_file, job_title, spool_dir): """\ @@ -252,10 +258,12 @@ class X2goPrintActionPRINT(X2goPrintAction): def __init__(self, client_instance=None, printer=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ + @param client_instance: the underlying L{X2goClient} instance + @type client_instance: C{instance} @param printer: name of the preferred printer, if C{None} the system's/user's default printer will be used @type printer: C{str} @param logger: you can pass an L{X2goLogger} object to the - L{X2goPrintAction} constructor + L{X2goPrintActionPRINT} constructor @type logger: C{instance} @param loglevel: if no L{X2goLogger} object has been supplied a new one will be constructed with the given loglevel @@ -263,8 +271,7 @@ class X2goPrintActionPRINT(X2goPrintAction): """ self.printer = printer - self.client_instance = client_instance - X2goPrintAction.__init__(self, logger=logger, loglevel=loglevel) + X2goPrintAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel) def do_print(self, pdf_file, job_title, spool_dir, ): """\ @@ -331,10 +338,12 @@ class X2goPrintActionPRINTCMD(X2goPrintAction): def __init__(self, client_instance=None, print_cmd=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ + @param client_instance: the underlying L{X2goClient} instance + @type client_instance: C{instance} @param print_cmd: external command to be called on incoming print jobs @type print_cmd: C{str} @param logger: you can pass an L{X2goLogger} object to the - L{X2goPrintAction} constructor + L{X2goPrintActionPRINTCMD} constructor @type logger: C{instance} @param loglevel: if no L{X2goLogger} object has been supplied a new one will be constructed with the given loglevel @@ -343,9 +352,8 @@ class X2goPrintActionPRINTCMD(X2goPrintAction): """ if print_cmd is None: print_cmd = defaults.DEFAULT_PRINTCMD_CMD - self.client_instance = client_instance self.print_cmd = print_cmd - X2goPrintAction.__init__(self, logger=logger, loglevel=loglevel) + X2goPrintAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel) def do_print(self, pdf_file, job_title, spool_dir): """\ @@ -392,18 +400,16 @@ class X2goPrintActionDIALOG(X2goPrintAction): open the print dialog. @type client_instance: C{instance} @param logger: you can pass an L{X2goLogger} object to the - L{X2goPrintAction} constructor + L{X2goPrintActionDIALOG} constructor @type logger: C{instance} @param loglevel: if no L{X2goLogger} object has been supplied a new one will be constructed with the given loglevel @type loglevel: C{int} """ - if client_instance is not None: - self.client_instance = client_instance - else: - raise x2go_exceptions.X2goPrintActionException('the DIALOG print actions needs to know the X2goClient instance (client=<instance>)') - X2goPrintAction.__init__(self, logger=logger, loglevel=loglevel) + if client_instance is None: + raise x2go_exceptions.X2goPrintActionException('the DIALOG print action needs to know the X2goClient instance (client=<instance>)') + X2goPrintAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel) def do_print(self, pdf_file, job_title, spool_dir): """\ diff --git a/x2go/printqueue.py b/x2go/printqueue.py index 647f879..f479760 100644 --- a/x2go/printqueue.py +++ b/x2go/printqueue.py @@ -29,7 +29,8 @@ of the incoming print job. __NAME__ = 'x2goprintqueue-pylib' # modules -import os, copy +import os +import copy import threading import gevent @@ -37,6 +38,8 @@ import gevent import defaults import utils import log +import printactions + # we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) from backends.printing import X2goClientPrinting as _X2goClientPrinting @@ -159,7 +162,7 @@ class X2goPrintQueue(threading.Thread): print_action = defaults.X2GO_PRINT_ACTIONS[print_action] if print_action in defaults.X2GO_PRINT_ACTIONS.values(): - self.print_action = eval ('%s(**kwargs)' % print_action) + self.print_action = eval ('printactions.%s(**kwargs)' % print_action) def run(self): """\ diff --git a/x2go/registry.py b/x2go/registry.py index 3f0a423..6ba4e6b 100644 --- a/x2go/registry.py +++ b/x2go/registry.py @@ -283,19 +283,7 @@ class X2goSessionRegistry(object): session_uuid = _virgin_sessions[0].get_uuid() _params = self.client_instance.session_profiles.to_session_params(profile_id) - try: del _params['server'] - except: pass - try: del _params['printing'] - except: pass - try: del _params['share_local_folders'] - except: pass - try: del _params['profile_name'] - except: pass - try: del _params['profile_id'] - except: pass - self(session_uuid).update_params(_params) - self.logger('using already initially-registered yet-unused session %s' % session_uuid, log.loglevel_NOTICE) return session_uuid @@ -303,17 +291,6 @@ class X2goSessionRegistry(object): session_uuid = self.get_session_of_session_name(session_name) _params = self.client_instance.session_profiles.to_session_params(profile_id) - try: del _params['server'] - except: pass - try: del _params['printing'] - except: pass - try: del _params['share_local_folders'] - except: pass - try: del _params['profile_name'] - except: pass - try: del _params['profile_id'] - except: pass - self(session_uuid).update_params(_params) self.logger('using already registered-by-session-name session %s' % session_uuid, log.loglevel_NOTICE) return session_uuid diff --git a/x2go/session.py b/x2go/session.py index 5356acb..64937a2 100644 --- a/x2go/session.py +++ b/x2go/session.py @@ -49,7 +49,7 @@ from defaults import X2GO_CLIENT_ROOTDIR as _X2GO_CLIENT_ROOTDIR from defaults import X2GO_SESSIONS_ROOTDIR as _X2GO_SESSIONS_ROOTDIR from defaults import X2GO_SSH_ROOTDIR as _X2GO_SSH_ROOTDIR -from defaults import SUPPORTED_SOUND, SUPPORTED_PRINTING, SUPPORTED_FOLDERSHARING +from defaults import SUPPORTED_SOUND, SUPPORTED_PRINTING, SUPPORTED_FOLDERSHARING, SUPPORTED_DROPBOX # options of the paramiko.SSHClient().connect() _X2GO_SESSION_PARAMS = ('geometry', 'depth', 'link', 'pack', @@ -77,6 +77,9 @@ class X2goSession(object): profile_id=None, profile_name='UNKNOWN', session_name=None, printing=False, + allow_dropbox=False, + dropbox_extensions=[], + dropbox_action='OPEN', allow_share_local_folders=False, share_local_folders=[], control_backend=_X2goControlSession, @@ -129,6 +132,9 @@ class X2goSession(object): self.printing = printing self.allow_share_local_folders = allow_share_local_folders self.share_local_folders = share_local_folders + self.allow_dropbox = allow_dropbox + self.dropbox_extensions = dropbox_extensions + self.dropbox_action = dropbox_action self._control_backend = control_backend self._terminal_backend = terminal_backend self._info_backend = info_backend @@ -237,6 +243,27 @@ class X2goSession(object): STILL UNDOCUMENTED """ + try: del params['server'] + except KeyError: pass + try: del params['profile_name'] + except KeyError: pass + try: del params['profile_id'] + except KeyError: pass + try: del params['printing'] + except KeyError: pass + try: del params['allow_share_local_folders'] + except KeyError: pass + try: del params['share_local_folders'] + except KeyError: pass + try: del params['allow_dropbox'] + except KeyError: pass + try: del params['dropbox_extensions'] + except KeyError: pass + try: del params['dropbox_action'] + except KeyError: pass + try: del params['use_sshproxy'] + except KeyError: pass + _terminal_params = copy.deepcopy(params) _control_params = copy.deepcopy(params) _sshproxy_params = copy.deepcopy(params) @@ -250,10 +277,6 @@ class X2goSession(object): else: del _sshproxy_params[p] del _terminal_params[p] - try: del _sshproxy_params['use_sshproxy'] - except KeyError: pass - try: del _control_params['allow_share_local_folders'] - except KeyError: pass self.control_params.update(_control_params) self.terminal_params.update(_terminal_params) @@ -563,7 +586,9 @@ class X2goSession(object): _terminal.start_sound() - if (SUPPORTED_PRINTING and self.printing) or (SUPPORTED_FOLDERSHARING and self.allow_share_local_folders and self.share_local_folders): + if (SUPPORTED_PRINTING and self.printing) or \ + (SUPPORTED_DROPBOX and self.allow_dropbox) or \ + (SUPPORTED_FOLDERSHARING and self.allow_share_local_folders and self.share_local_folders): _terminal.start_sshfs() try: @@ -572,6 +597,9 @@ class X2goSession(object): except X2goUserException: pass + if SUPPORTED_DROPBOX and self.allow_dropbox: + _terminal.start_dropbox(dropbox_extensions=self.dropbox_extensions, dropbox_action=self.dropbox_action) + if SUPPORTED_FOLDERSHARING and self.share_local_folders: if _control.get_transport().reverse_tunnels[_terminal.get_session_name()]['sshfs'][1] is not None: for _folder in self.share_local_folders: diff --git a/x2go/utils.py b/x2go/utils.py index 37eca39..389127c 100644 --- a/x2go/utils.py +++ b/x2go/utils.py @@ -33,6 +33,7 @@ import paramiko # Python X2go modules from defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS from defaults import X2GO_SESSIONPROFILE_DEFAULTS as _X2GO_SESSIONPROFILE_DEFAULTS +from defaults import X2GO_DROPBOX_ACTIONS as _X2GO_DROPBOX_ACTIONS from defaults import _pack_methods_nx3 def is_in_nx3packmethods(method): @@ -126,6 +127,9 @@ def _convert_SessionProfileOptions_2_SessionParams(_options): 'sshport': 'port', 'useexports': 'allow_share_local_folders', 'export': 'share_local_folders', + 'usedropbox': 'allow_dropbox', + 'dropboxextensions': 'dropbox_extensions', + 'dropboxaction': 'dropbox_action', 'print': 'printing', 'name': 'profile_name', 'key': 'key_filename', @@ -168,18 +172,12 @@ def _convert_SessionProfileOptions_2_SessionParams(_options): _params['link'] = val # share_local_folders is a list - if opt == 'share_local_folders': + if opt in ('share_local_folders', 'dropbox_extensions'): if type(val) is types.StringType: if val: - _params[opt] = _params[opt].split(',') + _params[opt] = val.split(',') else: _params[opt] = [] - #del _params['export'] - if not _options['fstunnel']: - _params[opt] = None - - if not val: - val = None # append value for quality to value for pack method if _params['quality']: @@ -206,6 +204,9 @@ def _convert_SessionProfileOptions_2_SessionParams(_options): _params['session_type'] = 'desktop' del _params['rootless'] + if _params['dropbox_action'] not in _X2GO_DROPBOX_ACTIONS.keys(): + _params['dropbox_action'] = 'OPEN' + # currently known but ignored in Python X2go _ignored_options = [ 'dpi', hooks/post-receive -- python-x2go.git (Python X2Go Client API) This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "python-x2go.git" (Python X2Go Client API).