[X2Go-Commits] python-x2go.git - build-baikal (branch) updated: 79a34563e372ff34bc23ba3d6090c4175221f2ed
X2Go dev team
git-admin at x2go.org
Wed Jan 8 15:25:20 CET 2014
The branch, build-baikal has been updated
via 79a34563e372ff34bc23ba3d6090c4175221f2ed (commit)
from 2111f148a5af2349cd426f4274561fa801bca9ff (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/__init__.py | 2 +-
{tests => x2go/backends}/__init__.py | 9 +-
test.py => x2go/backends/control/__init__.py | 21 +-
x2go/backends/control/stdout.py | 496 ++++++++
test.py => x2go/backends/info/__init__.py | 21 +-
x2go/backends/info/stdout.py | 165 +++
test.py => x2go/backends/profiles/__init__.py | 21 +-
.../profiles/https_broker.py} | 18 +-
.../profiles/sessions_file.py} | 18 +-
.../profiles/win_registry.py} | 19 +-
{tests => x2go/backends/proxy}/__init__.py | 13 +-
x2go/{proxy.py => backends/proxy/base.py} | 107 +-
x2go/backends/proxy/nx3.py | 128 ++
test.py => x2go/backends/terminal/__init__.py | 18 +-
x2go/backends/terminal/stdout.py | 720 +++++++++++
x2go/client.py | 28 +-
x2go/defaults.py | 37 +-
x2go/printing.py | 2 +-
x2go/registry.py | 415 +------
x2go/rforward.py | 14 +-
x2go/session.py | 1298 +++++---------------
21 files changed, 1975 insertions(+), 1595 deletions(-)
copy {tests => x2go/backends}/__init__.py (80%)
copy test.py => x2go/backends/control/__init__.py (67%)
create mode 100644 x2go/backends/control/stdout.py
copy test.py => x2go/backends/info/__init__.py (59%)
create mode 100644 x2go/backends/info/stdout.py
copy test.py => x2go/backends/profiles/__init__.py (63%)
copy x2go/{profiles.py => backends/profiles/https_broker.py} (94%)
copy x2go/{profiles.py => backends/profiles/sessions_file.py} (94%)
rename x2go/{profiles.py => backends/profiles/win_registry.py} (94%)
copy {tests => x2go/backends/proxy}/__init__.py (73%)
rename x2go/{proxy.py => backends/proxy/base.py} (62%)
create mode 100644 x2go/backends/proxy/nx3.py
copy test.py => x2go/backends/terminal/__init__.py (69%)
create mode 100644 x2go/backends/terminal/stdout.py
The diff of changes is:
diff --git a/x2go/__init__.py b/x2go/__init__.py
index 77a5b2b..70c7ba2 100644
--- a/x2go/__init__.py
+++ b/x2go/__init__.py
@@ -166,7 +166,7 @@ _signal.signal (_signal.SIGTERM, guardian._sigterm_handle )
_signal.signal (_signal.SIGINT, guardian._sigterm_handle )
from client import X2goClient
-from profiles import X2goSessionProfiles
+from backends.profiles import X2goSessionProfiles
from printing import X2goClientPrinting
from settings import X2goClientSettings
from x2go_exceptions import *
diff --git a/tests/__init__.py b/x2go/backends/__init__.py
similarity index 80%
copy from tests/__init__.py
copy to x2go/backends/__init__.py
index 6e5cefb..d2ad588 100644
--- a/tests/__init__.py
+++ b/x2go/backends/__init__.py
@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2010 by Mike Gabriel <m.gabriel at 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 2 of the License, or
+# 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,
@@ -15,7 +15,4 @@
# 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.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-
-import runalltests
-import test_printing
\ No newline at end of file
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
diff --git a/test.py b/x2go/backends/control/__init__.py
similarity index 67%
copy from test.py
copy to x2go/backends/control/__init__.py
index 34b2284..e7780c4 100644
--- a/test.py
+++ b/x2go/backends/control/__init__.py
@@ -1,11 +1,10 @@
-#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (C) 2010 by Mike Gabriel <m.gabriel at 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 2 of the License, or
+# 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,
@@ -16,13 +15,13 @@
# 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.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+
+from gevent import monkey
+monkey.patch_all()
+
+from x2go.defaults import DEFAULT_CONTROLSESSION_BACKEND
-"""
-Unit tests for Python X2go.
-"""
-import os
+from stdout import X2goControlSessionSTDOUT
-if __name__ == "__main__":
- os.chdir('tests')
- os.system('./runalltests.py')
\ No newline at end of file
+X2goControlSession = eval(DEFAULT_CONTROLSESSION_BACKEND)
diff --git a/x2go/backends/control/stdout.py b/x2go/backends/control/stdout.py
new file mode 100644
index 0000000..31089c3
--- /dev/null
+++ b/x2go/backends/control/stdout.py
@@ -0,0 +1,496 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2010 by Mike Gabriel <m.gabriel at 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.
+
+"""\
+X2goControlSessionSTDOUT class - core functions for handling your individual X2go sessions.
+
+This backend handles X2go server implementations that respond via server-side STDOUT.
+
+"""
+__NAME__ = 'x2gocontrolsession-pylib'
+
+# modules
+import types
+import paramiko
+
+import copy
+
+# Python X2go modules
+import x2go.log as log
+import x2go.utils as utils
+import x2go.x2go_exceptions as x2go_exceptions
+import x2go.defaults as defaults
+
+from x2go.backends.terminal import X2goTerminalSession as _X2goTerminalSession
+from x2go.backends.info import X2goServerSessionInfo as _X2goServerSessionInfo
+from x2go.backends.info import X2goServerSessionList as _X2goServerSessionList
+
+class X2goControlSessionSTDOUT(paramiko.SSHClient):
+ """\
+ STILL UNDOCUMENTED
+
+ @param logger: you can pass an L{X2goLogger} object to the
+ L{X2goProxy} constructor
+ @type logger: L{X2goLogger} instance
+ @param loglevel: if no L{X2goLogger} object has been supplied a new one will be
+ constructed with the given loglevel
+ @type loglevel: int
+
+ """
+ associated_terminals = {}
+ terminated_terminals = []
+
+ _session_auth_rsakey = None
+ _remote_home = None
+ _remote_group = {}
+
+ def __init__(self,
+ terminal_backend=_X2goTerminalSession,
+ info_backend=_X2goServerSessionInfo,
+ list_backend=_X2goServerSessionList,
+ logger=None, loglevel=log.loglevel_DEFAULT,
+ *args, **kwargs):
+ """\
+ Initialize an X2go session. With the X2goSession class you can start
+ new X2go sessions, resume suspended sessions or suspend resp. terminate
+ currently running sessions on a connected X2go server.
+
+ """
+ if logger is None:
+ self.logger = log.X2goLogger(loglevel=loglevel)
+ else:
+ self.logger = copy.deepcopy(logger)
+ self.logger.tag = __NAME__
+
+ self._terminal_backend = terminal_backend
+ self._info_backend = info_backend
+ self._list_backend = list_backend
+ paramiko.SSHClient.__init__(self, *args, **kwargs)
+
+ def __del__(self):
+
+ self.disconnect()
+
+ def _x2go_sftp_put(self, local_path, remote_path):
+
+ self.logger('sFTP-put: %s -> %s:%s' % (local_path, self.get_transport().getpeername(), remote_path), loglevel=log.loglevel_DEBUG)
+ self.sftp_client.put(local_path, remote_path)
+
+ def _x2go_sftp_write(self, remote_path, content):
+
+ self.logger('sFTP-write: opening remote file %s on host %s for writing' % (remote_path, self.get_transport().getpeername()), loglevel=log.loglevel_DEBUG)
+ remote_fileobj = self.sftp_client.open(remote_path, 'w')
+ self.logger('sFTP-write: writing content: %s' % content, loglevel=log.loglevel_DEBUG_SFTPXFER)
+ remote_fileobj.write(content)
+ remote_fileobj.close()
+
+ def _x2go_sftp_remove(self, remote_path):
+
+ self.logger('sFTP-write: removing remote file %s on host %s' % (remote_path, self.get_transport().getpeername()), loglevel=log.loglevel_DEBUG)
+ self.sftp_client.remove(remote_path)
+
+ def _x2go_exec_command(self, cmd_line, loglevel=log.loglevel_INFO, **kwargs):
+
+ if type(cmd_line) == types.ListType:
+ cmd = " ".join(cmd_line)
+ else:
+ cmd = cmd_line
+ if self.get_transport() is not None:
+
+ try:
+ self.logger('executing command on X2go server: %s' % cmd, loglevel)
+ return self.exec_command(cmd, **kwargs)
+ except AttributeError:
+ raise x2go_exceptions.X2goSessionException('a Paramiko/SSH control session has died')
+
+ else:
+ raise x2go_exceptions.X2goSessionException('the Paramiko/SSH client is not connected')
+
+ @property
+ def _x2go_remote_home(self):
+
+ if self._remote_home is None:
+ (stdin, stdout, stderr) = self._x2go_exec_command('echo $HOME')
+ self._remote_home = stdout.read().split()[0]
+ self.logger('remote user\' home directory: %s' % self._remote_home, loglevel=log.loglevel_DEBUG)
+ return self._remote_home
+ else:
+ return self._remote_home
+
+ def _x2go_remote_group(self, group):
+
+ if not self._remote_group.has_key(group):
+ (stdin, stdout, stderr) = self._x2go_exec_command('getent group %s | cut -d":" -f4' % group)
+ self._remote_group[group] = stdout.read().split('\n')[0].split(',')
+ self.logger('remote %s group: %s' % (group, self._remote_group[group]), loglevel=log.loglevel_DEBUG)
+ return self._remote_group[group]
+ else:
+ return self._remote_group[group]
+
+ @property
+ def _x2go_session_auth_rsakey(self):
+ if self._session_auth_rsakey is None:
+ self._session_auth_rsakey = paramiko.RSAKey.generate(defaults.RSAKEY_STRENGTH)
+ return self._session_auth_rsakey
+
+ def connect(self, hostname, port=22, username=None, password=None, pkey=None,
+ key_filename=None, timeout=None, allow_agent=False, look_for_keys=True,
+ add_to_known_hosts=False, force_password_auth=False):
+ """\
+ Connect to an X2go server and authenticate to it. This method is directly
+ inherited from the paramiko.SSHClient module. The features of the Paramiko
+ SSH client connect method are recited here. The parameters C{add_to_known_hosts}
+ and C{force_password_auth} have been added as a parameter for X2go.
+
+ The server's host key
+ is checked against the system host keys (see C{load_system_host_keys})
+ and any local host keys (C{load_host_keys}). If the server's hostname
+ is not found in either set of host keys, the missing host key policy
+ is used (see C{set_missing_host_key_policy}). The default policy is
+ to reject the key and raise an C{SSHException}.
+
+ Authentication is attempted in the following order of priority:
+
+ - The C{pkey} or C{key_filename} passed in (if any)
+ - Any key we can find through an SSH agent
+ - Any "id_rsa" or "id_dsa" key discoverable in C{~/.ssh/}
+ - Plain username/password auth, if a password was given
+
+ If a private key requires a password to unlock it, and a password is
+ passed in, that password will be used to attempt to unlock the key.
+
+ @param hostname: the server to connect to
+ @type hostname: str
+ @param port: the server port to connect to
+ @type port: int
+ @param username: the username to authenticate as (defaults to the
+ current local username)
+ @type username: str
+ @param password: a password to use for authentication or for unlocking
+ a private key
+ @type password: str
+ @param pkey: an optional private key to use for authentication
+ @type pkey: C{PKey}
+ @param key_filename: the filename, or list of filenames, of optional
+ private key(s) to try for authentication
+ @type key_filename: str or list(str)
+ @param timeout: an optional timeout (in seconds) for the TCP connect
+ @type timeout: float
+ @param allow_agent: set to False to disable connecting to the SSH agent
+ @type allow_agent: C{bool}
+ @param look_for_keys: set to False to disable searching for discoverable
+ private key files in C{~/.ssh/}
+ @type look_for_keys: C{bool}
+ @param add_to_known_hosts: non-paramiko option, if C{True} paramiko.AutoAddPolicy()
+ is used as missing-host-key-policy. If set to C{False} paramiko.RejectPolicy()
+ is used
+ @type add_to_known_hosts: C{bool}
+ @param force_password_auth: non-paramiko option, disable pub/priv key authentication
+ completely, even if the C{pkey} or the C{key_filename} parameter is given
+ @type force_password_auth: C{bool}
+
+ @raise BadHostKeyException: if the server's host key could not be
+ verified
+ @raise AuthenticationException: if authentication failed
+ @raise SSHException: if there was any other error connecting or
+ establishing an SSH session
+ @raise socket.error: if a socket error occurred while connecting
+
+ """
+ if add_to_known_hosts:
+ self.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+
+ # disable pub/priv key authentication if forced
+ if force_password_auth:
+ key_filename = None
+ pkey = None
+
+ self.logger('connecting to %s' % hostname, log.loglevel_NOTICE)
+
+ if (key_filename or pkey):
+ try:
+ self.logger('trying SSH pub/priv key authentication with server', log.loglevel_DEBUG)
+ paramiko.SSHClient.connect(self, hostname, port=port, username=username, pkey=pkey,
+ key_filename=key_filename, timeout=timeout, allow_agent=allow_agent,
+ look_for_keys=look_for_keys)
+ except paramiko.AuthenticationException, e:
+ if password:
+ self.logger('next auth mechanism we\'ll try is keyboard-interactive authentication', log.loglevel_DEBUG)
+ paramiko.SSHClient.connect(self, hostname, port=port, username=username, password=password,
+ timeout=timeout, allow_agent=allow_agent,
+ look_for_keys=look_for_keys)
+ else:
+ raise(e)
+
+ # if there is not private key, we will use the given password
+ elif password:
+ self.logger('performing SSH keyboard-interactive authentication with server', log.loglevel_DEBUG)
+ paramiko.SSHClient.connect(self, hostname, port=port, username=username, password=password,
+ timeout=timeout, allow_agent=allow_agent, look_for_keys=look_for_keys)
+
+ # authentication failed
+ else:
+ raise paramiko.AuthenticationException()
+
+ # if we succeed, we immediately grab us an sFTP client session
+ self.sftp_client = self.open_sftp()
+
+ # preparing reverse tunnels
+ ssh_transport = self.get_transport()
+ ssh_transport.reverse_tunnels = {}
+
+ # mark transport as X2goSession
+ ssh_transport._x2go_session_marker = True
+ self._session_password = password
+
+ return (self.get_transport() is not None)
+
+ def disconnect(self):
+ """\
+ STILL UNDOCUMENTED
+
+ """
+ for t in self.associated_terminals:
+ del t
+ if self.get_transport() is not None:
+ self.get_transport().close()
+
+ def start(self, **kwargs):
+ """\
+ Start a new X2go session.
+
+ The L{X2goControlSession.start()} method accepts any parameter
+ that can be passed to any of the L{X2goTerminalSession} class
+ constructors.
+
+ """
+ return self.resume(**kwargs)
+
+ def resume(self, session_name=None, **kwargs):
+ """\
+ Resume a running/suspended X2go session.
+
+ The L{X2goControlSession.resume()} method accepts any parameter
+ that can be passed to the L{X2goTerminalSession} class constructor.
+
+ @return: True if the session could be successfully resumed
+ @rtype: C{bool}
+
+ """
+ if self.get_transport().get_username() not in self._x2go_remote_group('x2gousers'):
+ raise x2go_exceptions.X2goSessionException('remote user %s is not member of X2go server group x2gousers' % self.get_transport().get_username())
+
+ _terminal = self._terminal_backend(self, **kwargs)
+ if session_name is not None:
+ if self.is_running(session_name):
+ self.suspend(session_name)
+ _terminal.resume()
+ else:
+ _terminal.start()
+
+ if _terminal.ok():
+ self.associated_terminals[_terminal.get_session_name()] = _terminal
+ self.get_transport().reverse_tunnels[_terminal.get_session_name()] = {
+ 'sshfs': (0, None),
+ 'snd': (0, None),
+ }
+
+ return _terminal or None
+
+ def list_sessions(self, raw=False):
+ """\
+ List all sessions of current user on the connected server.
+
+ @param raw: if C{True}, the raw output of the server-side X2go command
+ C{x2golistsessions} is returned.
+ @type raw: C{bool}
+
+ @return: normally an instance of L{X2goServerSessionList} is returned. However,
+ if the raw argument is set, the plain text output of the x2golistsessions
+ command is returned
+ @rtype: L{X2goServerSessionList} instance or str
+
+ """
+ (stdin, stdout, stderr) = self._x2go_exec_command("x2golistsessions")
+
+ if raw:
+ return stdout.read(), stderr.read()
+
+ _stdout_read = stdout.read()
+ return self._list_backend(_stdout_read, info_backend=self._info_backend).sessions
+
+ def clean_sessions(self):
+ """\
+ Find X2go terminals that have previously been started by the
+ connected user on the remote X2go server and terminate them.
+
+ """
+ session_infos = self.list_sessions()
+ for session_info in session_infos.values():
+ self.terminate(session_name=session_info)
+
+ def is_connected(self):
+ """\
+ Returns C{True} if this X2go session is connected to the remote server (that
+ is if it has a valid Paramiko Transport object).
+
+ @param session_name: X2go name of the session to be queried
+ @type session_name: str
+
+ @return: X2go session connected
+ @rtype: C{bool}
+
+ """
+ return self.get_transport() is not None and self.get_transport().is_authenticated()
+
+ def is_running(self, session_name):
+ """\
+ Returns C{True} if the given X2go session is in running state,
+ C{False} else.
+
+ @param session_name: X2go name of the session to be queried
+ @type session_name: str
+
+ @return: X2go session running?
+ @rtype: C{bool}
+
+ """
+ session_infos = self.list_sessions()
+ if session_name in session_infos.keys():
+ return session_infos[session_name].is_running()
+ return False
+
+ def is_suspended(self, session_name):
+ """\
+ Returns C{True} if the given X2go session is in suspended state,
+ C{False} else.
+
+ @return: X2go session suspended?
+ @rtype: C{bool}
+
+ """
+ session_infos = self.list_sessions()
+ if session_name in session_infos.keys():
+ return session_infos[session_name].is_suspended()
+ return False
+
+ def has_terminated(self, session_name):
+ """\
+ Returns C{True} if this X2go session is not in the session list on the
+ connected server, C{False} else.
+
+ Of course, if this command is called before session startup, it will also
+ return C{True}.
+
+ @return: X2go session has terminate?
+ @rtype: C{bool}
+
+ """
+ session_infos = self.list_sessions()
+
+
+ if session_name not in session_infos.keys():
+ if session_name in self.terminated_terminals:
+ return True
+ else:
+ # do a post-mortem tidy up
+ if session_name in self.associated_terminals.keys():
+ self.terminate(session_name)
+ return True
+
+ return False
+
+ def suspend(self, session_name):
+ """\
+ Suspend either this or another available X2go session on the connected
+ server.
+
+ If L{session_name} is given, L{X2goSession.suspend()} tries to suspend the
+ corresponding session.
+
+ @param session_name: X2go name of the session to be suspended
+ @type session_name: str
+
+ @return: True if the session could be successfully suspended
+ @rtype: C{bool}
+
+ """
+ _ret = False
+ _session_names = [ t.get_session_name() for t in associated_terminals.values() ]
+ if session_name in _session_names:
+
+ self.logger('suspending associated terminal session: %s' % session_name, log.loglevel_DEBUG)
+ (stdin, stdout, stderr) = self._x2go_exec_command("x2gosuspend-session %s" % session_name, loglevel=log.loglevel_DEBUG)
+ dummy_stdout = stdout.read()
+ dummy_stderr = stderr.read()
+ associated_terminals[session_name].__del__()
+ del associated_terminals[session_name]
+ _ret = True
+
+ else:
+
+ self.logger('suspending non-associated terminal session: %s' % session_name, log.loglevel_DEBUG)
+ (stdin, stdout, stderr) = self._x2go_exec_command("x2gosuspend-session %s" % session_name, loglevel=log.loglevel_DEBUG)
+ dummy_stdout = stdout.read()
+ dummy_stderr = stderr.read()
+ _ret = True
+
+ return _ret
+
+ def terminate(self, session_name):
+ """\
+ Terminate either this or another available X2go session on the connected
+ server.
+
+ If L{session_name} is given, L{X2goSession.terminate()} tries to terminate the
+ corresponding session.
+
+ @param session_name: X2go name of the session to be terminated
+ @type session_name: str
+
+ @return: True if the session could be successfully terminate
+ @rtype: C{bool}
+
+ """
+
+ _ret = False
+ _session_names = [ t.get_session_name() for t in self.associated_terminals.values() ]
+ if session_name in _session_names:
+
+ self.logger('suspending associated session: %s' % session_name, log.loglevel_DEBUG)
+ (stdin, stdout, stderr) = self._x2go_exec_command("x2goterminate-session %s" % session_name, loglevel=log.loglevel_DEBUG)
+ dummy_stdout = stdout.read()
+ dummy_stderr = stderr.read()
+ if self.associated_terminals[session_name] is not None:
+ self.associated_terminals[session_name].__del__()
+ del self.associated_terminals[session_name]
+ self.terminated_terminals.append(session_name)
+ _ret = True
+
+ else:
+
+ self.logger('suspending non-associated session: %s' % session_name, log.loglevel_DEBUG)
+ (stdin, stdout, stderr) = self._x2go_exec_command("x2goterminate-session %s" % session_name, loglevel=log.loglevel_DEBUG)
+ dummy_stdout = stdout.read()
+ dummy_stderr = stderr.read()
+ _ret = True
+
+ return _ret
+
+
diff --git a/test.py b/x2go/backends/info/__init__.py
similarity index 59%
copy from test.py
copy to x2go/backends/info/__init__.py
index 34b2284..0606d7b 100644
--- a/test.py
+++ b/x2go/backends/info/__init__.py
@@ -1,11 +1,10 @@
-#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (C) 2010 by Mike Gabriel <m.gabriel at 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 2 of the License, or
+# 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,
@@ -16,13 +15,13 @@
# 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.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+
+from x2go.defaults import DEFAULT_SERVERSESSIONINFO_BACKEND
+from x2go.defaults import DEFAULT_SERVERSESSIONLIST_BACKEND
-"""
-Unit tests for Python X2go.
-"""
-import os
+from stdout import X2goServerSessionInfoSTDOUT
+from stdout import X2goServerSessionListSTDOUT
-if __name__ == "__main__":
- os.chdir('tests')
- os.system('./runalltests.py')
\ No newline at end of file
+X2goServerSessionInfo = eval(DEFAULT_SERVERSESSIONINFO_BACKEND)
+X2goServerSessionList = eval(DEFAULT_SERVERSESSIONLIST_BACKEND)
diff --git a/x2go/backends/info/stdout.py b/x2go/backends/info/stdout.py
new file mode 100644
index 0000000..b675bb9
--- /dev/null
+++ b/x2go/backends/info/stdout.py
@@ -0,0 +1,165 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2010 by Mike Gabriel <m.gabriel at 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.
+
+"""\
+X2goServerSessionList and X2goServerSessionInfo classes - data handling for
+X2go server sessions.
+
+This backend handles X2go server implementations that respond with session infos
+via server-side STDOUT.
+
+"""
+__NAME__ = 'x2goserversessioninfo-pylib'
+
+class X2goServerSessionInfoSTDOUT(object):
+ """\
+ L{X2goServerSessionInfo} is used to store all information
+ that is retrieved from the connected X2go server on
+ L{X2goSession.start()} resp. L{X2goSession.resume()}.
+
+ """
+ def __str__(self):
+ return self.name
+ def __repr__(self):
+ return "<%s instance: %s>" % (self.__class__, self.name)
+
+ def _parse_x2golistsessions_line(self, x2go_output):
+ """\
+ Parse a single line of X2go's listsessions output.
+
+ """
+ l = x2go_output.split("|")
+ self.name = l[1]
+ self.cookie = l[6]
+ self.agent_pid = int(l[0])
+ self.display = int(l[2])
+ self.status = l[4]
+ self.graphics_port = int(l[8])
+ self.snd_port = int(l[9])
+ self.sshfs_port = int(l[13])
+ self.username = l[11]
+ self.hostname = l[3]
+ # TODO: turn into datetime object
+ self.date_created = l[5]
+ # TODO: turn into datetime object
+ self.date_suspended = l[10]
+ self.local_container = ''
+
+ def is_running(self):
+
+ return self.status == 'R'
+
+ def is_suspended(self):
+
+ return self.status == 'S'
+
+ def _parse_x2gostartagent_output(self, x2go_output):
+ """\
+ Parse x2gostartagent output.
+
+ """
+ l = x2go_output.split("\n")
+ self.name = l[3]
+ self.cookie = l[1]
+ self.agent_pid = int(l[2])
+ self.display = int(l[0])
+ self.graphics_port = int(l[4])
+ self.snd_port = int(l[5])
+ self.sshfs_port = int(l[6])
+ self.username = ''
+ self.hostname = ''
+ # TODO: we have to see how we fill these fields here...
+ self.date_created = ''
+ self.date_suspended = ''
+ # TODO: presume session is running after x2gostartagent, this could be better
+ self.status = 'R'
+ self.local_container = ''
+ self.remote_container = ''
+
+ def initialize(self, x2go_output, username='', hostname='', local_container='', remote_container=''):
+ """\
+ Parse X2go server's C{x2gostartagent} stdout values.
+
+ @param x2go_output: X2go server's C{x2gostartagent} command output, each value
+ separated by a newline character.
+ @type x2go_output: str
+ @param username: session user name
+ @type username: str
+ @param hostname: hostname of X2go server
+ @type hostname: str
+ @param local_container: X2go client session directory for config files, cache and session logs
+ @type local_container: str
+ @param remote_container: X2go server session directory for config files, cache and session logs
+ @type remote_container: str
+
+ """
+ self._parse_x2gostartagent_output(x2go_output)
+ self.username = username
+ self.hostname = hostname
+ self.local_container = local_container
+ self.remote_container = remote_container
+
+ def clear(self):
+ """\
+ Clear all properties of a L{X2goServerSessionInfo} object.
+
+ """
+ self.name = ''
+ self.cookie = ''
+ self.agent_pid = ''
+ self.display = ''
+ self.graphics_port = ''
+ self.snd_port = ''
+ self.sshfs_port = ''
+ self.username = ''
+ self.hostname = ''
+ self.date_created = ''
+ self.date_suspended = ''
+ self.status = ''
+ self.local_container = ''
+ self.remote_container = ''
+
+ __init__ = clear
+
+
+class X2goServerSessionListSTDOUT(object):
+ """\
+ L{X2goServerSessionList} is used to store all information
+ that is retrieved from a connected X2go server on a
+ L{X2goSession.list_sessions()} call.
+
+ """
+ def __init__(self, x2go_output, info_backend=X2goServerSessionInfoSTDOUT):
+ """\
+ @param x2go_output: X2go server's C{x2golistsessions} command output, each
+ session separated by a newline character. Session values are separated
+ by Unix Pipe Symbols ('|')
+ @type x2go_output: str
+
+ """
+ self.sessions = {}
+ lines = x2go_output.split("\n")
+ for line in lines:
+ if not line:
+ continue
+ s_info = info_backend()
+ s_info._parse_x2golistsessions_line(line)
+ self.sessions[s_info.name] = s_info
+
+
diff --git a/test.py b/x2go/backends/profiles/__init__.py
similarity index 63%
copy from test.py
copy to x2go/backends/profiles/__init__.py
index 34b2284..8eab147 100644
--- a/test.py
+++ b/x2go/backends/profiles/__init__.py
@@ -1,11 +1,10 @@
-#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (C) 2010 by Mike Gabriel <m.gabriel at 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 2 of the License, or
+# 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,
@@ -16,13 +15,13 @@
# 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.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+
+from x2go.defaults import DEFAULT_SESSIONPROFILES_BACKEND
+
+from sessions_file import X2goSessionProfilesFILE
+from win_registry import X2goSessionProfilesWINREG
+from https_broker import X2goSessionProfilesHTTP
-"""
-Unit tests for Python X2go.
-"""
-import os
+X2goSessionProfiles = eval(DEFAULT_SESSIONPROFILES_BACKEND)
-if __name__ == "__main__":
- os.chdir('tests')
- os.system('./runalltests.py')
\ No newline at end of file
diff --git a/x2go/profiles.py b/x2go/backends/profiles/https_broker.py
similarity index 94%
copy from x2go/profiles.py
copy to x2go/backends/profiles/https_broker.py
index b6e4e6b..8355e81 100644
--- a/x2go/profiles.py
+++ b/x2go/backends/profiles/https_broker.py
@@ -29,15 +29,15 @@ __NAME__ = 'x2gosessionprofiles-pylib'
import copy
# Python X2go modules
-from defaults import X2GO_SESSIONPROFILES_CONFIGFILES
-from defaults import X2GO_SESSIONPROFILE_DEFAULTS
-import inifiles
-import log
-import utils
-from x2go_exceptions import X2goProfileException
+from x2go.defaults import X2GO_SESSIONPROFILES_CONFIGFILES
+from x2go.defaults import X2GO_SESSIONPROFILE_DEFAULTS
+import x2go.inifiles as inifiles
+import x2go.log as log
+import x2go.utils as utils
+from x2go.x2go_exceptions import X2goProfileException
-class X2goSessionProfiles(inifiles.X2goIniFile):
+class X2goSessionProfilesHTTP(inifiles.X2goIniFile):
defaultValues = {}
defaultSessionProfile = X2GO_SESSIONPROFILE_DEFAULTS
@@ -151,11 +151,13 @@ class X2goSessionProfiles(inifiles.X2goIniFile):
"""
return self.get_profile_config(profile_id)['name']
- def add_profile(self, profile_id, **kwargs):
+ def add_profile(self, profile_id=None, **kwargs):
"""\
STILL UNDOCUMENTED
"""
+ if profile_id is None:
+ profile_id = utils._get_SessionProfileId()
for key, value in kwargs.items():
if key in self.defaultSessionProfile:
self.update_value(profile_id, key, value)
diff --git a/x2go/profiles.py b/x2go/backends/profiles/sessions_file.py
similarity index 94%
copy from x2go/profiles.py
copy to x2go/backends/profiles/sessions_file.py
index b6e4e6b..d6fe03d 100644
--- a/x2go/profiles.py
+++ b/x2go/backends/profiles/sessions_file.py
@@ -29,15 +29,15 @@ __NAME__ = 'x2gosessionprofiles-pylib'
import copy
# Python X2go modules
-from defaults import X2GO_SESSIONPROFILES_CONFIGFILES
-from defaults import X2GO_SESSIONPROFILE_DEFAULTS
-import inifiles
-import log
-import utils
-from x2go_exceptions import X2goProfileException
+from x2go.defaults import X2GO_SESSIONPROFILES_CONFIGFILES
+from x2go.defaults import X2GO_SESSIONPROFILE_DEFAULTS
+import x2go.inifiles as inifiles
+import x2go.log as log
+import x2go.utils as utils
+from x2go.x2go_exceptions import X2goProfileException
-class X2goSessionProfiles(inifiles.X2goIniFile):
+class X2goSessionProfilesFILE(inifiles.X2goIniFile):
defaultValues = {}
defaultSessionProfile = X2GO_SESSIONPROFILE_DEFAULTS
@@ -151,11 +151,13 @@ class X2goSessionProfiles(inifiles.X2goIniFile):
"""
return self.get_profile_config(profile_id)['name']
- def add_profile(self, profile_id, **kwargs):
+ def add_profile(self, profile_id=None, **kwargs):
"""\
STILL UNDOCUMENTED
"""
+ if profile_id is None:
+ profile_id = utils._get_SessionProfileId()
for key, value in kwargs.items():
if key in self.defaultSessionProfile:
self.update_value(profile_id, key, value)
diff --git a/x2go/profiles.py b/x2go/backends/profiles/win_registry.py
similarity index 94%
rename from x2go/profiles.py
rename to x2go/backends/profiles/win_registry.py
index b6e4e6b..380c37a 100644
--- a/x2go/profiles.py
+++ b/x2go/backends/profiles/win_registry.py
@@ -29,15 +29,16 @@ __NAME__ = 'x2gosessionprofiles-pylib'
import copy
# Python X2go modules
-from defaults import X2GO_SESSIONPROFILES_CONFIGFILES
-from defaults import X2GO_SESSIONPROFILE_DEFAULTS
-import inifiles
-import log
-import utils
-from x2go_exceptions import X2goProfileException
+from x2go.defaults import X2GO_SESSIONPROFILES_CONFIGFILES
+from x2go.defaults import X2GO_SESSIONPROFILE_DEFAULTS
+import x2go.inifiles as inifiles
+import x2go.log as log
+import x2go.utils as hostname
+from x2go.x2go_exceptions import X2goProfileException
-class X2goSessionProfiles(inifiles.X2goIniFile):
+
+class X2goSessionProfilesWINREG(inifiles.X2goIniFile):
defaultValues = {}
defaultSessionProfile = X2GO_SESSIONPROFILE_DEFAULTS
@@ -151,11 +152,13 @@ class X2goSessionProfiles(inifiles.X2goIniFile):
"""
return self.get_profile_config(profile_id)['name']
- def add_profile(self, profile_id, **kwargs):
+ def add_profile(self, profile_id=None, **kwargs):
"""\
STILL UNDOCUMENTED
"""
+ if profile_id is None:
+ profile_id = utils._get_SessionProfileId()
for key, value in kwargs.items():
if key in self.defaultSessionProfile:
self.update_value(profile_id, key, value)
diff --git a/tests/__init__.py b/x2go/backends/proxy/__init__.py
similarity index 73%
copy from tests/__init__.py
copy to x2go/backends/proxy/__init__.py
index 6e5cefb..5ce688d 100644
--- a/tests/__init__.py
+++ b/x2go/backends/proxy/__init__.py
@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2010 by Mike Gabriel <m.gabriel at 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 2 of the License, or
+# 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,
@@ -15,7 +15,10 @@
# 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.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+
+from x2go.defaults import DEFAULT_PROXY_BACKEND
+
+from nx3 import X2goProxyNX3
-import runalltests
-import test_printing
\ No newline at end of file
+X2goProxy = eval(DEFAULT_PROXY_BACKEND)
diff --git a/x2go/proxy.py b/x2go/backends/proxy/base.py
similarity index 62%
rename from x2go/proxy.py
rename to x2go/backends/proxy/base.py
index 5dd8c35..8353d3c 100644
--- a/x2go/proxy.py
+++ b/x2go/backends/proxy/base.py
@@ -18,7 +18,7 @@
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
"""\
-X2goProxy classes - proxying your connection through NX3 and others.
+X2goProxyBASE class - proxying your connection through NX3 and others.
"""
__NAME__ = 'x2goproxy-pylib'
@@ -33,20 +33,20 @@ import copy
import threading
# Python X2go modules
-import forward
-import log
+import x2go.forward as forward
+import x2go.log as log
-from defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS
+from x2go.defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS
if _X2GOCLIENT_OS in ("Windows"):
import subprocess
else:
- import gevent_subprocess as subprocess
+ import x2go.gevent_subprocess as subprocess
-from defaults import LOCAL_HOME as _LOCAL_HOME
-from defaults import X2GO_SESSION_ROOTDIR as _X2GO_SESSION_ROOTDIR
+from x2go.defaults import LOCAL_HOME as _LOCAL_HOME
+from x2go.defaults import X2GO_SESSION_ROOTDIR as _X2GO_SESSION_ROOTDIR
-class X2goProxy(threading.Thread):
+class X2goProxyBASE(threading.Thread):
"""\
X2goProxy is an abstract class for X2go proxy connections.
@@ -106,8 +106,6 @@ class X2goProxy(threading.Thread):
"""
self.stop_thread()
- #if p.poll() is None:
- # p.kill()
def _tidy_up(self):
"""\
@@ -134,8 +132,6 @@ class X2goProxy(threading.Thread):
self._keepalive = False
gevent.sleep(1)
self._tidy_up()
- #if p.poll() is None:
- # p.kill()
def run(self):
"""\
@@ -203,90 +199,3 @@ class X2goProxy(threading.Thread):
return self.proxy
-class X2goNX3Proxy(X2goProxy):
- """\
- X2goNX3Proxy is a NX version 3 based X2go proxy connection class.
-
- It basically fills X2goProxy variables with sensible content. Its
- methods mostly wrap around the corresponding methods of the parent class.
-
- """
- def __init__(self, *args, **kwargs):
- """\
- For available parameters refer to L{X2goProxy} class documentation.
-
- """
- X2goProxy.__init__(self, *args, **kwargs)
-
- # setting some default environment variables, nxproxy paths etc.
- if _X2GOCLIENT_OS == "Windows":
- self.PROXY_CMD = os.path.join(os.environ["ProgramFiles"], os.path.normpath("x2goclient/nxproxy.exe"))
- else:
- self.PROXY_CMD = "/usr/bin/nxproxy"
- self.PROXY_ENV.update({
- "NX_CLIENT": "/bin/true",
- "NX_ROOT": os.path.join(_LOCAL_HOME, _X2GO_SESSION_ROOTDIR)
- })
- self.PROXY_MODE = '-S'
- if _X2GOCLIENT_OS == "Windows":
- self.PROXY_OPTIONS = [
- "nx/nx" ,
- "retry=5",
- "composite=1",
- "connect=localhost",
- "cookie=%s" % self.session_info.cookie,
- "port=%d" % self.session_info.graphics_port,
- "errors=%s" % os.path.join(".", "..", "S-%s" % self.session_info.name, self.session_log, ),
- ]
- else:
- self.PROXY_OPTIONS = [
- "nx/nx" ,
- "retry=5",
- "composite=1",
- "connect=localhost",
- "cookie=%s" % self.session_info.cookie,
- "port=%d" % self.session_info.graphics_port,
- "errors=%s" % os.path.join(self.session_info.local_container, self.session_log, ),
- ]
-
- self.PROXY_DISPLAY = self.session_info.display
-
- def _update_local_proxy_socket(self, port):
- for idx, a in enumerate(self.PROXY_OPTIONS):
- if a.startswith('port='):
- self.PROXY_OPTIONS[idx] = 'port=%s' % port
-
- def _generate_cmdline(self):
-
- if (_X2GOCLIENT_OS == "Windows") and (len(",".join(self.PROXY_OPTIONS)) >= 250):
- _options_filename = os.path.join(self.session_info.local_container, 'options')
- options = open(_options_filename, 'w')
- options.write('%s:%s' % (','.join(self.PROXY_OPTIONS), self.PROXY_DISPLAY))
- options.close()
- self.PROXY_OPTIONS= [ 'nx/nx', 'options=%s' % os.path.join(".", "..", "S-%s" % self.session_info.name, 'options'), ]
-
- cmd_line = [ self.PROXY_CMD, ]
- cmd_line.append(self.PROXY_MODE)
- _proxy_options = "%s:%s" % (",".join(self.PROXY_OPTIONS), self.PROXY_DISPLAY)
- cmd_line.append(_proxy_options)
- return cmd_line
-
-
- def start_proxy(self):
- self.logger('starting local NX3 proxy...', loglevel=log.loglevel_INFO)
- self.logger('NX3 Proxy mode is server, cookie=%s, host=localhost, port=%s.' % (self.session_info.cookie, self.session_info.graphics_port,), loglevel=log.loglevel_DEBUG)
- self.logger('NX3 proxy writes session log to %s.' % os.path.join(self.session_info.local_container, 'session.log'), loglevel=log.loglevel_DEBUG)
-
- p = X2goProxy.start_proxy(self)
-
- if p is not None:
- self.logger('NX3 proxy is up and running.', loglevel=log.loglevel_INFO)
- else:
- self.logger('Bringing up NX3 proxy failed.', loglevel=log.loglevel_ERROR)
-
- return p
-
-
-# this is our default proxy: NX3 Proxy
-DEFAULT_PROXY_CLASS = X2goNX3Proxy
-"""Currently L{X2goNX3Proxy} is Python X2go's default proxy class."""
\ No newline at end of file
diff --git a/x2go/backends/proxy/nx3.py b/x2go/backends/proxy/nx3.py
new file mode 100644
index 0000000..a6b73a3
--- /dev/null
+++ b/x2go/backends/proxy/nx3.py
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2010 by Mike Gabriel <m.gabriel at 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.
+
+"""\
+X2goProxy classes - proxying your connection through NX3 and others.
+
+"""
+__NAME__ = 'x2goproxynx3-pylib'
+
+# modules
+import gevent
+import os
+import sys
+import types
+import time
+import copy
+import threading
+
+# Python X2go modules
+import x2go.forward as forward
+import x2go.log as log
+import base
+
+from x2go.defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS
+from x2go.defaults import LOCAL_HOME as _LOCAL_HOME
+from x2go.defaults import X2GO_SESSION_ROOTDIR as _X2GO_SESSION_ROOTDIR
+
+class X2goProxyNX3(base.X2goProxyBASE):
+ """\
+ X2goNX3Proxy is a NX version 3 based X2go proxy connection class.
+
+ It basically fills L{X2goProxyBASE} variables with sensible content. Its
+ methods mostly wrap around the corresponding methods of the parent class.
+
+ """
+ def __init__(self, *args, **kwargs):
+ """\
+ For available parameters refer to L{X2goProxyBASE} class documentation.
+
+ """
+ base.X2goProxyBASE.__init__(self, *args, **kwargs)
+
+ # setting some default environment variables, nxproxy paths etc.
+ if _X2GOCLIENT_OS == "Windows":
+ self.PROXY_CMD = os.path.join(os.environ["ProgramFiles"], os.path.normpath("x2goclient/nxproxy.exe"))
+ else:
+ self.PROXY_CMD = "/usr/bin/nxproxy"
+ self.PROXY_ENV.update({
+ "NX_CLIENT": "/bin/true",
+ "NX_ROOT": os.path.join(_LOCAL_HOME, _X2GO_SESSION_ROOTDIR)
+ })
+ self.PROXY_MODE = '-S'
+ if _X2GOCLIENT_OS == "Windows":
+ self.PROXY_OPTIONS = [
+ "nx/nx" ,
+ "retry=5",
+ "composite=1",
+ "connect=localhost",
+ "cookie=%s" % self.session_info.cookie,
+ "port=%d" % self.session_info.graphics_port,
+ "errors=%s" % os.path.join(".", "..", "S-%s" % self.session_info.name, self.session_log, ),
+ ]
+ else:
+ self.PROXY_OPTIONS = [
+ "nx/nx" ,
+ "retry=5",
+ "composite=1",
+ "connect=localhost",
+ "cookie=%s" % self.session_info.cookie,
+ "port=%d" % self.session_info.graphics_port,
+ "errors=%s" % os.path.join(self.session_info.local_container, self.session_log, ),
+ ]
+
+ self.PROXY_DISPLAY = self.session_info.display
+
+ def _update_local_proxy_socket(self, port):
+ for idx, a in enumerate(self.PROXY_OPTIONS):
+ if a.startswith('port='):
+ self.PROXY_OPTIONS[idx] = 'port=%s' % port
+
+ def _generate_cmdline(self):
+
+ if (_X2GOCLIENT_OS == "Windows") and (len(",".join(self.PROXY_OPTIONS)) >= 250):
+ _options_filename = os.path.join(self.session_info.local_container, 'options')
+ options = open(_options_filename, 'w')
+ options.write('%s:%s' % (','.join(self.PROXY_OPTIONS), self.PROXY_DISPLAY))
+ options.close()
+ self.PROXY_OPTIONS= [ 'nx/nx', 'options=%s' % os.path.join(".", "..", "S-%s" % self.session_info.name, 'options'), ]
+
+ cmd_line = [ self.PROXY_CMD, ]
+ cmd_line.append(self.PROXY_MODE)
+ _proxy_options = "%s:%s" % (",".join(self.PROXY_OPTIONS), self.PROXY_DISPLAY)
+ cmd_line.append(_proxy_options)
+ return cmd_line
+
+ def start_proxy(self):
+ self.logger('starting local NX3 proxy...', loglevel=log.loglevel_INFO)
+ self.logger('NX3 Proxy mode is server, cookie=%s, host=localhost, port=%s.' % (self.session_info.cookie, self.session_info.graphics_port,), loglevel=log.loglevel_DEBUG)
+ self.logger('NX3 proxy writes session log to %s.' % os.path.join(self.session_info.local_container, 'session.log'), loglevel=log.loglevel_DEBUG)
+
+ p = base.X2goProxyBASE.start_proxy(self)
+
+ if p is not None:
+ self.logger('NX3 proxy is up and running.', loglevel=log.loglevel_INFO)
+ else:
+ self.logger('Bringing up NX3 proxy failed.', loglevel=log.loglevel_ERROR)
+
+ return p
+
+
+
+#
diff --git a/test.py b/x2go/backends/terminal/__init__.py
similarity index 69%
copy from test.py
copy to x2go/backends/terminal/__init__.py
index 34b2284..91c158c 100644
--- a/test.py
+++ b/x2go/backends/terminal/__init__.py
@@ -1,11 +1,10 @@
-#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (C) 2010 by Mike Gabriel <m.gabriel at 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 2 of the License, or
+# 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,
@@ -16,13 +15,10 @@
# 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.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+
+from x2go.defaults import DEFAULT_TERMINALSESSION_BACKEND
-"""
-Unit tests for Python X2go.
-"""
-import os
+from stdout import X2goTerminalSessionSTDOUT
-if __name__ == "__main__":
- os.chdir('tests')
- os.system('./runalltests.py')
\ No newline at end of file
+X2goTerminalSession = eval(DEFAULT_TERMINALSESSION_BACKEND)
diff --git a/x2go/backends/terminal/stdout.py b/x2go/backends/terminal/stdout.py
new file mode 100644
index 0000000..7e6d61a
--- /dev/null
+++ b/x2go/backends/terminal/stdout.py
@@ -0,0 +1,720 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2010 by Mike Gabriel <m.gabriel at 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.
+
+"""\
+X2goTerminalSession class - core functions for handling your individual X2go sessions.
+
+This backend handles X2go server implementations that respond with session infos
+via server-side STDOUT and use NX3 as graphical proxy.
+
+"""
+__NAME__ = 'x2goterminalsession-pylib'
+
+# modules
+import os, sys, types
+import gevent
+import threading
+import signal
+import cStringIO
+import copy
+
+# Python X2go modules
+import x2go.rforward as rforward
+import x2go.sftpserver as sftpserver
+import x2go.printing as printing
+import x2go.log as log
+import x2go.defaults as defaults
+import x2go.utils as utils
+import x2go.x2go_exceptions as x2go_exceptions
+import x2go.guardian as guardian
+
+from x2go.cleanup import x2go_cleanup
+
+# we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables)
+from x2go.defaults import LOCAL_HOME as _LOCAL_HOME
+from x2go.defaults import CURRENT_LOCAL_USER as _CURRENT_LOCAL_USER
+from x2go.defaults import X2GO_SESSION_ROOTDIR as _X2GO_SESSION_ROOTDIR
+
+from x2go.backends.info import X2goServerSessionInfo
+from x2go.backends.info import X2goServerSessionList
+from x2go.backends.proxy import X2goProxy
+
+def _rewrite_cmd(cmd):
+
+ # start with an empty string
+ cmd = cmd or ''
+
+ # find window manager commands
+ if cmd in defaults.window_managers.keys():
+ cmd = defaults.window_managers[cmd]
+
+ # X2go run command replace X2GO_SPACE_CHAR string with blanks
+ cmd.replace(" ", "X2GO_SPACE_CHAR")
+
+ # place quot marks around cmd if not empty string
+ if cmd:
+ cmd = '"%s"' % cmd
+ return cmd
+
+
+class X2goSessionParams(object):
+ """\
+ The L{X2goSessionParams} class is used to store all parameters that
+ L{X2goSession} objects are constructed with.
+
+ """
+ def rewrite_session_type(self):
+ """\
+ Rewrite the X2go session type, so that the X2go server
+ can understand it (C{desktop} -> C{D}).
+
+ Also if the object's C{command} property is a known window
+ manager, the session type will be set to 'D'
+ (i.e. desktop).
+
+ @return: 'D' if session should probably a desktop session,
+ 'R' (for rootless) else
+ @rtype: str
+
+ """
+ session_type = self.session_type
+ cmd = self.cmd
+ if session_type == "desktop":
+ self.session_type = 'D'
+ return
+ if cmd:
+ if cmd in defaults.window_managers.keys():
+ self.session_type = 'D'
+ return
+ if os.path.basename(cmd) in defaults.window_managers.values():
+ self.session_type = 'D'
+ return
+ self.session_type = 'R'
+
+ def update(self, properties_to_be_updated={}):
+ """\
+ Update all properties in the object L{X2goSessionParams} object from
+ the passed on dictionary.
+
+ @param properties_to_be_updated: a dictionary with L{X2goSessionParams}
+ property names as keys und their values to be update in
+ L{X2goSessionParams} object.
+ @type properties_to_be_updated: dict
+
+ """
+ for key in properties_to_be_updated.keys():
+ setattr(self, key, properties_to_be_updated[key] or '')
+ self.rewrite_session_type()
+
+
+class X2goTerminalSessionSTDOUT(object):
+ """\
+ Class for managing X2go sessions on a remote X2go server via Paramiko/SSH.
+ With the X2goSession class you can start new X2go sessions, resume suspended
+ sessions or suspend resp. terminate currently running sessions on a
+ connected X2go server.
+
+ When suspending or terminating sessions there are two possible ways:
+
+ 1. Initialize an X2go session object, start a new session (or resume)
+ and use the L{X2goSession.suspend()} or L{X2goSession.terminate()} method
+ to suspend/terminate the current session object.
+ 2. Alternatively, you can pass a session name to L{X2goSession.suspend()}
+ or L{X2goSession.terminate()}. If a session of this name exists on the
+ X2go server the respective action will be performed on the session.
+
+ An L{X2goSession} object uses two main data structure classes:
+
+ - L{X2goSessionParams}: stores all parameters that have been passed to the
+ constructor method.
+
+ - L{X2goServerSessionInfo}: when starting or resuming a session, an object of this class
+ will be used to store all information retrieved from the X2go server.
+
+ @param geometry: screen geometry of the X2go session. Can be either C{<width>x<height>}
+ or C{fullscreen}
+ @type geometry: str
+ @param depth: color depth in bits (common values: C{16}, C{24})
+ @type depth: int
+ @param link: network link quality (either one of C{modem}, C{isdn}, C{adsl}, C{wan} or C{lan})
+ @type link: str
+ @param pack: compression method for NX based session proxying
+ @type pack: str
+ @param cache_type: a dummy parameter that is passed to the L{X2goProxy}. In NX Proxy
+ (class C{X2goNX3Proxy}) this originally is the session name. With X2go it
+ defines the name of the NX cache directory. Best is to leave it untouched.
+ @type cache_type: str
+ @param kblayout: keyboard layout, e.g. C{us} (default), C{de}, C{fr}, ...
+ @type kblayout: str
+ @param kbtype: keyboard type, e.g. C{pc105/us} (default), C{pc105/de}, ...
+ @type kbtype: str
+ @param session_type: either C{desktop} or C{application} (rootless session)
+ @type session_type: str
+ @param snd_system: sound system to be used on server (C{none}, C{pulse} (default),
+ C{arts} (obsolete) or C{esd})
+ @type snd_system: str
+ @param cmd: command to be run on X2go server after session start (only used
+ when L{X2goSession.start()} is called, ignored on resume, suspend etc.
+ @type cmd: str
+ @param rootdir: X2go session directory, normally C{~/.x2go}
+ @type rootdir: str
+ @param proxy_class: other than the default L{X2goProxy} class
+ @type proxy_class: L{X2goProxy} related instance
+ @param print_action: either a print action short name (PDFVIEW, PDFSAVE, PRINT, PRINTCMD) or the
+ resp. C{X2goPrintActionXXX} class (where XXX equals one of the given short names)
+ @type print_action: str or class
+ @param print_action_args: optional arguments for a given print_action (for further info refer to
+ L{X2goPrintActionPDFVIEW}, L{X2goPrintActionPDFSAVE}, L{X2goPrintActionPRINT} and L{X2goPrintActionPRINTCMD})
+ @type print_action_args: dict
+ @param logger: you can pass an L{X2goLogger} object to the
+ L{X2goProxy} constructor
+ @type logger: L{X2goLogger} instance
+ @param loglevel: if no L{X2goLogger} object has been supplied a new one will be
+ constructed with the given loglevel
+ @type loglevel: int
+
+ """
+ params = None
+ session_info = None
+ control_session = None
+
+ proxy_class = None
+ proxy = None
+ proxy_subprocess = None
+
+ guardian_thread = None
+ reverse_tunnels = {}
+
+ print_queue = None
+
+ def __init__(self, control_session, session_info=None,
+ geometry="800x600", depth=24, link="adsl", pack="16m-jpeg-9",
+ cache_type="unix-kde", kblayout='us', kbtype='pc105/us',
+ session_type="application", snd_system='pulse', cmd=None,
+ rootdir=None,
+ profile_name='UNKNOWN', profile_id=utils._genSessionProfileId(),
+ print_action=None, print_action_args={},
+ info_backend=X2goServerSessionInfo,
+ list_backend=X2goServerSessionList,
+ proxy_backend=X2goProxy,
+ logger = None, loglevel=log.loglevel_DEFAULT):
+ """\
+ Initialize an X2go session. With the X2goSession class you can start
+ new X2go sessions, resume suspended sessions or suspend resp. terminate
+ currently running sessions on a connected X2go server.
+
+ """
+ if logger is None:
+ self.logger = log.X2goLogger(loglevel=loglevel)
+ else:
+ self.logger = copy.deepcopy(logger)
+ self.logger.tag = __NAME__
+
+ self.control_session = control_session
+ self.reverse_tunnels = self.control_session.get_transport().reverse_tunnels
+
+ self.params = X2goSessionParams()
+
+ if session_info is not None:
+ if self.session_info.name:
+ self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name)
+ else:
+ raise X2goSessionException('no valid session info availble')
+ else:
+ self.session_info = info_backend()
+
+ self.params.geometry = geometry
+ self.params.depth = str(depth)
+ self.params.link = link
+ self.params.pack = pack
+ self.params.cache_type = cache_type
+ self.params.session_type = session_type
+ self.params.kblayout = kblayout
+ self.params.kbtype = kbtype
+ self.params.snd_system = snd_system
+ self.params.cmd = cmd
+ self.params.rootdir = (type(rootdir) is types.StringType) and rootdir or os.path.join(_LOCAL_HOME,_X2GO_SESSION_ROOTDIR)
+ self.params.update()
+
+ self.proxy_class = proxy_backend
+
+ self.print_action = print_action
+ self.print_action_args = print_action_args
+
+ self._mk_session_rootdir(self.params.rootdir)
+
+ # each terminal session has its own guardian
+ self.guardian_thread = guardian.X2goSessionGuardian(self, logger=self.logger)
+ self.guardian_thread.start()
+
+
+ def __del__(self):
+ self._x2go_tidy_up()
+
+ def _x2go_tidy_up(self):
+
+ if self.proxy is not None:
+ self.proxy.__del__()
+
+ try:
+
+ if self.control_session.get_transport() is not None:
+ for _tunnel in [ _tun[1] for _tun in self.reverse_tunnels[self.session_info.name].values() ]:
+ if _tunnel is not None:
+ _tunnel.__del__()
+
+ if self.print_queue is not None:
+ self.print_queue.__del__()
+
+ except AttributeError:
+ pass
+
+ def _mk_session_rootdir(self, d):
+
+ try:
+ os.mkdir(d)
+ except OSError, e:
+ if e.errno == 17:
+ # file exists
+ pass
+ else:
+ raise OSError, e
+
+ def get_session_name(self):
+ """\
+ STILL UNDOCUMENTED
+
+ """
+ return self.session_info.name
+
+ def start_sound(self):
+ """\
+ Initialize Paramiko/SSH reverse forwarding tunnel for X2go sound.
+
+ Currently supported audio protocols:
+
+ - Pulse Audio
+ - Esound
+
+ """
+ _tunnel = None
+ if self.reverse_tunnels[self.session_info.name]['snd'][1] is None:
+ if self.params.snd_system == 'pulse':
+ self.logger('initializing Pulse Audio sound support in X2go session', loglevel=log.loglevel_INFO)
+ ###
+ ### PULSE AUDIO
+ ###
+ # setup pulse client config file on X2go server
+ cmd_line = "echo 'default-server=localhost:%s'>%s/.pulse-client.conf;" % (self.session_info.snd_port, self.session_info.remote_container) + \
+ "echo 'cookie-file=%s/.pulse-cookie'>>%s/.pulse-client.conf" % (self.session_info.remote_container, self.session_info.remote_container)
+ (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
+
+ self.control_session._x2go_sftp_put(local_path='%s/.pulse-cookie' % _LOCAL_HOME, remote_path='%s/.pulse-cookie' % self.session_info.remote_container)
+
+ # start reverse SSH tunnel for pulse stream
+ _tunnel = rforward.X2goRevFwTunnel(server_port=self.session_info.snd_port,
+ remote_host='localhost',
+ remote_port=4713,
+ ssh_transport=self.control_session.get_transport(),
+ logger=self.logger
+ )
+
+ elif self.params.snd_system == 'arts':
+ ###
+ ### ARTSD AUDIO
+ ###
+ self.logger('the ArtsD sound server (as in KDE3) is obsolete and will not be supported by Python X2go...', loglevel=log.loglevel_WARNING)
+
+ elif self.params.snd_system == 'esd':
+ ###
+ ### ESD AUDIO
+ ###
+
+ self.logger('initializing ESD sound support in X2go session', loglevel=log.loglevel_INFO)
+ self.control_session._x2go_sftp_put(local_path='%s/.esd_auth' % _LOCAL_HOME, remote_path='%s/.esd_auth' % self.control_session._x2go_remote_home)
+
+ # start reverse SSH tunnel for pulse stream
+ _tunnel = rforward.X2goRevFwTunnel(server_port=self.session_info.snd_port,
+ remote_host='localhost',
+ remote_port=16001,
+ ssh_transport=self.control_session.get_transport(),
+ logger=self.logger
+ )
+
+
+ if _tunnel is not None:
+ self.reverse_tunnels[self.session_info.name]['snd'] = (self.session_info.snd_port, _tunnel)
+ _tunnel.start()
+ self.guardian_thread.active_threads.append(_tunnel)
+
+ else:
+ # tunnel has already been started and might simply need a resume call
+ self.reverse_tunnels[self.session_info.name]['snd'][1].resume()
+
+ def start_sshfs(self):
+ """\
+ Initialize Paramiko/SSH reverse forwarding tunnel for X2go folder sharing.
+
+ """
+ # start reverse SSH tunnel for sshfs (folder sharing, printing)
+ ssh_transport = self.control_session.get_transport()
+ if self.reverse_tunnels[self.session_info.name]['sshfs'][1] is None:
+
+ _tunnel = sftpserver.X2goRevFwTunnelToSFTP(server_port=self.session_info.sshfs_port,
+ ssh_transport=ssh_transport,
+ auth_key=self.control_session._x2go_session_auth_rsakey,
+ logger=self.logger
+ )
+
+ if _tunnel is not None:
+ self.reverse_tunnels[self.session_info.name]['sshfs'] = (self.session_info.sshfs_port, _tunnel)
+ _tunnel.start()
+ self.guardian_thread.active_threads.append(_tunnel)
+
+ else:
+ # tunnel has already been started and might simply need a resume call
+ self.reverse_tunnels[self.session_info.name]['sshfs'][1].resume()
+
+ def _x2go_pause_rev_fw_tunnel(self, name):
+ # pause reverse SSH tunnel of name <name>
+ ssh_transport = self.get_transport()
+ _tunnel = self.reverse_tunnels[self.session_info.name][name][1]
+ if _tunnel is not None:
+ _tunnel.pause()
+
+ def stop_sound(self):
+ """\
+ Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2go sound.
+
+ """
+ self._x2go_pause_rev_fw_tunnel('snd')
+
+ def stop_sshfs(self):
+ """\
+ Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2go folder sharing.
+
+ """
+ self._x2go_pause_rev_fw_tunnel('sshfs')
+
+ def start_printing(self):
+ """\
+ Initialize X2go print spooling.
+
+ """
+ if self.session_info.username not in self.control_session._x2go_remote_group('x2goprint'):
+ raise x2go_exceptions.X2goSessionException('remote user %s is not member of X2go server group x2goprint' % self.session_info.username)
+
+ spool_dir = os.path.join(self.session_info.local_container, 'spool')
+ if not os.path.exists(spool_dir):
+ os.mkdir(spool_dir)
+ self.share_local_folder(folder_name=spool_dir, folder_type='spool')
+ self.print_queue = printing.X2goPrintQueue(spool_dir=spool_dir,
+ print_action=self.print_action,
+ print_action_args=self.print_action_args,
+ logger=self.logger,
+ )
+ self.print_queue.start()
+ self.guardian_thread.active_threads.append(self.print_queue)
+
+ def set_print_action(self, print_action, **kwargs):
+ """\
+ STILL UNDOCUMENTED
+
+ """
+ self.print_queue.set_print_action(print_action, logger=self.logger, **kwargs)
+
+
+ def stop_printing(self):
+ """\
+ Shutdown (pause) the X2go Print Queue thread.
+
+ """
+ if self.print_queue is not None:
+ self.print_queue.pause()
+
+ def share_local_folder(self, folder_name=None, folder_type='disk'):
+ """\
+ Share a local folder with the X2go session.
+
+ @param folder_name: the full path to an existing folder on the local
+ file system
+ @type folder_name: str
+ @param folder_type: one of 'disk' (a folder on your local hard drive), 'rm' (removeable device),
+ 'cdrom' (CD/DVD Rom) or 'spool' (for X2go print spooling)
+ @type folder_type: str
+
+ @return: returns C{True} if the local folder has been successfully mounted within the X2go server session
+ @rtype: bool
+
+ """
+ if self.session_info.username not in self.control_session._x2go_remote_group('fuse'):
+ raise x2go_exceptions.X2goSessionException('remote user %s is not member of X2go server group fuse' % self.session_info.username)
+
+ if folder_name is None:
+ self.logger('no folder name given...', log.loglevel_WARN)
+ return False
+
+ if type(folder_name) is not types.StringType:
+ self.logger('folder name needs to be of type StringType...', log.loglevel_WARN)
+ return False
+
+ if not os.path.exists(folder_name):
+ self.logger('local folder does not exist: %s' % folder_name, log.loglevel_WARN)
+ return False
+
+ self.logger('sharing local folder: %s' % folder_name, log.loglevel_INFO)
+
+ _auth_rsakey = self.control_session._x2go_session_auth_rsakey
+ _host_rsakey = defaults.RSAHostKey
+
+ _tmp_io_object = cStringIO.StringIO()
+ _auth_rsakey.write_private_key(_tmp_io_object)
+ _tmp_io_object.write('----BEGIN RSA IDENTITY----')
+ _tmp_io_object.write('%s %s' % (_host_rsakey.get_name(),_host_rsakey.get_base64(),))
+
+ _x2go_key_fname = os.path.join(os.path.dirname(self.session_info.remote_container), 'ssh', 'key.z%s' % self.session_info.agent_pid)
+ _x2go_key_bundle = _tmp_io_object.getvalue()
+
+ self.control_session._x2go_sftp_write(_x2go_key_fname, _x2go_key_bundle)
+
+ if folder_type is 'disk':
+
+ cmd_line = [ 'export HOSTNAME &&',
+ '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),
+ ]
+
+ elif folder_type is 'spool':
+
+ cmd_line = [ 'export HOSTNAME &&',
+ 'x2gomountdirs',
+ 'dir',
+ str(self.session_info.name),
+ _CURRENT_LOCAL_USER,
+ _x2go_key_fname,
+ '%s__PRINT_SPOOL___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_INFO)
+
+ def run_command(self, cmd=None):
+ """\
+ Run a command in this session.
+
+ After L{X2goSession.start()} has been called
+ one or more commands can be executed with L{X2goSession.run_command()}
+ within the current X2go session.
+
+ @param cmd: Command to be run
+ @type cmd: str
+
+ @return: stdout.read() and stderr.read() as returned by the run command
+ on the X2go server
+ @rtype: tuple of str
+
+ """
+ if cmd in ("", None):
+ if self.params.cmd is None:
+ cmd = 'TERMINAL'
+ else:
+ cmd = self.params.cmd
+
+ self.params.update({'cmd': cmd})
+
+ cmd_line = [ "setsid x2goruncommand",
+ str(self.session_info.display),
+ str(self.session_info.agent_pid),
+ str(self.session_info.name),
+ str(self.session_info.snd_port),
+ _rewrite_cmd(self.params.cmd),
+ str(self.params.snd_system),
+ str(self.params.session_type),
+ ">& /dev/null & exit",
+ ]
+
+ if self.params.snd_system is 'pulse':
+ cmd_line = [ 'PULSE_CLIENTCONFIG=%s/.pulse-client.conf' % self.session_info.remote_container ] + cmd_line
+
+ (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
+
+ return stdout.read(), stderr.read()
+
+ def ok(self):
+ """\
+ Returns C{True} if this X2go session is up and running,
+ C{False} else
+
+ @return: X2go session OK?
+ @rtype: bool
+
+ """
+ return bool(self.session_info.name and (self.proxy_subprocess and self.proxy_subprocess.poll() is None))
+
+ def is_running(self):
+ """\
+ Returns C{True} if this X2go session is in running state,
+ C{False} else.
+
+ @return: X2go session running?
+ @rtype: bool
+
+ """
+ return self.session_info.is_running()
+
+ def is_suspended(self):
+ """\
+ Returns C{True} if this X2go session is in suspended state,
+ C{False} else.
+
+ @return: X2go session suspended?
+ @rtype: bool
+
+ """
+ return self.session_info.is_suspended()
+
+ def start(self):
+ """\
+ Start a new X2go session.
+
+ The L{X2goTerminalSession.start()} method accepts any parameter
+ that can be passed to the class constructor.
+
+ """
+ setkbd = "0"
+ if self.params.kblayout or self.params.kbtype:
+ setkbd = "1"
+
+ cmd_line = [ "x2gostartagent",
+ str(self.params.geometry),
+ str(self.params.link),
+ str(self.params.pack),
+ str(self.params.cache_type+'-depth_'+self.params.depth),
+ str(self.params.kblayout),
+ str(self.params.kbtype),
+ str(setkbd),
+ str(self.params.session_type),
+ self.params.cmd,
+ ]
+
+ (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
+
+ self.session_info.initialize(stdout.read(),
+ username=self.control_session.get_transport().get_username(),
+ hostname=self.control_session.get_transport().getpeername(),
+ )
+
+ # local path may be a Windows path, so we use the path separator of the local system
+ self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name)
+ # remote path is always a UniX path...
+ self.session_info.remote_container = '%s/%s/C-%s' % (self.control_session._x2go_remote_home,
+ _X2GO_SESSION_ROOTDIR,
+ self.session_info.name,
+ )
+
+ # set up SSH tunnel for X11 graphical elements
+ self.proxy = self.proxy_class(session_info=self.session_info, ssh_transport=self.control_session.get_transport(), logger=self.logger)
+ self.proxy_subprocess = self.proxy.start_proxy()
+ self.guardian_thread.active_threads.append(self.proxy)
+
+ self.associated = True
+ return self.ok()
+
+ def resume(self):
+ """\
+ Resume a running/suspended X2go session.
+
+ The L{X2goSession.resume()} method accepts any parameter
+ that can be passed to the class constructor.
+
+ @return: True if the session could be successfully resumed
+ @rtype: bool
+
+ """
+ setkbd = "0"
+ if self.params.kblayout or self.params.kbtype:
+ setkbd = "1"
+
+ cmd_line = [ "x2goresume-session", self.session_info.name,
+ self.params.geometry,
+ self.params.link,
+ self.params.pack,
+ self.params.kblayout,
+ self.params.kbtype,
+ setkbd,
+ ]
+
+ (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
+
+ self.proxy = self.proxy_class(self.session_info, self.control_session.get_transport(), logger=self.logger)
+ self.proxy_subprocess = self.proxy.start_proxy()
+
+ # local path may be a Windows path, so we use the path separator of the local system
+ self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name)
+ # remote path is always a UniX path...
+ self.session_info.remote_container = '%s/%s/C-%s' % (self.control_session._x2go_remote_home,
+ _X2GO_SESSION_ROOTDIR,
+ self.session_info.name,
+ )
+ return self.ok()
+
+ def suspend(self):
+ """\
+ Suspend this X2go session terminal.
+
+ @return: True if the session terminal could be successfully suspended
+ @rtype: bool
+
+ """
+ self.logger('suspending associated session: %s' % self.session_info, log.loglevel_DEBUG)
+ (stdin, stdout, stderr) = self.control_session._x2go_exec_command("x2gosuspend-session %s" % self.session_info, loglevel=log.loglevel_DEBUG)
+ dummy_stdout = stdout.read()
+ dummy_stderr = stderr.read()
+ self.associated = False
+ self._x2go_tidy_up()
+ # TODO: check if session has really suspended
+ _ret = True
+
+ return _ret
+
+ def terminate(self, session_name=None):
+ """\
+ Terminate this X2go session.
+
+ @return: True if the session terminal could be successfully terminate
+ @rtype: bool
+
+ """
+ self.logger('terminating associated session: %s' % self.session_info, log.loglevel_INFO)
+ (stdin, stdout, stderr) = self.control_session._x2go_exec_command("x2goterminate-session %s" % self.session_info, loglevel=log.loglevel_DEBUG)
+ dummy_stdout = stdout.read()
+ dummy_stderr = stderr.read()
+ self.session_info.clear()
+ self.associated = False
+ self._x2go_tidy_up()
+ # TODO: check if session has really suspended
+ _ret = True
+
+ return _ret
diff --git a/x2go/client.py b/x2go/client.py
index 9f2a18d..74c813d 100644
--- a/x2go/client.py
+++ b/x2go/client.py
@@ -124,9 +124,7 @@ import sys
# Python X2go modules
from settings import X2goClientSettings
from printing import X2goClientPrinting
-from profiles import X2goSessionProfiles
from registry import X2goSessionRegistry
-from session import X2goSession, _X2GO_SESSION_OPTIONS
import log
import utils
@@ -135,6 +133,8 @@ from defaults import LOCAL_HOME as _LOCAL_HOME
from defaults import CURRENT_LOCAL_USER as _CURRENT_LOCAL_USER
from defaults import X2GO_CLIENT_ROOTDIR as _X2GO_CLIENT_ROOTDIR
+from x2go.backends.profiles import X2goSessionProfiles
+
class X2goClient(object):
"""\
The X2goClient implements _THE_ public Python X2go API. With it you can
@@ -278,20 +278,22 @@ class X2goClient(object):
if k in kwargs.keys():
_params[k] = kwargs[k]
+ server = _params['server']
+ del _params['server']
+
else:
if server is None:
return None
_profile_id = utils._genSessionProfileId()
_profile_name = profile_name or sys.argv[0]
_params = kwargs
- _params['server'] = server
_params['printing'] = printing
_params['share_local_folders'] = share_local_folders
- session_uuid = self.session_registry.register(_profile_id, _profile_name, **_params )
+ session_uuid = self.session_registry.register(server=server, profile_id=_profile_id, profile_name=_profile_name, **_params )
- connect_options = self.session_registry(session_uuid).connect_options
- session_options = self.session_registry(session_uuid).session_options
+ control_params = self.session_registry(session_uuid).control_params
+ terminal_params = self.session_registry(session_uuid).terminal_params
self.logger('initializing X2go session...', log.loglevel_NOTICE, tag=self._logger_tag)
if return_object:
@@ -356,7 +358,7 @@ class X2goClient(object):
@rtype: obj
"""
- return self.session_registry(session_uuid).session_object
+ return self.session_registry(session_uuid)
__get_session = get_session
with_session = __get_session
"""Alias for L{get_session()}."""
@@ -548,7 +550,7 @@ class X2goClient(object):
for session in self.session_registry.running_sessions:
if session_name == session.get_session_name():
return session.suspend()
- return self.session_registry(session_uuid).session_object.suspend(session_name=session_name)
+ return self.session_registry(session_uuid).suspend(session_name=session_name)
__suspend_session = suspend_session
def terminate_session(self, session_uuid, session_name=None):
@@ -587,7 +589,7 @@ class X2goClient(object):
for session in self.session_registry.running_sessions + self.session_registry.suspended_sessions:
if session_name == session.get_session_name():
return session.terminate()
- return self.session_registry(session_uuid).session_object.terminate(session_name=session_name)
+ return self.session_registry(session_uuid).terminate(session_name=session_name)
__terminate_session = terminate_session
def get_session_profile_name(self, session_uuid):
@@ -887,10 +889,8 @@ class X2goClient(object):
@type session_uuid: C{str}
"""
- session = self.session_registry(session_uuid).session_object
- session_infos = session.list_sessions()
- for session_info in session_infos.values():
- session.terminate(session_name=session_info)
+ session = self.session_registry(session_uuid)
+ session.clean_sessions()
__clean_sessions = clean_sessions
def list_sessions(self, session_uuid):
@@ -908,7 +908,7 @@ class X2goClient(object):
@type session_uuid: C{str}
"""
- session = self.session_registry(session_uuid).session_object
+ session = self.session_registry(session_uuid)
return session.list_sessions()
__list_sessions = list_sessions
diff --git a/x2go/defaults.py b/x2go/defaults.py
index 5df419c..a75e059 100644
--- a/x2go/defaults.py
+++ b/x2go/defaults.py
@@ -59,6 +59,23 @@ else:
##
+## control and terminal session backend as well as session info backend defaults
+##
+
+DEFAULT_CONTROLSESSION_BACKEND = 'X2goControlSessionSTDOUT'
+DEFAULT_TERMINALSESSION_BACKEND = 'X2goTerminalSessionSTDOUT'
+DEFAULT_SERVERSESSIONINFO_BACKEND = 'X2goServerSessionInfoSTDOUT'
+DEFAULT_SERVERSESSIONLIST_BACKEND = 'X2goServerSessionListSTDOUT'
+DEFAULT_PROXY_BACKEND = 'X2goProxyNX3'
+
+##
+## profile backend defaults
+##
+
+DEFAULT_SESSIONPROFILES_BACKEND = 'X2goSessionProfilesFILE'
+
+
+##
## X2go Printing
##
@@ -214,13 +231,6 @@ _pack_methods_nx3 = [ m for m in pack_methods_nx3 if "%" not in m ]
for meth in [ m for m in pack_methods_nx3 if "%" in m ]:
_pack_methods_nx3 += [ meth.replace('%','%s' % str(i)) for i in range(0,10) ]
-
-##
-## THESE ARE NOT NEEDED!!!! THERE IS A METHOD IN utils.py that does the job...
-##
-X2GO_INIPARMS_TO_SESSION_PARMS = (('soundsystem','snd_system'), ('command','cmd'),('host','server'),('user', 'username'),
- ('key', 'key_filename'),('layout','kblayout'),('type','kbtype'), ('sshport', 'port'))
-
##
## X2go session defaults
##
@@ -239,16 +249,11 @@ window_managers={
RSAKEY_STRENGTH = 1024
RSAHostKey = paramiko.RSAKey.generate(RSAKEY_STRENGTH)
-from printing import X2goPrintActionPDFVIEW
-from printing import X2goPrintActionPDFSAVE
-from printing import X2goPrintActionPRINT
-from printing import X2goPrintActionPRINTCMD
-
X2GO_PRINT_ACTIONS = {
- 'PDFVIEW': X2goPrintActionPDFVIEW,
- 'PDFSAVE': X2goPrintActionPDFSAVE,
- 'PRINT': X2goPrintActionPRINT,
- 'PRINTCMD': X2goPrintActionPRINTCMD,
+ 'PDFVIEW': 'X2goPrintActionPDFVIEW',
+ 'PDFSAVE': 'X2goPrintActionPDFSAVE',
+ 'PRINT': 'X2goPrintActionPRINT',
+ 'PRINTCMD': 'X2goPrintActionPRINTCMD',
}
"""Relating print action names and classes."""
diff --git a/x2go/printing.py b/x2go/printing.py
index 3091975..41bc685 100644
--- a/x2go/printing.py
+++ b/x2go/printing.py
@@ -531,7 +531,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 = print_action(**kwargs)
+ self.print_action = eval ('%s(**kwargs)' % print_action)
def run(self):
"""\
diff --git a/x2go/registry.py b/x2go/registry.py
index 79c5da9..89727cf 100644
--- a/x2go/registry.py
+++ b/x2go/registry.py
@@ -29,351 +29,17 @@ import time
import threading
# Python X2go modules
-import profiles
import log
import utils
import session
from x2go_exceptions import *
-
-class X2goRegisteredSession():
-
- def __init__(self, logger=None, loglevel=log.loglevel_DEFAULT):
-
- if logger is None:
- self.logger = log.X2goLogger(loglevel=loglevel)
- else:
- self.logger = copy.deepcopy(logger)
- self.logger.tag = __NAME__
-
- self._keep_alive = True
-
- self.uuid = uuid.uuid1()
- self.connected = False
- self.running = False
- self.suspended = False
- self.terminated = False
- self.logger('starting threaded X2goRegisteredSession', loglevel=log.loglevel_DEBUG)
-
- def __str__(self):
- return self.__get_uuid()
- def __repr__(self):
- result = 'X2goRegisteredSession('
- for p in dir(self):
- if '__' in p or not p in self.__dict__ or type(p) is types.InstanceType: continue
- result += p + '=' + str(self.__dict__[p]) + ', '
- return result + ')'
- def __call__(self):
- return self.__get_uuid()
-
- def get_uuid(self):
- """\
- STILL UNDOCUMENTED
-
- """
- return str(self.uuid)
- __get_uuid = get_uuid
-
- def get_username(self):
- """\
- After a session has been setup up you can query the
- username the sessions runs as.
-
- @return: the remote username the X2go session runs as
- @rtype: C{str}
-
- """
- return self.session_object.get_transport().get_username()
- __get_username = get_username
-
- def get_password(self):
- """\
- After a session has been setup up you can query the
- username's password from the session.
-
- @return: the username's password
- @rtype: C{str}
-
- """
- return self.session_object._session_password
-
- def get_server(self):
- """\
- After a session has been setup up you can query the
- hostname of the host the sessions is connected to (or
- about to connect to).
-
- @return: the hostname of the server the X2go session is
- connected to (as an C{(addr,port)} tuple)
- @rtype: tuple
-
- """
- return self.session_object.get_transport().getpeername()
- __get_server = get_server
-
- def get_session_name(self):
- """\
- Retrieve the server-side X2go session name for the session that has
- been registered under C{profile_id}.
-
- @return: X2go session name
- @rtype: C{str}
-
- """
- return str(self.session_object.session_info) or None
- __get_session_name = get_session_name
-
- def connect(self, username='', password='', add_to_known_hosts=False, force_password_auth=False):
- """\
- Connect to a registered X2go session with registry hash C{<session_uuid>}.
- This method basically wraps around paramiko.SSHClient.connect() for the
- corresponding session.
-
- @param username: the username for the X2go server that is going to be
- connected to (as a last minute way of changing the session username)
- @type username: C{str}
- @param password: the user's password for the X2go server that is going to be
- connected to
- @type password: C{str}
- @param add_to_known_hosts: non-paramiko option, if C{True} paramiko.AutoAddPolicy()
- is used as missing-host-key-policy. If set to C{False} paramiko.RejectPolicy()
- is used
- @type add_to_known_hosts: C{bool}
- @param force_password_auth: disable SSH pub/priv key authentication mechanisms
- completely
- @type force_password_auth: C{bool}
-
- """
- # do connect
- connect_options = self.connect_options
- connect_options['password'] = password
- if username:
- connect_options['username'] = username
- connect_options['force_password_auth'] = force_password_auth
- self.connected = self.session_object.connect(self.server, **connect_options)
- return self.connected
- __connect = connect
-
- def disconnect(self):
- """\
- STILL UNDOCUMENTED
-
- """
- self.session_object.disconnect()
- self.connected = False
- self.running = False
- self.suspended = False
- self.terminated = False
- __disconnect = disconnect
-
- def set_print_action(self, print_action, **kwargs):
- """\
- STILL UNDOCUMENTED
-
- """
- if type(print_action) is not types.StringType:
- return False
- self.session_object.set_print_action(print_action, **kwargs)
- __set_print_action = set_print_action
-
- def start(self):
- """\
- Start a new X2go session on the remote X2go server.
-
- """
- session = self.session_object
- if session.start():
-
- if session.params.snd_system is not 'none':
- session.start_sound()
-
- session.start_sshfs()
- if self.printing:
- session.start_printing()
-
- if self.share_local_folders:
- if session.get_transport().reverse_tunnels['sshfs'][1] is not None:
- for _folder in self.share_local_folders:
- session.share_local_folder(_folder)
-
- session.run_command()
- self.suspended = False
- self.running = True
- self.terminated = False
-
- return self.running
- __start = start
-
- def resume(self, session_name):
- """\
- Resume or continue a suspended / running X2go session on the
- remote X2go server.
-
- @param session_name: the server-side name of an X2go session
- @type session_name: C{str}
-
- """
- self.session_object.associate(session_name)
- if self.session_object.resume():
-
- if self.session_object.params.snd_system is not 'none':
- self.session_object.start_sound()
-
- self.session_object.start_sshfs()
- if self.printing:
- self.session_object.start_printing()
-
- self.suspended = False
- self.running = True
- self.terminated = False
- return True
-
- return False
- __resume = resume
-
- def suspend(self):
- """\
- Suspend an X2go session.
-
- You can either suspend a session that you have formerly
- started/resumed the current X2goClient instance.
-
- Or you can suspend a non-attached session by simply
- registering an X2go server session and then passing the
- server-side X2go session name to this method.
-
- """
- if self.session_object.suspend():
-
- self.running = False
- self.suspended = True
- return True
-
- return False
- __suspend = suspend
-
- def terminate(self):
- """\
- Terminate an X2go session.
-
- You can either terminate a session that you have formerly
- started/resumed within the current X2goClient instance.
-
- Or you can terminate a non-attached session by simply
- registering an X2go server session and then passing the
- server-side X2go session name to this method.
-
- """
- if self.session_object.terminate():
-
- self.running = False
- self.suspended = False
- self.terminated = True
- return True
-
- return False
- __terminate = terminate
-
- def get_profile_name(self):
- """\
- Retrieve the profile name of this registered session.
-
- @return: X2go client profile name of the session
- @rtype: C{str}
-
- """
- return self.profile_name
- __get_profile_name = get_profile_name
-
- def get_profile_id(self):
- """\
- Retrieve this registered session's profile id.
-
- @return: the session profile's id
- @rtype: C{str}
-
- """
- return self.profile_id
- __get_profile_id = get_profile_id
-
- ###
- ### QUERYING INFORMATION
- ###
-
- def session_ok(self):
- """\
- Test if this registered X2go session is
- in a healthy state.
-
- @return: C{True} if session is ok, C{False} otherwise
- @rtype: C{bool}
-
- """
- return self.session_object.ok()
- __session_ok = session_ok
-
-
- def is_connected(self):
- """\
- Test if this registered X2go session is connected to the
- remote server.
-
- @return: C{True} if session is connected, C{False} otherwise
- @rtype: C{bool}
-
- """
- return self.session_object.is_connected()
- _is_connected = is_connected
-
- def is_running(self):
- """\
- Test if this registered X2go session is up and running.
-
- @return: C{True} if session is running, C{False} otherwise
- @rtype: C{bool}
-
- """
- return self.is_connected() and self.session_object.is_running()
- _is_running = is_running
-
- def is_suspended(self):
- """\
- Test if this registered X2go session is in suspended state.
-
- @return: C{True} if session is suspended, C{False} otherwise
- @rtype: C{bool}
-
- """
- return self.is_connected() and self.session_object.is_suspended()
- __is_suspended = is_suspended
-
- def has_terminated(self):
- """\
- Test if this registered X2go session has terminated.
-
- @return: C{True} if session has terminated, C{False} otherwise
- @rtype: C{bool}
-
- """
- return self.is_connected() and self.session_object.has_terminated()
- __has_terminated = has_terminated
-
- def share_local_folder(self, folder_name):
- """\
- Share a local folder with this registered X2go session.
-
- @param folder_name: the full path to an existing folder on the local
- file system
- @type folder_name: C{str}
-
- @return: returns C{True} if the local folder has been successfully mounted within
- this registered X2go server session
- @rtype: C{bool}
-
- """
- return self.session_object.share_local_folder(folder_name=folder_name)
- __share_local_folder = share_local_folder
-
+# import the default terminal session backend
+from x2go.backends.control import X2goControlSession
+from x2go.backends.terminal import X2goTerminalSession
+from x2go.backends.proxy import X2goProxy
+from x2go.backends.info import X2goServerSessionInfo
+from x2go.backends.info import X2goServerSessionList
class X2goSessionRegistry(object):
"""\
@@ -392,6 +58,7 @@ class X2goSessionRegistry(object):
self.logger.tag = __NAME__
self.registry = {}
+ self.control_sessions = {}
def __repr__(self):
result = 'X2goSessionRegistry('
@@ -421,53 +88,39 @@ class X2goSessionRegistry(object):
"""
return self(session_uuid).profile.profile_name
- def register(self, profile_id, profile_name, **kwargs):
-
- _r = X2goRegisteredSession(logger=self.logger)
- session_uuid = _r()
- self.registry[session_uuid] = _r
- self(session_uuid).profile_id = profile_id
- self(session_uuid).profile_name = profile_name
- self(session_uuid).session_params = kwargs
- self(session_uuid).server = kwargs['server']
- del kwargs['server']
- self(session_uuid).printing = kwargs['printing']
- del kwargs['printing']
- self(session_uuid).share_local_folders = kwargs['share_local_folders']
- del kwargs['share_local_folders']
- self(session_uuid).session_params = kwargs
- # differentiate SSH options from X2go options
- _session_options = copy.deepcopy(kwargs)
- _connect_options = copy.deepcopy(kwargs)
-
- for k in kwargs.keys():
- if k in session._X2GO_SESSION_OPTIONS:
- del _connect_options[k]
- else:
- del _session_options[k]
-
+ def register(self, server, profile_id, profile_name,
+ control_backend=X2goControlSession,
+ terminal_backend=X2goTerminalSession,
+ info_backend=X2goServerSessionInfo,
+ list_backend=X2goServerSessionList,
+ proxy_backend=X2goProxy,
+ **kwargs):
+
+ control_session = None
+ if profile_id in self.control_sessions.keys():
+ control_session = control_sessions[profile_id]
+
+ s = session.X2goSession(server=server, control_session=control_session,
+ profile_id=profile_id, profile_name=profile_name,
+ control_backend=control_backend,
+ terminal_backend=terminal_backend,
+ info_backend=info_backend,
+ list_backend=list_backend,
+ proxy_backend=proxy_backend,
+ logger=self.logger, **kwargs)
+
+ session_uuid = s._X2goSession__get_uuid()
self.logger('registering X2go session %s...' % profile_name, log.loglevel_NOTICE)
self.logger('registering X2go session with UUID %s' % session_uuid, log.loglevel_DEBUG)
- self.logger('X2go session options for profile %s:' % profile_name, log.loglevel_DEBUG)
- for k in _session_options:
- self.logger(' %s: %s' % (k, _session_options[k]), log.loglevel_DEBUG)
-
- self.logger('Paramiko connect options for profile %s are:' % profile_name, log.loglevel_DEBUG)
- for k in _connect_options:
- self.logger(' %s: %s' % (k,_connect_options[k]), log.loglevel_DEBUG)
-
- self(session_uuid).session_object = session.X2goSession(logger=self.logger, **_session_options)
- self(session_uuid).connected = False
- self(session_uuid).running = False
- self(session_uuid).suspended = False
- self(session_uuid).terminated = False
- self(session_uuid).connect_options = _connect_options
- self(session_uuid).session_options = _session_options
+
+ self.registry[session_uuid] = s
+ if profile_id not in self.control_sessions.keys():
+ self.control_sessions[profile_id] = s.get_control_session()
return session_uuid
def _sessionsWithState(self, state):
- return [ session for session in self.registry.values() if eval('session.%s' % state) ]
+ return [ ts for ts in self.registry.values() if eval('ts.%s' % state) ]
@property
def connected_sessions(self):
diff --git a/x2go/rforward.py b/x2go/rforward.py
index 8161338..9c9ee19 100644
--- a/x2go/rforward.py
+++ b/x2go/rforward.py
@@ -57,13 +57,16 @@ def x2go_transport_tcp_handler(chan, (origin_addr, origin_port), (server_addr, s
transport = chan.get_transport()
transport._queue_incoming_channel(chan)
rev_tuns = transport.reverse_tunnels
- if int(server_port) in [ int(tunnel[0]) for tunnel in rev_tuns.values() ]:
- if rev_tuns['snd'] is not None and int(server_port) == int(rev_tuns['snd'][0]):
- rev_tuns['snd'][1].notify()
+ for session_name in rev_tuns.keys():
- elif rev_tuns['sshfs'] is not None and int(server_port) == int(rev_tuns['sshfs'][0]):
- rev_tuns['sshfs'][1].notify()
+ if int(server_port) in [ int(tunnel[0]) for tunnel in rev_tuns[session_name].values() ]:
+
+ if rev_tuns[session_name]['snd'] is not None and int(server_port) == int(rev_tuns[session_name]['snd'][0]):
+ rev_tuns[session_name]['snd'][1].notify()
+
+ elif rev_tuns[session_name]['sshfs'] is not None and int(server_port) == int(rev_tuns[session_name]['sshfs'][0]):
+ rev_tuns[session_name]['sshfs'][1].notify()
class X2goRevFwTunnel(threading.Thread):
@@ -201,6 +204,7 @@ class X2goRevFwTunnel(threading.Thread):
self.logger('waiting for incoming data channel on X2go server port: [localhost]:%s' % self.server_port, loglevel=log.loglevel_DEBUG)
self.incoming_channel.wait()
+
if self._keepalive:
self.logger('detected incoming data channel on X2go server port: [localhost]:%s' % self.server_port, loglevel=log.loglevel_DEBUG)
_chan = self.ssh_transport.accept()
diff --git a/x2go/session.py b/x2go/session.py
index dbe7ab6..77c2dfa 100644
--- a/x2go/session.py
+++ b/x2go/session.py
@@ -18,1143 +18,443 @@
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
"""\
-X2goSession class - core functions for handling your individual X2go sessions.
-
+X2goSession class - the X2goClient's session backend
"""
__NAME__ = 'x2gosession-pylib'
-# modules
-import os, sys, types
-import paramiko
-import gevent
-import threading
-import signal
-import cStringIO
import copy
+import types
+import uuid
+import time
+import threading
# Python X2go modules
-import proxy
-import rforward
-import sftpserver
-import printing
import log
-import defaults
import utils
-import x2go_exceptions
-import guardian
+import session
+from x2go_exceptions import *
-from cleanup import x2go_cleanup
-
-# for debugging code
-import pprint
-
-# we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables)
-from defaults import LOCAL_HOME as _LOCAL_HOME
-from defaults import CURRENT_LOCAL_USER as _CURRENT_LOCAL_USER
-from defaults import X2GO_SESSION_ROOTDIR as _X2GO_SESSION_ROOTDIR
+from x2go.backends.control import X2goControlSession
+from x2go.backends.terminal import X2goTerminalSession
+from x2go.backends.info import X2goServerSessionInfo
+from x2go.backends.info import X2goServerSessionList
+from x2go.backends.proxy import X2goProxy
+from x2go.backends.profiles import X2goSessionProfiles
# options of the paramiko.SSHClient().connect()
-_X2GO_SESSION_OPTIONS = ('geometry', 'depth', 'link', 'pack',
- 'cache_type', 'kblayout', 'kbtype',
- 'session_type', 'snd_system', 'cmd',
- 'rootdir', 'loglevel', 'profile_name', 'profile_id',
- 'print_action', 'print_action_args',
- 'proxy_class', 'logger',
+_X2GO_SESSION_PARAMS = ('geometry', 'depth', 'link', 'pack',
+ 'cache_type', 'kblayout', 'kbtype',
+ 'session_type', 'snd_system', 'cmd',
+ 'rootdir', 'loglevel', 'profile_name', 'profile_id',
+ 'print_action', 'print_action_args',
+ 'proxy_class', 'logger',
)
+class X2goSession(object):
-def _rewrite_cmd(cmd):
-
- # start with an empty string
- cmd = cmd or ''
-
- # find window manager commands
- if cmd in defaults.window_managers.keys():
- cmd = defaults.window_managers[cmd]
-
- # X2go run command replace X2GO_SPACE_CHAR string with blanks
- cmd.replace(" ", "X2GO_SPACE_CHAR")
-
- # place quot marks around cmd if not empty string
- if cmd:
- cmd = '"%s"' % cmd
- return cmd
-
-
-class X2goSessionParams(object):
- """\
- The L{X2goSessionParams} class is used to store all parameters that
- L{X2goSession} objects are constructed with.
-
- """
- def rewrite_session_type(self):
- """\
- Rewrite the X2go session type, so that the X2go server
- can understand it (C{desktop} -> C{D}).
-
- Also if the object's C{command} property is a known window
- manager, the session type will be set to 'D'
- (i.e. desktop).
-
- @return: 'D' if session should probably a desktop session,
- 'R' (for rootless) else
- @rtype: str
-
- """
- session_type = self.session_type
- cmd = self.cmd
- if session_type == "desktop":
- self.session_type = 'D'
- return
- if cmd:
- if cmd in defaults.window_managers.keys():
- self.session_type = 'D'
- return
- if os.path.basename(cmd) in defaults.window_managers.values():
- self.session_type = 'D'
- return
- self.session_type = 'R'
-
- def update(self, properties_to_be_updated={}):
- """\
- Update all properties in the object L{X2goSessionParams} object from
- the passed on dictionary.
-
- @param properties_to_be_updated: a dictionary with L{X2goSessionParams}
- property names as keys und their values to be update in
- L{X2goSessionParams} object.
- @type properties_to_be_updated: dict
-
- """
- for key in properties_to_be_updated.keys():
- setattr(self, key, properties_to_be_updated[key] or '')
- self.rewrite_session_type()
-
-
-class X2goServerSessionInfo(object):
- """\
- L{X2goServerSessionInfo} is used to store all information
- that is retrieved from the connected X2go server on
- L{X2goSession.start()} resp. L{X2goSession.resume()}.
-
- """
- def __str__(self):
- return self.name
- def __repr__(self):
- return "<%s instance: %s>" % (self.__class__, self.name)
-
- def _parse_x2golistsessions_line(self, x2go_output):
- """\
- Parse a single line of X2go's listsessions output.
-
- """
- l = x2go_output.split("|")
- self.name = l[1]
- self.cookie = l[6]
- self.agent_pid = int(l[0])
- self.display = int(l[2])
- self.status = l[4]
- self.graphics_port = int(l[8])
- self.snd_port = int(l[9])
- self.sshfs_port = int(l[13])
- self.username = l[11]
- self.hostname = l[3]
- # TODO: turn into datetime object
- self.date_created = l[5]
- # TODO: turn into datetime object
- self.date_suspended = l[10]
- self.local_container = ''
-
- def _parse_x2gostartagent_output(self, x2go_output):
- """\
- Parse x2gostartagent output.
-
- """
- l = x2go_output.split("\n")
- self.name = l[3]
- self.cookie = l[1]
- self.agent_pid = int(l[2])
- self.display = int(l[0])
- self.graphics_port = int(l[4])
- self.snd_port = int(l[5])
- self.sshfs_port = int(l[6])
- self.username = ''
- self.hostname = ''
- # TODO: we have to see how we fill these fields here...
- self.date_created = ''
- self.date_suspended = ''
- # TODO: presume session is running after x2gostartagent, this could be better
- self.status = 'R'
- self.local_container = ''
- self.remote_container = ''
-
- def initialize(self, x2go_output, username='', hostname='', local_container='', remote_container=''):
- """\
- Parse X2go server's C{x2gostartagent} stdout values.
-
- @param x2go_output: X2go server's C{x2gostartagent} command output, each value
- separated by a newline character.
- @type x2go_output: str
- @param username: session user name
- @type username: str
- @param hostname: hostname of X2go server
- @type hostname: str
- @param local_container: X2go client session directory for config files, cache and session logs
- @type local_container: str
- @param remote_container: X2go server session directory for config files, cache and session logs
- @type remote_container: str
-
- """
- self._parse_x2gostartagent_output(x2go_output)
- self.username = username
- self.hostname = hostname
- self.local_container = local_container
- self.remote_container = remote_container
-
- def clear(self):
- """\
- Clear all properties of a L{X2goServerSessionInfo} object.
-
- """
- self.name = ''
- self.cookie = ''
- self.agent_pid = ''
- self.display = ''
- self.graphics_port = ''
- self.snd_port = ''
- self.sshfs_port = ''
- self.username = ''
- self.hostname = ''
- self.date_created = ''
- self.date_suspended = ''
- self.status = ''
- self.local_container = ''
- self.remote_container = ''
-
- __init__ = clear
-
-
-class X2goServerSessionList(object):
- """\
- L{X2goServerSessionList} is used to store all information
- that is retrieved from a connected X2go server on a
- L{X2goSession.list_sessions()} call.
-
- """
- def __init__(self, x2go_output):
- """\
- @param x2go_output: X2go server's C{x2golistsessions} command output, each
- session separated by a newline character. Session values are separated
- by Unix Pipe Symbols ('|')
- @type x2go_output: str
-
- """
- self.sessions = {}
- lines = x2go_output.split("\n")
- for line in lines:
- if not line:
- continue
- s_info = X2goServerSessionInfo()
- s_info._parse_x2golistsessions_line(line)
- self.sessions[s_info.name] = s_info
-
-
-class X2goSession(paramiko.SSHClient):
- """\
- Class for managing X2go sessions on a remote X2go server via Paramiko/SSH.
- With the X2goSession class you can start new X2go sessions, resume suspended
- sessions or suspend resp. terminate currently running sessions on a
- connected X2go server.
-
- When suspending or terminating sessions there are two possible ways:
-
- 1. Initialize an X2go session object, start a new session (or resume)
- and use the L{X2goSession.suspend()} or L{X2goSession.terminate()} method
- to suspend/terminate the current session object.
- 2. Alternatively, you can pass a session name to L{X2goSession.suspend()}
- or L{X2goSession.terminate()}. If a session of this name exists on the
- X2go server the respective action will be performed on the session.
-
- An L{X2goSession} object uses two main data structure classes:
-
- - L{X2goSessionParams}: stores all parameters that have been passed to the
- constructor method.
-
- - L{X2goServerSessionInfo}: when starting or resuming a session, an object of this class
- will be used to store all information retrieved from the X2go server.
-
- @param geometry: screen geometry of the X2go session. Can be either C{<width>x<height>}
- or C{fullscreen}
- @type geometry: str
- @param depth: color depth in bits (common values: C{16}, C{24})
- @type depth: int
- @param link: network link quality (either one of C{modem}, C{isdn}, C{adsl}, C{wan} or C{lan})
- @type link: str
- @param pack: compression method for NX based session proxying
- @type pack: str
- @param cache_type: a dummy parameter that is passed to the L{X2goProxy}. In NX Proxy
- (class C{X2goNX3Proxy}) this originally is the session name. With X2go it
- defines the name of the NX cache directory. Best is to leave it untouched.
- @type cache_type: str
- @param kblayout: keyboard layout, e.g. C{us} (default), C{de}, C{fr}, ...
- @type kblayout: str
- @param kbtype: keyboard type, e.g. C{pc105/us} (default), C{pc105/de}, ...
- @type kbtype: str
- @param session_type: either C{desktop} or C{application} (rootless session)
- @type session_type: str
- @param snd_system: sound system to be used on server (C{none}, C{pulse} (default),
- C{arts} (obsolete) or C{esd})
- @type snd_system: str
- @param cmd: command to be run on X2go server after session start (only used
- when L{X2goSession.start()} is called, ignored on resume, suspend etc.
- @type cmd: str
- @param rootdir: X2go session directory, normally C{~/.x2go}
- @type rootdir: str
- @param proxy_class: other than the default L{X2goProxy} class
- @type proxy_class: L{X2goProxy} related instance
- @param print_action: either a print action short name (PDFVIEW, PDFSAVE, PRINT, PRINTCMD) or the
- resp. C{X2goPrintActionXXX} class (where XXX equals one of the given short names)
- @type print_action: str or class
- @param print_action_args: optional arguments for a given print_action (for further info refer to
- L{X2goPrintActionPDFVIEW}, L{X2goPrintActionPDFSAVE}, L{X2goPrintActionPRINT} and L{X2goPrintActionPRINTCMD})
- @type print_action_args: dict
- @param logger: you can pass an L{X2goLogger} object to the
- L{X2goProxy} constructor
- @type logger: L{X2goLogger} instance
- @param loglevel: if no L{X2goLogger} object has been supplied a new one will be
- constructed with the given loglevel
- @type loglevel: int
-
- """
- associated = False
- params = None
- session_info = None
-
- proxy_class = None
- proxy = None
- proxy_subprocess = None
-
- guardian_thread = None
- reverse_tunnels = None
-
- print_queue = None
-
- _session_auth_rsakey = None
- _remote_home = None
- _remote_group = {}
-
- def __init__(self,
- geometry="800x600", depth=24, link="adsl", pack="16m-jpeg-9",
- cache_type="unix-kde", kblayout='us', kbtype='pc105/us',
- session_type="application", snd_system='pulse', cmd=None,
- rootdir=None, proxy_class=None,
- profile_name='UNKNOWN', profile_id=utils._genSessionProfileId(),
- print_action=None, print_action_args={},
- logger = None, loglevel=log.loglevel_DEFAULT,
- *args, **kwargs):
- """\
- Initialize an X2go session. With the X2goSession class you can start
- new X2go sessions, resume suspended sessions or suspend resp. terminate
- currently running sessions on a connected X2go server.
+ def __init__(self, server, control_session=None,
+ profile_id=None, profile_name=None,
+ printing=None, share_local_folders=[],
+ control_backend=X2goControlSession,
+ terminal_backend=X2goTerminalSession,
+ info_backend=X2goServerSessionInfo,
+ list_backend=X2goServerSessionList,
+ proxy_backend=X2goProxy,
+ known_hosts=None,
+ logger=None, loglevel=log.loglevel_DEFAULT,
+ **params):
- """
if logger is None:
self.logger = log.X2goLogger(loglevel=loglevel)
else:
self.logger = copy.deepcopy(logger)
self.logger.tag = __NAME__
- if proxy_class is None:
- proxy_class = proxy.DEFAULT_PROXY_CLASS
-
- self.session_info = X2goServerSessionInfo()
- self.params = X2goSessionParams()
-
- self.params.geometry = geometry
- self.params.depth = str(depth)
- self.params.link = link
- self.params.pack = pack
- self.params.cache_type = cache_type
- self.params.session_type = session_type
- self.params.kblayout = kblayout
- self.params.kbtype = kbtype
- self.params.snd_system = snd_system
- self.params.cmd = cmd
- self.params.rootdir = (type(rootdir) is types.StringType) and rootdir or os.path.join(_LOCAL_HOME,_X2GO_SESSION_ROOTDIR)
- self.params.update()
-
- self.proxy_class = proxy_class
-
- self.print_action = print_action
- self.print_action_args = print_action_args
-
- self._mk_session_rootdir(self.params.rootdir)
- paramiko.SSHClient.__init__(self, *args, **kwargs)
-
- def __del__(self):
- self._x2go_tidy_up()
-
- def _x2go_tidy_up(self):
-
- if self.proxy is not None:
- self.proxy.__del__()
-
- try:
- if self.get_transport() is not None:
- for _tunnel in [ _tun[1] for _tun in self.get_transport().reverse_tunnels.values() ]:
- if _tunnel is not None:
- _tunnel.__del__()
-
- self.get_transport().stop_thread()
-
- if self.print_queue is not None:
- self.print_queue.__del__()
-
- except AttributeError:
- pass
-
- def _mk_session_rootdir(self, d):
-
- try:
- os.mkdir(d)
- except OSError, e:
- if e.errno == 17:
- # file exists
- pass
+ self._keep_alive = True
+
+ self.uuid = uuid.uuid1()
+ self.connected = False
+ self.running = False
+ self.suspended = False
+ self.terminated = False
+
+ self.profile_id = profile_id
+ self.profile_name = profile_name
+ self.server = server
+ self.printing = printing
+ self.share_local_folders = share_local_folders
+ self._control_backend = control_backend
+ self._terminal_backend = terminal_backend
+ self._info_backend = info_backend
+ self._list_backend = list_backend
+ self._proxy_backend = proxy_backend
+ _terminal_params = copy.deepcopy(params)
+ _control_params = copy.deepcopy(params)
+
+ for p in params.keys():
+ if p in session._X2GO_SESSION_PARAMS:
+ del _control_params[p]
else:
- raise OSError, e
-
- def _x2go_exec_command(self, cmd_line, loglevel=log.loglevel_INFO, **kwargs):
-
- if type(cmd_line) == types.ListType:
- cmd = " ".join(cmd_line)
- else:
- cmd = cmd_line
- if self.get_transport() is not None:
-
- try:
- self.logger('executing command on X2go server: %s' % cmd, loglevel)
- return self.exec_command(cmd, **kwargs)
- except AttributeError:
- raise x2go_exceptions.X2goSessionException('the Paramiko/SSH session of X2go session %s has died' % self.session_info)
-
+ del _terminal_params[p]
+
+ self.logger('X2go control session parameters for profile %s:' % profile_name, log.loglevel_DEBUG)
+ for p in _control_params:
+ self.logger(' %s: %s' % (p, _control_params[p]), log.loglevel_DEBUG)
+ self.logger('X2go terminal session parameters for profile %s:' % profile_name, log.loglevel_DEBUG)
+ for p in _terminal_params:
+ self.logger(' %s: %s' % (p,_terminal_params[p]), log.loglevel_DEBUG)
+
+ self.control_params = _control_params
+ self.terminal_params = _terminal_params
+
+ self.logger('starting X2goSession', loglevel=log.loglevel_DEBUG)
+ if control_session is None:
+ self.control_session = control_backend(terminal_backend=terminal_backend,
+ info_backend=info_backend,
+ list_backend=list_backend,
+ logger=logger)
else:
- raise x2go_exceptions.X2goSessionException('the Paramiko/SSH client is not connected')
-
- def _x2go_sftp_put(self, local_path, remote_path):
-
- self.logger('sFTP-put: %s -> %s:%s' % (local_path, self.session_info.hostname, remote_path), loglevel=log.loglevel_DEBUG)
- self.sftp_client.put(local_path, remote_path)
-
- def _x2go_sftp_write(self, remote_path, content):
-
- self.logger('sFTP-write: opening remote file %s on host %s for writing' % (remote_path, self.session_info.hostname), loglevel=log.loglevel_DEBUG)
- remote_fileobj = self.sftp_client.open(remote_path, 'w')
- self.logger('sFTP-write: writing content: %s' % content, loglevel=log.loglevel_DEBUG_SFTPXFER)
- remote_fileobj.write(content)
- remote_fileobj.close()
-
- def _x2go_sftp_remove(self, remote_path):
+ self.control_session = control_session
- self.logger('sFTP-write: removing remote file %s on host %s' % (remote_path, self.session_info.hostname), loglevel=log.loglevel_DEBUG)
- self.sftp_client.remove(remote_path)
+ self.terminal_session = None
+ self.logger('starting X2goSession', loglevel=log.loglevel_DEBUG)
+ if known_hosts:
+ self.control_session.load_host_keys(known_hosts)
- @property
- def _x2go_remote_home(self):
-
- if self._remote_home is None:
- (stdin, stdout, stderr) = self._x2go_exec_command('echo $HOME')
- self._remote_home = stdout.read().split()[0]
- self.logger('remote user\' home directory: %s' % self._remote_home, loglevel=log.loglevel_DEBUG)
- return self._remote_home
- else:
- return self._remote_home
-
- def _x2go_remote_group(self, group):
-
- if not self._remote_group.has_key(group):
- (stdin, stdout, stderr) = self._x2go_exec_command('getent group %s | cut -d":" -f4' % group)
- self._remote_group[group] = stdout.read().split('\n')[0].split(',')
- self.logger('remote %s group: %s' % (group, self._remote_group[group]), loglevel=log.loglevel_DEBUG)
- return self._remote_group[group]
- else:
- return self._remote_group[group]
-
- @property
- def _x2go_session_auth_rsakey(self):
- if self._session_auth_rsakey is None:
- self._session_auth_rsakey = paramiko.RSAKey.generate(defaults.RSAKEY_STRENGTH)
- return self._session_auth_rsakey
-
- def connect(self, hostname, port=22, username=None, password=None, pkey=None,
- key_filename=None, timeout=None, allow_agent=False, look_for_keys=True,
- add_to_known_hosts=False, force_password_auth=False):
+ def __str__(self):
+ return self.__get_uuid()
+ def __repr__(self):
+ result = 'X2goRegisteredSession('
+ for p in dir(self):
+ if '__' in p or not p in self.__dict__ or type(p) is types.InstanceType: continue
+ result += p + '=' + str(self.__dict__[p]) + ', '
+ return result + ')'
+ def __call__(self):
+ return self.__get_uuid()
+
+ def get_uuid(self):
"""\
- Connect to an X2go server and authenticate to it. This method is directly
- inherited from the paramiko.SSHClient module. The features of the Paramiko
- SSH client connect method are recited here. The parameters C{add_to_known_hosts}
- and C{force_password_auth} have been added as a parameter for X2go.
-
- The server's host key
- is checked against the system host keys (see C{load_system_host_keys})
- and any local host keys (C{load_host_keys}). If the server's hostname
- is not found in either set of host keys, the missing host key policy
- is used (see C{set_missing_host_key_policy}). The default policy is
- to reject the key and raise an C{SSHException}.
-
- Authentication is attempted in the following order of priority:
-
- - The C{pkey} or C{key_filename} passed in (if any)
- - Any key we can find through an SSH agent
- - Any "id_rsa" or "id_dsa" key discoverable in C{~/.ssh/}
- - Plain username/password auth, if a password was given
-
- If a private key requires a password to unlock it, and a password is
- passed in, that password will be used to attempt to unlock the key.
-
- @param hostname: the server to connect to
- @type hostname: str
- @param port: the server port to connect to
- @type port: int
- @param username: the username to authenticate as (defaults to the
- current local username)
- @type username: str
- @param password: a password to use for authentication or for unlocking
- a private key
- @type password: str
- @param pkey: an optional private key to use for authentication
- @type pkey: C{PKey}
- @param key_filename: the filename, or list of filenames, of optional
- private key(s) to try for authentication
- @type key_filename: str or list(str)
- @param timeout: an optional timeout (in seconds) for the TCP connect
- @type timeout: float
- @param allow_agent: set to False to disable connecting to the SSH agent
- @type allow_agent: bool
- @param look_for_keys: set to False to disable searching for discoverable
- private key files in C{~/.ssh/}
- @type look_for_keys: bool
- @param add_to_known_hosts: non-paramiko option, if C{True} paramiko.AutoAddPolicy()
- is used as missing-host-key-policy. If set to C{False} paramiko.RejectPolicy()
- is used
- @type add_to_known_hosts: bool
- @param force_password_auth: non-paramiko option, disable pub/priv key authentication
- completely, even if the C{pkey} or the C{key_filename} parameter is given
- @type force_password_auth: bool
-
- @raise BadHostKeyException: if the server's host key could not be
- verified
- @raise AuthenticationException: if authentication failed
- @raise SSHException: if there was any other error connecting or
- establishing an SSH session
- @raise socket.error: if a socket error occurred while connecting
+ STILL UNDOCUMENTED
"""
- if add_to_known_hosts:
- self.set_missing_host_key_policy(paramiko.AutoAddPolicy())
-
- # disable pub/priv key authentication if forced
- if force_password_auth:
- key_filename = None
- pkey = None
-
- self.logger('connecting to %s' % hostname, log.loglevel_NOTICE)
-
- if (key_filename or pkey):
- try:
- self.logger('trying SSH pub/priv key authentication with server', log.loglevel_DEBUG)
- paramiko.SSHClient.connect(self, hostname, port=port, username=username, pkey=pkey,
- key_filename=key_filename, timeout=timeout, allow_agent=allow_agent,
- look_for_keys=look_for_keys)
- except paramiko.AuthenticationException, e:
- if password:
- self.logger('next auth mechanism we\'ll try is keyboard-interactive authentication', log.loglevel_DEBUG)
- paramiko.SSHClient.connect(self, hostname, port=port, username=username, passwort=password,
- timeout=timeout, allow_agent=allow_agent,
- look_for_keys=look_for_keys)
- else:
- raise(e)
-
- # if there is not private key, we will use the given password
- elif password:
- self.logger('performing SSH keyboard-interactive authentication with server', log.loglevel_DEBUG)
- paramiko.SSHClient.connect(self, hostname, port=port, username=username, password=password,
- timeout=timeout, allow_agent=allow_agent, look_for_keys=look_for_keys)
-
- # authentication failed
- else:
- raise paramiko.AuthenticationException()
-
- # if we succeed, we immediately grab us an sFTP client session
- self.sftp_client = self.open_sftp()
+ return str(self.uuid)
+ __get_uuid = get_uuid
- # preparing reverse tunnels
- ssh_transport = self.get_transport()
- ssh_transport.reverse_tunnels = {
- 'snd': (0, None),
- 'sshfs': (0, None),
- }
-
- # mark transport as X2goSession
- ssh_transport._x2go_session_marker = True
+ def get_username(self):
+ """\
+ After a session has been setup up you can query the
+ username the sessions runs as.
- # once connected start the X2goSession guardian
- self.guardian_thread = guardian.X2goSessionGuardian(self, logger=self.logger)
- self.guardian_thread.start()
- self.guardian_thread.active_threads.append(self.get_transport())
- self._session_password = password
+ @return: the remote username the X2go session runs as
+ @rtype: C{str}
- return (self.get_transport() is not None)
+ """
+ return self.control_session.get_transport().get_username()
+ __get_username = get_username
- def disconnect(self):
+ def get_password(self):
"""\
- STILL UNDOCUMENTED
+ After a session has been setup up you can query the
+ username's password from the session.
+
+ @return: the username's password
+ @rtype: C{str}
"""
- self.get_transport().close()
+ return self.control_session._session_password
- def start(self, **kwargs):
+ def get_server(self):
"""\
- Start a new X2go session.
+ After a session has been setup up you can query the
+ hostname of the host the sessions is connected to (or
+ about to connect to).
- The L{X2goSession.start()} method accepts any parameter
- that can be passed to the class constructor.
+ @return: the hostname of the server the X2go session is
+ connected to (as an C{(addr,port)} tuple)
+ @rtype: tuple
"""
- self.params.update(kwargs)
-
- _remote_username = self.get_transport().get_username()
- if _remote_username not in self._x2go_remote_group('x2gousers'):
- raise x2go_exceptions.X2goSessionException('remote user %s is not member of X2go server group x2gousers' % _remote_username)
-
- setkbd = "0"
- if self.params.kblayout or self.params.kbtype:
- setkbd = "1"
-
- cmd_line = [ "x2gostartagent",
- str(self.params.geometry),
- str(self.params.link),
- str(self.params.pack),
- str(self.params.cache_type+'-depth_'+self.params.depth),
- str(self.params.kblayout),
- str(self.params.kbtype),
- str(setkbd),
- str(self.params.session_type),
- self.params.cmd,
- ]
-
- (stdin, stdout, stderr) = self._x2go_exec_command(cmd_line)
-
- self.session_info.initialize(stdout.read(),
- username=_remote_username,
- hostname=self.get_transport().getpeername(),
- )
-
- # local path may be a Windows path, so we use the path separator of the local system
- self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name)
- # remote path is always a UniX path...
- self.session_info.remote_container = '%s/%s/C-%s' % (self._x2go_remote_home,
- _X2GO_SESSION_ROOTDIR,
- self.session_info.name,
- )
-
- # set up SSH tunnel for X11 graphical elements
- self.proxy = self.proxy_class(session_info=self.session_info, ssh_transport=self.get_transport(), logger=self.logger)
- self.proxy_subprocess = self.proxy.start_proxy()
- self.guardian_thread.active_threads.append(self.proxy)
-
- self.associated = True
- return self.ok()
-
- def start_sound(self):
- """\
- Initialize Paramiko/SSH reverse forwarding tunnel for X2go sound.
+ return self.control_session.get_transport().getpeername()
+ __get_server = get_server
- Currently supported audio protocols:
+ def get_session_name(self):
+ """\
+ Retrieve the server-side X2go session name for the session that has
+ been registered under C{profile_id}.
- - Pulse Audio
- - Esound
+ @return: X2go session name
+ @rtype: C{str}
"""
- _tunnel = None
- ssh_transport = self.get_transport()
- if ssh_transport.reverse_tunnels['snd'][1] is None:
- if self.params.snd_system == 'pulse':
- self.logger('initializing Pulse Audio sound support in X2go session', loglevel=log.loglevel_INFO)
- ###
- ### PULSE AUDIO
- ###
- # setup pulse client config file on X2go server
- cmd_line = "echo 'default-server=localhost:%s'>%s/.pulse-client.conf;" % (self.session_info.snd_port, self.session_info.remote_container) + \
- "echo 'cookie-file=%s/.pulse-cookie'>>%s/.pulse-client.conf" % (self.session_info.remote_container, self.session_info.remote_container)
- (stdin, stdout, stderr) = self._x2go_exec_command(cmd_line)
-
- self._x2go_sftp_put(local_path='%s/.pulse-cookie' % _LOCAL_HOME, remote_path='%s/.pulse-cookie' % self.session_info.remote_container)
-
- # start reverse SSH tunnel for pulse stream
- _tunnel = rforward.X2goRevFwTunnel(server_port=self.session_info.snd_port,
- remote_host='localhost',
- remote_port=4713,
- ssh_transport=ssh_transport,
- logger=self.logger
- )
-
- elif self.params.snd_system == 'arts':
- ###
- ### ARTSD AUDIO
- ###
- self.logger('the ArtsD sound server (as in KDE3) is obsolete and will not be supported by Python X2go...', loglevel=log.loglevel_WARNING)
-
- elif self.params.snd_system == 'esd':
- ###
- ### ESD AUDIO
- ###
-
- self.logger('initializing ESD sound support in X2go session', loglevel=log.loglevel_INFO)
- self._x2go_sftp_put(local_path='%s/.esd_auth' % _LOCAL_HOME, remote_path='%s/.esd_auth' % self._x2go_remote_home)
-
- # start reverse SSH tunnel for pulse stream
- _tunnel = rforward.X2goRevFwTunnel(server_port=self.session_info.snd_port,
- remote_host='localhost',
- remote_port=16001,
- ssh_transport=ssh_transport,
- logger=self.logger
- )
-
-
- if _tunnel is not None:
- ssh_transport.reverse_tunnels['snd'] = (self.session_info.snd_port, _tunnel)
- _tunnel.start()
- self.guardian_thread.active_threads.append(_tunnel)
+ if self.terminal_session is not None:
+ return self.terminal_session.get_session_name() or None
+ __get_session_name = get_session_name
- else:
- # tunnel has already been started and might simply need a resume call
- ssh_transport.reverse_tunnels['snd'][1].resume()
-
- def start_sshfs(self):
+ def get_session_cmd(self):
"""\
- Initialize Paramiko/SSH reverse forwarding tunnel for X2go folder sharing.
+ STILL UNDOCUMENTED
"""
- # start reverse SSH tunnel for sshfs (folder sharing, printing)
- ssh_transport = self.get_transport()
- if ssh_transport.reverse_tunnels['sshfs'][1] is None:
+ if self.terminal_params.has_key('cmd'):
+ return self.terminal_params['cmd']
+ return None
- _tunnel = sftpserver.X2goRevFwTunnelToSFTP(server_port=self.session_info.sshfs_port,
- ssh_transport=ssh_transport,
- auth_key=self._x2go_session_auth_rsakey,
- logger=self.logger
- )
+ def get_control_session(self):
+ return self.control_session
+ __get_control_session = get_control_session
- if _tunnel is not None:
- ssh_transport.reverse_tunnels['sshfs'] = (self.session_info.sshfs_port, _tunnel)
- _tunnel.start()
- self.guardian_thread.active_threads.append(_tunnel)
-
- else:
- # tunnel has already been started and might simply need a resume call
- ssh_transport.reverse_tunnels['sshfs'][1].resume()
+ def get_terminal_session(self):
+ return self.terminal_session
+ __get_terminal_session = get_terminal_session
- def _x2go_pause_rev_fw_tunnel(self, name):
- # pause reverse SSH tunnel of name <name>
- ssh_transport = self.get_transport()
- _tunnel = ssh_transport.reverse_tunnels[name][1]
- if _tunnel is not None:
- _tunnel.pause()
-
- def stop_sound(self):
+ def connect(self, username='', password='', add_to_known_hosts=False, force_password_auth=False):
"""\
- Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2go sound.
-
- """
- self._x2go_pause_rev_fw_tunnel('sound')
+ Connect to a registered X2go session with registry hash C{<session_uuid>}.
+ This method basically wraps around paramiko.SSHClient.connect() for the
+ corresponding session.
- def stop_sshfs(self):
- """\
- Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2go folder sharing.
+ @param username: the username for the X2go server that is going to be
+ connected to (as a last minute way of changing the session username)
+ @type username: C{str}
+ @param password: the user's password for the X2go server that is going to be
+ connected to
+ @type password: C{str}
+ @param add_to_known_hosts: non-paramiko option, if C{True} paramiko.AutoAddPolicy()
+ is used as missing-host-key-policy. If set to C{False} paramiko.RejectPolicy()
+ is used
+ @type add_to_known_hosts: C{bool}
+ @param force_password_auth: disable SSH pub/priv key authentication mechanisms
+ completely
+ @type force_password_auth: C{bool}
"""
- self._x2go_pause_rev_fw_tunnel('sshfs')
+ if self.control_session.is_connected():
+ self.logger('control session is already connected, skipping authentication', loglevel=log.loglevel_DEBUG)
+ self.connected = True
+ else:
+ if username:
+ self.control_params['username'] = username
+ self.control_params['password'] = password
+ self.connected = self.control_session.connect(self.server, **self.control_params)
+ return self.connected
+ __connect = connect
- def start_printing(self):
+ def disconnect(self):
"""\
- Initialize X2go print spooling.
+ STILL UNDOCUMENTED
"""
- if self.session_info.username not in self._x2go_remote_group('x2goprint'):
- raise x2go_exceptions.X2goSessionException('remote user %s is not member of X2go server group x2goprint' % self.session_info.username)
-
- spool_dir = os.path.join(self.session_info.local_container, 'spool')
- if not os.path.exists(spool_dir):
- os.mkdir(spool_dir)
- self.share_local_folder(folder_name=spool_dir, folder_type='spool')
- self.print_queue = printing.X2goPrintQueue(spool_dir=spool_dir,
- print_action=self.print_action,
- print_action_args=self.print_action_args,
- logger=self.logger,
- )
- self.print_queue.start()
- self.guardian_thread.active_threads.append(self.print_queue)
+ self.terminal_session.disconnect()
+ self.connected = False
+ self.running = False
+ self.suspended = False
+ self.terminated = False
+ __disconnect = disconnect
def set_print_action(self, print_action, **kwargs):
"""\
STILL UNDOCUMENTED
"""
- self.print_queue.set_print_action(print_action, logger=self.logger, **kwargs)
-
+ if type(print_action) is not types.StringType:
+ return False
+ self.terminal_session.set_print_action(print_action, **kwargs)
+ __set_print_action = set_print_action
- def stop_printing(self):
- """\
- Shutdown (pause) the X2go Print Queue thread.
+ def clean_sessions(self):
+ self.control_session.clean_sessions()
- """
- if self.print_queue is not None:
- self.print_queue.pause()
+ def list_sessions(self):
+ return self.control_session.list_sessions()
- def share_local_folder(self, folder_name=None, folder_type='disk'):
+ def resume(self, session_name=None):
"""\
- Share a local folder with the X2go session.
+ Resume or continue a suspended / running X2go session on the
+ remote X2go server.
- @param folder_name: the full path to an existing folder on the local
- file system
- @type folder_name: str
- @param folder_type: one of 'disk' (a folder on your local hard drive), 'rm' (removeable device),
- 'cdrom' (CD/DVD Rom) or 'spool' (for X2go print spooling)
- @type folder_type: str
-
- @return: returns C{True} if the local folder has been successfully mounted within the X2go server session
- @rtype: bool
+ @param session_name: the server-side name of an X2go session
+ @type session_name: C{str}
"""
- if self.session_info.username not in self._x2go_remote_group('fuse'):
- raise x2go_exceptions.X2goSessionException('remote user %s is not member of X2go server group fuse' % self.session_info.username)
-
- if folder_name is None:
- self.logger('no folder name given...', log.loglevel_WARN)
- return False
-
- if type(folder_name) is not types.StringType:
- self.logger('folder name needs to be of type StringType...', log.loglevel_WARN)
- return False
-
- if not os.path.exists(folder_name):
- self.logger('local folder does not exist: %s' % folder_name, log.loglevel_WARN)
- return False
+ _control = self.control_session
+ _terminal = _control.resume(session_name=session_name, logger=self.logger, **self.terminal_params)
+ if _terminal is not None:
- self.logger('sharing local folder: %s' % folder_name, log.loglevel_INFO)
+ if _terminal.params.snd_system is not 'none':
+ _terminal.start_sound()
- _auth_rsakey = self._x2go_session_auth_rsakey
- _host_rsakey = defaults.RSAHostKey
+ if self.printing or self.share_local_folders:
+ _terminal.start_sshfs()
- _tmp_io_object = cStringIO.StringIO()
- _auth_rsakey.write_private_key(_tmp_io_object)
- _tmp_io_object.write('----BEGIN RSA IDENTITY----')
- _tmp_io_object.write('%s %s' % (_host_rsakey.get_name(),_host_rsakey.get_base64(),))
+ if self.printing:
+ _terminal.start_printing()
- _x2go_key_fname = os.path.join(os.path.dirname(self.session_info.remote_container), 'ssh', 'key.z%s' % self.session_info.agent_pid)
- _x2go_key_bundle = _tmp_io_object.getvalue()
+ if 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:
+ _terminal.share_local_folder(_folder)
- self._x2go_sftp_write(_x2go_key_fname, _x2go_key_bundle)
+ _terminal.run_command()
+ self.suspended = False
+ self.running = True
+ self.terminated = False
- if folder_type is 'disk':
+ self.terminal_session = _terminal
- cmd_line = [ 'export HOSTNAME &&',
- '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),
- ]
+ return self.running
+ __resume = resume
- elif folder_type is 'spool':
-
- cmd_line = [ 'export HOSTNAME &&',
- 'x2gomountdirs',
- 'dir',
- str(self.session_info.name),
- _CURRENT_LOCAL_USER,
- _x2go_key_fname,
- '%s__PRINT_SPOOL___REVERSESSH_PORT__%s; ' % (folder_name, self.session_info.sshfs_port),
- 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname),
- ]
+ def start(self):
+ """\
+ Start a new X2go session on the remote X2go server.
- (stdin, stdout, stderr) = self._x2go_exec_command(cmd_line)
- self.logger('x2gomountdirs output is : %s' % stdout.read().split('\n'), log.loglevel_INFO)
+ """
+ self.resume()
+ __start = start
- def run_command(self, cmd=None):
+ def suspend(self):
"""\
- Run a command in this session.
+ Suspend an X2go session.
- After L{X2goSession.start()} has been called
- one or more commands can be executed with L{X2goSession.run_command()}
- within the current X2go session.
+ You can either suspend a session that you have formerly
+ started/resumed the current X2goClient instance.
- @param cmd: Command to be run
- @type cmd: str
-
- @return: stdout.read() and stderr.read() as returned by the run command
- on the X2go server
- @rtype: tuple of str
+ Or you can suspend a non-attached session by simply
+ registering an X2go server session and then passing the
+ server-side X2go session name to this method.
"""
- if cmd in ("", None):
- if self.params.cmd is None:
- cmd = 'TERMINAL'
- else:
- cmd = self.params.cmd
+ if self.terminal_session.suspend():
- self.params.update({'cmd': cmd})
+ self.running = False
+ self.suspended = True
+ return True
- cmd_line = [ "setsid x2goruncommand",
- str(self.session_info.display),
- str(self.session_info.agent_pid),
- str(self.session_info.name),
- str(self.session_info.snd_port),
- _rewrite_cmd(self.params.cmd),
- str(self.params.snd_system),
- str(self.params.session_type),
- ">& /dev/null & exit",
- ]
-
- if self.params.snd_system is 'pulse':
- cmd_line = [ 'PULSE_CLIENTCONFIG=%s/.pulse-client.conf' % self.session_info.remote_container ] + cmd_line
-
- (stdin, stdout, stderr) = self._x2go_exec_command(cmd_line)
-
- return stdout.read(), stderr.read()
+ return False
+ __suspend = suspend
- def list_sessions(self, raw=False):
+ def terminate(self):
"""\
- List all sessions of current user on the connected server.
+ Terminate an X2go session.
- @param raw: if C{True}, the raw output of the server-side X2go command
- C{x2golistsessions} is returned.
- @type raw: bool
+ You can either terminate a session that you have formerly
+ started/resumed within the current X2goClient instance.
- @return: normally an instance of L{X2goServerSessionList} is returned. However,
- if the raw argument is set, the plain text output of the x2golistsessions
- command is returned
- @rtype: L{X2goServerSessionList} instance or str
+ Or you can terminate a non-attached session by simply
+ registering an X2go server session and then passing the
+ server-side X2go session name to this method.
"""
- (stdin, stdout, stderr) = self._x2go_exec_command("x2golistsessions")
+ if self.terminal_session.terminate():
- if raw:
- return stdout.read(), stderr.read()
+ self.running = False
+ self.suspended = False
+ self.terminated = True
+ return True
- _stdout_read = stdout.read()
- return X2goServerSessionList(_stdout_read).sessions
+ return False
+ __terminate = terminate
- def ok(self):
+ def get_profile_name(self):
"""\
- Returns C{True} if this X2go session is up and running,
- C{False} else
+ Retrieve the profile name of this registered session.
- @return: X2go session OK?
- @rtype: bool
+ @return: X2go client profile name of the session
+ @rtype: C{str}
"""
- return bool(self.session_info.name and (self.proxy_subprocess and self.proxy_subprocess.poll() is None))
+ return self.profile_name
+ __get_profile_name = get_profile_name
- def is_connected(self):
+ def get_profile_id(self):
"""\
- Returns C{True} if this X2go session is connected to the remote server (that
- is if it has a valid Paramiko Transport object).
+ Retrieve this registered session's profile id.
- @return: X2go session connected
- @rtype: bool
+ @return: the session profile's id
+ @rtype: C{str}
"""
- return self.get_transport() is not None
-
- def is_running(self):
- """\
- Returns C{True} if this X2go session is in running state ('R'),
- C{False} else.
+ return self.profile_id
+ __get_profile_id = get_profile_id
- @return: X2go session running?
- @rtype: bool
+ ###
+ ### QUERYING INFORMATION
+ ###
- """
- session_infos = self.list_sessions()
- if self.session_info.name in session_infos.keys():
- return session_infos[self.session_info.name].status == "R"
- return False
-
- def is_suspended(self):
+ def session_ok(self):
"""\
- Returns C{True} if this X2go session is in suspended state ('S'),
- C{False} else.
+ Test if this registered X2go session is
+ in a healthy state.
- @return: X2go session suspended?
- @rtype: bool
+ @return: C{True} if session is ok, C{False} otherwise
+ @rtype: C{bool}
"""
- session_infos = self.list_sessions()
- if self.session_info.name in session_infos.keys():
- return session_infos[self.session_info.name].status == "S"
+ if self.terminal_session is not None:
+ return self.terminal_session.ok()
return False
+ __session_ok = session_ok
- def has_terminated(self):
- """\
- Returns C{True} if this X2go session is not in the session list on the
- connected server, C{False} else.
- Of course, if this command is called before session startup, it will also
- return C{True}.
+ def is_connected(self):
+ """\
+ Test if this registered X2go session is connected to the
+ remote server.
- @return: X2go session has terminate?
- @rtype: bool
+ @return: C{True} if session is connected, C{False} otherwise
+ @rtype: C{bool}
"""
- session_infos = self.list_sessions()
- return self.session_info.name not in session_infos.keys()
+ return self.control_session.is_connected()
+ _is_connected = is_connected
- def associate(self, session_name):
+ def is_running(self):
"""\
- Associate L{session_name} with an available (state 'R' or 'S')
- X2go session on the server.
+ Test if this registered X2go session is up and running.
- @param session_name: X2go name of an available session.
- @type session_name: str
+ @return: C{True} if session is running, C{False} otherwise
+ @rtype: C{bool}
"""
- self.associated = False
- try:
- self.session_info = self.list_sessions()[session_name]
- if self.session_info.name:
- self.associated = True
- self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name)
- except KeyError:
- pass
- return self.associated
-
- def resume(self, **kwargs):
- """\
- Resume a running/suspended X2go session.
+ return self.is_connected() and self.terminal_session.is_running()
+ _is_running = is_running
- The L{X2goSession.resume()} method accepts any parameter
- that can be passed to the class constructor.
+ def is_suspended(self):
+ """\
+ Test if this registered X2go session is in suspended state.
- @return: True if the session could be successfully resumed
- @rtype: bool
+ @return: C{True} if session is suspended, C{False} otherwise
+ @rtype: C{bool}
"""
- if self.associated:
-
- if self.session_info.username not in self._x2go_remote_group('x2gousers'):
- raise x2go_exceptions.X2goSessionException('remote user %s is not member of X2go server group x2gousers' % self.session_info.username)
-
- self.params.update(kwargs)
-
- # if the session is still running, suspend it first
- if self.session_info.status == "R":
- self.suspend()
-
- setkbd = "0"
- if self.params.kblayout or self.params.kbtype:
- setkbd = "1"
-
- cmd_line = [ "x2goresume-session", self.session_info.name,
- self.params.geometry,
- self.params.link,
- self.params.pack,
- self.params.kblayout,
- self.params.kbtype,
- setkbd,
- ]
+ return self.is_connected() and self.terminal_session.is_suspended()
+ __is_suspended = is_suspended
- (stdin, stdout, stderr) = self._x2go_exec_command(cmd_line)
-
- self.proxy = self.proxy_class(self.session_info, self.get_transport(), logger=self.logger)
- self.proxy_subprocess = self.proxy.start()
-
- # local path may be a Windows path, so we use the path separator of the local system
- self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name)
- # remote path is always a UniX path...
- self.session_info.remote_container = '%s/%s/C-%s' % (self._x2go_remote_home,
- _X2GO_SESSION_ROOTDIR,
- self.session_info.name,
- )
- return self.ok()
-
- else:
- raise x2go_exceptions.X2goSessionException('This X2go session instance is not associated to any server-side X2go session.')
-
- def suspend(self, session_name=None):
+ def has_terminated(self):
"""\
- Suspend either this or another available X2go session on the connected
- server.
-
- If L{session_name} is given, L{X2goSession.suspend()} tries to suspend the
- corresponding session.
+ Test if this registered X2go session has terminated.
- @param session_name: X2go name of the session to be suspended
- @type session_name: str
-
- @return: True if the session could be successfully suspended
- @rtype: bool
+ @return: C{True} if session has terminated, C{False} otherwise
+ @rtype: C{bool}
"""
- _ret = False
- if session_name is not None:
-
- self.logger('suspending non-associated session: %s' % session_name, log.loglevel_DEBUG)
- (stdin, stdout, stderr) = self._x2go_exec_command("x2gosuspend-session %s" % session_name, loglevel=log.loglevel_DEBUG)
- dummy_stdout = stdout.read()
- dummy_stderr = stderr.read()
- _ret = True
+ return self.is_connected() and self.control_session.has_terminated(self.get_session_name())
+ __has_terminated = has_terminated
- elif self.associated:
-
- self.logger('suspending associated session: %s' % self.session_info, log.loglevel_DEBUG)
- (stdin, stdout, stderr) = self._x2go_exec_command("x2gosuspend-session %s" % self.session_info, loglevel=log.loglevel_DEBUG)
- dummy_stdout = stdout.read()
- dummy_stderr = stderr.read()
- self.associated = False
- _ret = True
-
- self._x2go_tidy_up()
-
- return _ret
-
- def terminate(self, session_name=None):
+ def share_local_folder(self, folder_name):
"""\
- Terminate either this or another available X2go session on the connected
- server.
-
- If L{session_name} is given, L{X2goSession.terminate()} tries to terminate the
- corresponding session.
+ Share a local folder with this registered X2go session.
- @param session_name: X2go name of the session to be terminated
- @type session_name: str
+ @param folder_name: the full path to an existing folder on the local
+ file system
+ @type folder_name: C{str}
- @return: True if the session could be successfully terminate
- @rtype: bool
+ @return: returns C{True} if the local folder has been successfully mounted within
+ this registered X2go server session
+ @rtype: C{bool}
"""
- _ret = False
- if session_name is not None:
-
- self.logger('terminating non-associated session: %s' % session_name, log.loglevel_INFO)
- (stdin, stdout, stderr) = self._x2go_exec_command("x2goterminate-session %s" % session_name, loglevel=log.loglevel_DEBUG)
- dummy_stdout = stdout.read()
- dummy_stderr = stderr.read()
- _ret = True
-
- elif self.associated:
-
- self.logger('terminating associated session: %s' % self.session_info, log.loglevel_INFO)
- (stdin, stdout, stderr) = self._x2go_exec_command("x2goterminate-session %s" % self.session_info, loglevel=log.loglevel_DEBUG)
- dummy_stdout = stdout.read()
- dummy_stderr = stderr.read()
- self.session_info.clear()
- self.associated = False
- _ret = True
+ return self.session_object.share_local_folder(folder_name=folder_name)
+ __share_local_folder = share_local_folder
- self._x2go_tidy_up()
- return _ret
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).
More information about the x2go-commits
mailing list