The branch, master has been updated via 8d835dcdec07d7f620a25cbcd4aafd88a1a044c7 (commit) from 19142c8cf8165b553780cfc33048df5ae11890ef (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 ----------------------------------------------------------------- commit 8d835dcdec07d7f620a25cbcd4aafd88a1a044c7 Author: Mike Gabriel <mike.gabriel@das-netzwerkteam.de> Date: Mon Nov 19 22:37:19 2012 +0100 Implement functionality for restoring mounted shares on session resumption / re-start. Sponsored by Dick Kniep, LinDix NL. ----------------------------------------------------------------------- Summary of changes: debian/changelog | 2 + x2go/backends/profiles/_file.py | 42 ++++++++++++-- x2go/cache.py | 1 + x2go/client.py | 36 +++++++++++- x2go/defaults.py | 2 +- x2go/registry.py | 10 ++++ x2go/session.py | 121 +++++++++++++++++++++++++++++---------- x2go/utils.py | 1 + 8 files changed, 178 insertions(+), 37 deletions(-) The diff of changes is: diff --git a/debian/changelog b/debian/changelog index 3cd247b..871adcb 100644 --- a/debian/changelog +++ b/debian/changelog @@ -48,6 +48,8 @@ python-x2go (0.2.1.0-0~x2go1) UNRELEASED; urgency=low get_published_applications() of control sessions. - Implement some internal locking for X2goSession objects. - Add option to disable auto-registration of pubapp sessions. + - Implement functionality for restoring mounted shares on session + resumption / re-start. Sponsored by Dick Kniep, LinDix NL. * /debian/rules: + Allow package build on systems with missing dh_python2. * /debian/control: diff --git a/x2go/backends/profiles/_file.py b/x2go/backends/profiles/_file.py index 586f262..24c2912 100644 --- a/x2go/backends/profiles/_file.py +++ b/x2go/backends/profiles/_file.py @@ -28,6 +28,7 @@ __NAME__ = 'x2gosessionprofiles-pylib' import copy import types +import re # Python X2Go modules from x2go.defaults import X2GO_SESSIONPROFILES_CONFIGFILES as _X2GO_SESSIONPROFILES_CONFIGFILES @@ -177,12 +178,14 @@ class X2goSessionProfilesFILE(inifiles.X2goIniFile): # we have to handle the get_type method separately... return self.get_profile_option_type(key) - def get_profile_config(self, profile_id_or_name=None, profile_id=None): + def get_profile_config(self, profile_id_or_name=None, parameter=None, profile_id=None): """\ The configuration options for a single session profile. @param profile_id_or_name: either profile ID or profile name is accepted @type profile_id_or_name: C{str} + @param parameter: if specified, only the value for the given parameter is returned + @type parameter: C{str} @param profile_id: profile ID (fast than specifying C{profile_id_or_name}) @type profile_id: C{str} @@ -194,6 +197,27 @@ class X2goSessionProfilesFILE(inifiles.X2goIniFile): _profile_config = {} for option in self.iniConfig.options(_profile_id): _profile_config[option] = self.get(_profile_id, option, key_type=self.get_profile_option_type(option)) + + if parameter is not None: + if parameter in _profile_config.keys(): + + value = _profile_config[parameter] + + if parameter == 'export': + _strvalue = value.replace(',', ';').strip().strip('"').strip().strip(';').strip() + value = {} + if _strvalue: + _export_paths = _strvalue.split(';') + for _path in _export_paths: + if not re.match('.*:(0|1)$', _path): _path = '%s:1' % _path + _auto_export_path = re.match('.*:1$', _path) and True or False + _export_path = ':'.join(_path.split(':')[:-1]) + value[_export_path] = _auto_export_path + + return value + + else: + raise X2goProfileException('no such session profile parameter: %s' % parameter) return _profile_config def default_profile_config(self): @@ -362,17 +386,27 @@ class X2goSessionProfilesFILE(inifiles.X2goIniFile): @type value: any type, depends on the session profile option """ - profile_id = section + profile_id = self.check_profile_id_or_name(section) if key == 'name': profile_name = value - current_profile_name = self.get_value(section, key) + current_profile_name = self.get_value(profile_id, key) if not profile_name: raise X2goProfileException('profile name for profile id %s may not be empty' % profile_id) else: if profile_name != current_profile_name and profile_name in self.profile_names: raise X2goProfileException('a profile of name ,,%s'' already exists' % profile_name) self._cached_profile_names = [] - inifiles.X2goIniFile.update_value(self, section, key, value) + + if key == 'export' and type(value) == types.DictType: + + _strvalue = '"' + for folder in value.keys(): + _strvalue += "%s:%s;" % (folder, int(value[folder])) + _strvalue += '"' + _strvalue = _strvalue.replace('""', '') + value = _strvalue + + inifiles.X2goIniFile.update_value(self, profile_id, key, value) def check_profile_id_or_name(self, profile_id_or_name): """\ diff --git a/x2go/cache.py b/x2go/cache.py index 5d90191..7c73692 100644 --- a/x2go/cache.py +++ b/x2go/cache.py @@ -149,6 +149,7 @@ class X2goListSessionsCache(object): @raise X2goControlSessionException: if the control session's C{list_mounts} method fails """ try: + self.x2go_listsessions_cache[profile_name]['mounts'] = {} if self.x2go_listsessions_cache[profile_name]['sessions']: for session_name in self.x2go_listsessions_cache[profile_name]['sessions']: self.x2go_listsessions_cache[profile_name]['mounts'].update(control_session.list_mounts(session_name)) diff --git a/x2go/client.py b/x2go/client.py index 1dd219b..142e970 100644 --- a/x2go/client.py +++ b/x2go/client.py @@ -1919,6 +1919,13 @@ class X2goClient(object): Get a list of local folders mounted within X2Go session with session hash <session_uuid> from this client. + @param session_uuid: the X2Go session's UUID registry hash + @type session_uuid: C{str} + @param profile_name: alternatively, the profile name can be used to get mounted folders of a session connected profile + @type profile_name: C{str} + @param check_list_mounts: query the server-side mount list for up-to-date information + @type check_list_mounts: C{bool} + @return: returns a C{list} of those local folder names that are mounted within X2Go session <session_uuid>. @rtype: C{list} @@ -2710,7 +2717,7 @@ class X2goClient(object): ### Session profile oriented methods ### - def get_profile_config(self, profile_id_or_name): + def get_profile_config(self, profile_id_or_name, parameter=None): """\ Returns a dictionary with session options and values that represent the session profile for C{profile_id_or_name}. @@ -2718,15 +2725,38 @@ class X2goClient(object): @param profile_id_or_name: name or id of an X2Go session profile as found in the sessions configuration file @type profile_id_or_name: C{str} + @param parameter: if specified, only the value for the given parameter is returned + @type parameter: C{str} @return: a Python dictionary with session profile options - @rtype: C{dict} + @rtype: C{dict} or C{bool}, C{int}, C{str} """ - return self.session_profiles.get_profile_config(profile_id_or_name) + return self.session_profiles.get_profile_config(profile_id_or_name, parameter=parameter) __get_profile_config = get_profile_config with_profile_config = get_profile_config + def set_profile_config(self, profile_id_or_name, parameter, value): + """\ + Set individual session profile parameters for session profile C{profile_id_or_name}. + + @param profile_id_or_name: name or id of an X2Go session profile as found + in the sessions configuration file + @type profile_id_or_name: C{str} + @param parameter: set this parameter with the given C{value} + @type parameter: C{str} + @param value: set this value for the given C{parameter} + @type value: C{bool}, C{int}, C{str}, C{list} or C{dict} + + @return: returns C{True} if this operation has been successful + @rtype: C{dict} + + """ + self.session_profiles.update_value(profile_id_or_name, parameter, value) + self.session_profiles.write_user_config = True + self.session_profiles.write() + __set_profile_config = set_profile_config + def to_profile_id(self, profile_name): """\ Retrieve the session profile ID of the session whose profile name diff --git a/x2go/defaults.py b/x2go/defaults.py index b158bb2..58a0615 100644 --- a/x2go/defaults.py +++ b/x2go/defaults.py @@ -297,7 +297,7 @@ X2GO_SESSIONPROFILE_DEFAULTS = { 'iconvto': 'UTF-8', 'iconvfrom': 'UTF-8', 'useiconv': False, 'usesshproxy': False, 'sshproxyhost': 'proxyhost.mydomain', 'sshproxyport': 22, 'sshproxyuser': '', 'sshproxytunnel': 'localhost:44444:server.mydomain.private:22', 'sshproxykeyfile': '', 'sshproxytype': 'SSH', 'sshproxysameuser': False, 'sshproxysamepass': False, 'sshproxyautologin': True, - 'useexports': True, 'fstunnel': True, 'export': '', + 'useexports': True, 'restoreexports': False, 'fstunnel': True, 'export': '', 'usemimebox': False, 'mimeboxextensions': '', 'mimeboxaction': 'OPEN', 'fullscreen': False, 'width': 800,'height': 600, 'maxdim': False, 'dpi': 96, 'setdpi': False, 'xinerama': False, 'multidisp': False, diff --git a/x2go/registry.py b/x2go/registry.py index e7b6732..5b4d67f 100644 --- a/x2go/registry.py +++ b/x2go/registry.py @@ -332,6 +332,11 @@ class X2goSessionRegistry(object): # unregister as master session if _profile_name in self.master_sessions.keys(): if self.master_sessions[_profile_name] == self(_session_uuid): + + # save exported folders to session profile config if requested by session profile parameter ,,restoreexports''... + if self.client_instance and self(_session_uuid).restore_shared_local_folders: + self.client_instance.set_profile_config(_profile_name, 'export', self(_session_uuid)._restore_exported_folders) + self(_session_uuid).unset_master_session() del self.master_sessions[_profile_name] @@ -344,6 +349,11 @@ class X2goSessionRegistry(object): # unregister as master session if _profile_name in self.master_sessions.keys(): if self.master_sessions[_profile_name] == self(_session_uuid): + + # save exported folders to session profile config if requested by session profile parameter ,,restoreexports''... + if self.client_instance and self(_session_uuid).restore_shared_local_folders: + self.client_instance.set_profile_config(_profile_name, 'export', self(_session_uuid)._restore_exported_folders) + self(_session_uuid).unset_master_session() del self.master_sessions[_profile_name] diff --git a/x2go/session.py b/x2go/session.py index da67aaf..31d7aea 100644 --- a/x2go/session.py +++ b/x2go/session.py @@ -89,7 +89,7 @@ _X2GO_SESSION_PARAMS = ('use_sshproxy', 'sshproxy_reuse_authinfo', 'auto_start_or_resume', 'auto_connect', 'printing', 'allow_mimebox', 'mimebox_extensions', 'mimebox_action', - 'allow_share_local_folders', 'share_local_folders', + 'allow_share_local_folders', 'share_local_folders', 'restore_shared_local_folders', 'control_backend', 'terminal_backend', 'info_backend', 'list_backend', 'proxy_backend', 'settings_backend', 'printing_backend', 'client_rootdir', 'sessions_rootdir', 'ssh_rootdir', 'keep_controlsession_alive', 'add_to_known_hosts', 'known_hosts', 'forward_sshagent', @@ -145,6 +145,7 @@ class X2goSession(object): mimebox_action='OPEN', allow_share_local_folders=False, share_local_folders=[], + restore_shared_local_folders=False, control_backend=_X2goControlSession, terminal_backend=_X2goTerminalSession, info_backend=_X2goServerSessionInfo, @@ -194,6 +195,8 @@ class X2goSession(object): @type allow_share_local_folders: C{bool} @param share_local_folders: list of local folders to share with the remote X2Go session @type share_local_folders: C{list} + @param restore_shared_local_folders: store actual list of shared local folders after session has been suspended or terminated + @type restore_shared_local_folders: C{bool} @param control_backend: X2Go control session backend to use @type control_backend: C{class} @param terminal_backend: X2Go terminal session backend to use @@ -276,6 +279,7 @@ class X2goSession(object): self.printing = printing self.allow_share_local_folders = allow_share_local_folders self.share_local_folders = share_local_folders + self.restore_shared_local_folders = restore_shared_local_folders self.allow_mimebox = allow_mimebox self.mimebox_extensions = mimebox_extensions self.mimebox_action = mimebox_action @@ -364,6 +368,9 @@ class X2goSession(object): self._progress_status = 0 self._lock = threading.Lock() + if self.client_instance and self.restore_shared_local_folders: + self._restore_exported_folders = self.client_instance.get_profile_config(self.profile_name, 'export') + def get_client_instance(self): """\ Return parent L{X2goClient} instance if avaiable. @@ -569,9 +576,9 @@ class X2goSession(object): gevent.sleep(1) if wait: - gevent.spawn_later(wait, self.share_all_local_folders) + gevent.spawn_later(wait, self.share_all_local_folders, update_exported_folders=False) else: - gevent.spawn(self.share_all_local_folders) + gevent.spawn(self.share_all_local_folders, update_exported_folders=False) __set_master_session = set_master_session def unset_master_session(self): @@ -581,7 +588,7 @@ class X2goSession(object): """ # unmount shared folders if self.has_terminal_session(): - self.unshare_all_local_folders() + self.unshare_all_local_folders(update_exported_folders=False) self.master_session = False __unset_master_session = unset_master_session @@ -719,6 +726,10 @@ class X2goSession(object): del params['share_local_folders'] except KeyError: pass try: + self.restore_shared_local_folders = params['restore_shared_local_folders'] + del params['restore_shared_local_folders'] + except KeyError: pass + try: self.allow_mimebox = params['allow_mimebox'] del params['allow_mimebox'] except KeyError: pass @@ -1290,6 +1301,7 @@ class X2goSession(object): self.terminated = None self.faults = None self.active = False + self._lock.release() self.unset_master_session() try: self.update_status(force_update=True) @@ -1894,7 +1906,7 @@ class X2goSession(object): self._progress_status = 90 progress_event.set() - # if there is a client instance for X2Go sessions that the client instance will handle the mounting of shared folders + # if self.client_instance exists than the folder sharing is handled via the self.set_master_session() evoked by the session registry if (not self.client_instance) and \ self._SUPPORTED_FOLDERSHARING and \ self.allow_share_local_folders: @@ -2104,6 +2116,10 @@ class X2goSession(object): @raise X2goSessionException: if the session could not be suspended """ + _store_shared_folders = {} + for folder in self.shared_folders: + _store_shared_folders.update({ folder: True }) + self._lock.acquire() try: _retval = self._suspend() @@ -2111,6 +2127,7 @@ class X2goSession(object): raise finally: self._lock.release() + return _retval def _suspend(self): @@ -2137,12 +2154,12 @@ class X2goSession(object): self.unset_master_session() - if self.terminal_session.suspend(): - - self.session_cleanup() - del self.terminal_session - self.terminal_session = None - return True + if self.has_terminal_session(): + if self.terminal_session.suspend(): + self.session_cleanup() + del self.terminal_session + self.terminal_session = None + return True elif self.has_control_session() and self.session_name: if self.control_session.suspend(session_name=self.session_name): @@ -2181,6 +2198,7 @@ class X2goSession(object): raise finally: self._lock.release() + return _retval def _terminate(self): @@ -2207,11 +2225,12 @@ class X2goSession(object): self.unset_master_session() - if self.terminal_session.terminate(): - self.session_cleanup() - del self.terminal_session - self.terminal_session = None - return True + if self.has_terminal_session(): + if self.terminal_session.terminate(): + self.session_cleanup() + del self.terminal_session + self.terminal_session = None + return True elif self.has_control_session() and self.session_name: if self.control_session.terminate(session_name=self.session_name): @@ -2397,7 +2416,19 @@ class X2goSession(object): return False __is_folder_sharing_available = is_folder_sharing_available - def share_local_folder(self, local_path=None, folder_name=None): + def _update_restore_exported_folders(self): + + # remember exported folders for restoring them on session suspension/termination + if self.client_instance and self.restore_shared_local_folders: + _exported_folders = self.client_instance.get_profile_config(self.profile_name, 'export') + for folder in self.shared_folders: + _exported_folders.update({ unicode(folder): True }) + for folder in _exported_folders.keys(): + if (folder not in self.shared_folders) and _exported_folders[folder]: + del _exported_folders[folder] + self._restore_exported_folders = _exported_folders + + def share_local_folder(self, local_path=None, folder_name=None, update_exported_folders=True): """\ Share a local folder with this registered X2Go session. @@ -2406,6 +2437,8 @@ class X2goSession(object): @type local_path: C{str} @param folder_name: synonymous to C{local_path} @type folder_name: C{str} + @param update_exported_folders: do an update of the session profile option ,,export'' after the operation + @type update_exported_folders: C{bool} @return: returns C{True} if the local folder has been successfully mounted within this X2Go session @@ -2419,18 +2452,26 @@ class X2goSession(object): if self.has_terminal_session(): if self.is_folder_sharing_available() and self.is_master_session(): + # append local_path to list of shared folders before mounting + self.shared_folders.append(local_path) if self.terminal_session.share_local_folder(local_path=local_path): - self.shared_folders.append(local_path) + if update_exported_folders: + self._update_restore_exported_folders() return True + # if we failed remove the local_path element again... + self.shared_folders.remove(local_path) return False else: raise x2go_exceptions.X2goSessionException('this X2goSession object does not have any associated terminal') __share_local_folder = share_local_folder - def share_all_local_folders(self): + def share_all_local_folders(self, update_exported_folders=True): """\ Share all local folders configured to be mounted within this X2Go session. + @param update_exported_folders: do an update of the session profile option ,,export'' after the operation + @type update_exported_folders: C{bool} + @return: returns C{True} if all local folders could be successfully mounted inside this X2Go session @rtype: C{bool} @@ -2444,21 +2485,26 @@ class X2goSession(object): _retval = True for _folder in self.share_local_folders: try: - _retval = self.share_local_folder(_folder) and _retval + _retval = self.share_local_folder(_folder, update_exported_folders=False) and _retval except x2go_exceptions.X2goUserException, e: self.logger('%s' % str(e), loglevel=log.loglevel_WARN) + if update_exported_folders: + self._update_restore_exported_folders() + else: self.HOOK_foldersharing_not_available() return _retval __share_all_local_folders = share_all_local_folders - def unshare_all_local_folders(self, force_all=False): + def unshare_all_local_folders(self, force_all=False, update_exported_folders=True): """\ Unshare all local folders mounted within this X2Go session. @param force_all: Really unmount _all_ shared folders, including the print spool folder and the MIME box spool dir (not recommended). @type force_all: C{bool} + @param update_exported_folders: do an update of the session profile option ,,export'' after the operation + @type update_exported_folders: C{bool} @return: returns C{True} if all local folders could be successfully unmounted inside this X2Go session @@ -2471,19 +2517,23 @@ class X2goSession(object): if self.is_folder_sharing_available() and self.is_master_session(): if force_all: self.shared_folders = [] - return self.terminal_session.unshare_all_local_folders() + retval = self.terminal_session.unshare_all_local_folders() + return retval else: + _shared_folders = copy.deepcopy(self.shared_folders) + self.shared_folders = [] retval = 0 - for _shared_folder in self.shared_folders: + for _shared_folder in _shared_folders: retval = retval | self.terminal_session.unshare_local_folder(_shared_folder) - self.shared_folders = [] + if update_exported_folders: + self._update_restore_exported_folders() return retval else: raise x2go_exceptions.X2goSessionException('this X2goSession object does not have any associated terminal') return False __unshare_all_local_folders = unshare_all_local_folders - def unshare_local_folder(self, local_path=None): + def unshare_local_folder(self, local_path=None, update_exported_folders=True): """\ Unshare a local folder that is mounted within this X2Go session. @@ -2491,6 +2541,9 @@ class X2goSession(object): file system that is mounted in this X2Go session and shall be unmounted @type local_path: C{str} + @param update_exported_folders: do an update of the session profile option ,,export'' after the operation + @type update_exported_folders: C{bool} + @return: returns C{True} if all local folders could be successfully unmounted inside this X2Go session @rtype: C{bool} @@ -2500,10 +2553,18 @@ class X2goSession(object): """ if self.has_terminal_session(): if self.is_folder_sharing_available() and self.is_master_session() and local_path in self.shared_folders: + # update shared_folders before(!) unmounting... self.shared_folders.remove(local_path) - return self.terminal_session.unshare_local_folder(local_path=local_path) + _retval = self.terminal_session.unshare_local_folder(local_path=local_path) + # unmounting failed? Re-add local_patch to shared_folders for now... + if not _retval: + self.shared_folders.append(local_path) + if _retval and update_exported_folders: + self._update_restore_exported_folders() else: raise x2go_exceptions.X2goSessionException('this X2goSession object does not have any associated terminal') + + return _retval __unshare_local_folder = unshare_local_folder def get_shared_folders(self, check_list_mounts=False, mounts=None): @@ -2531,21 +2592,22 @@ class X2goSession(object): _found = False for mount in mounts: + if _found: continue mount = mount.split('|')[1] if _X2GOCLIENT_OS == 'Windows': + _driveletter, _path = os.path.splitdrive(shared_folder) _mount_point = '_windrive_%s%s' % (_driveletter[0], _path.replace('\\', '_')) _mount_point = _mount_point.replace(' ', '_') if mount.lower().endswith(_mount_point.lower()): _found = True - break + else: _mount_point = shared_folder.replace('/', '_') _mount_point = _mount_point.replace(' ', '_') if mount.endswith(_mount_point): _found = True - break if not _found: unshared_folders.append(shared_folder) @@ -2553,11 +2615,12 @@ class X2goSession(object): for unshared_folder in unshared_folders: try: self.shared_folders.remove(unshared_folder) + self._update_restore_exported_folders() self.logger('Detected server-side unsharing of client-side folder for profile %s: %s:' % (self.get_profile_name(), shared_folder), loglevel=log.loglevel_INFO) except IndexError: pass - return self.shared_folders + return [ unicode(s) for s in self.shared_folders ] __get_shared_folders = get_shared_folders def is_locked(self): diff --git a/x2go/utils.py b/x2go/utils.py index 0a25fd3..ab98120 100644 --- a/x2go/utils.py +++ b/x2go/utils.py @@ -186,6 +186,7 @@ def _convert_SessionProfileOptions_2_SessionParams(options): 'speed': 'link', 'sshport': 'port', 'useexports': 'allow_share_local_folders', + 'restoreexports': 'restore_shared_local_folders', 'usemimebox': 'allow_mimebox', 'mimeboxextensions': 'mimebox_extensions', 'mimeboxaction': 'mimebox_action', 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).