#!/usr/bin/env python

# This file is part of Window-Switch.
# Copyright (c) 2009-2013 Antoine Martin <antoine@nagafix.co.uk>
# Window-Switch is released under the terms of the GNU GPL v3

import os
import threading

from winswitch.objects.session import Session
from winswitch.globals import WIN32, USER_ID
from winswitch.virt.options_common import TIMEZONE, LOCALE
from winswitch.objects.common import ModifiedCallbackObject

""" This is the server representation of the sessions it manages """
class ServerSession(Session):

	SESSION_UPDATE_FIELDS = Session.SESSION_UPDATE_COMMON_FIELDS[:]

	PERSIST = Session.PERSIST_COMMON + [
									"ipp_pid", "ipp_address",
									"xauth_file", "xauth_cookie",
									"dbus_pid", "dbus_address",
									"pulse_pid", "pulse_address",
									"server_process_pid", "command_process_pid",
									"onexit_kill_pids",
									"options",
									"gst_export_plugin", "gst_export_plugin_options",
									"gst_import_plugin", "gst_import_plugin_options",
									"gst_clone_plugin", "gst_clone_plugin_options"
									]

	def __init__(self, server=None):
		Session.__init__(self)
		self.ipp_pid = -1
		self.ipp_address = ""
		self.xauth_file = ""			# XAUTHORITY for this session
		self.xauth_cookie = ""
		self.dbus_pid = -1
		self.dbus_address = ""
		self.pulse_pid = -1				# the pulse daemon's pid
		self.pulse_address = ""			# address of the pulse server, ie: unix:/tmp/pulse-NN/native
		self.orbit_dir = ""
		self.gnome_keyring_env = {}
		self.server_process_pid = -1
		self.command_process_pid = -1
		self.onexit_kill_pids = {}

		self.capture_delay = 30
		self.sound_clone_device = None
		if server:
			self.can_export_sound = server.tunnel_sink
			self.can_import_sound = server.tunnel_source
			self.can_clone_sound = False			#off by default
			self.capture_delay = server.screen_capture_delay
		self.gst_export_plugin = ""
		self.gst_export_plugin_options = {}
		self.gst_import_plugin = ""
		self.gst_import_plugin_options = {}
		self.gst_clone_plugin = ""
		self.gst_clone_plugin_options = {}

	def __str__(self):
		return	"ServerSession(%s - %s - %s)" % (self.display, self.session_type, self.status)

	def init_transients(self):
		Session.init_transients(self)
		self.default_xmodmap_keys = None			#used to backup the default xmodmap (also stored in a session file)
		self.default_xmodmap_modifiers = None		#used to backup the default xmodmap (also stored in a session file)

		self.log_watcher = None						#used by ServerUtilBase implementation to keep an eye on the server process's logfile
		self.log_watcher_cancel = None				#to cancel the above
		self.reloading = False						#used for delaying status updates when replaying the logfiles

		self.read_pending = False					#used for batching log read requests
		self.capture_pending = False				#used for batching screen capture

		#the gst pipelines for each client:
		self.client_soundout_pipelines = {}			#the soundout pipeline for each client: {user.uuid:sender_pid}
		self.client_soundin_pipelines = {}			#the soundin pipeline for each client: {user.uuid:receiver_pid}

		self.env = None								#server will replace this with the virtual env (own dbus, cups, pulseaudio, ..)
		self.read_lock = threading.Lock()			#used by server to manage access the session's logfile
		self.log_buffer = ''						#partial contents from the logfile
		self.last_log_line = ''
		self.debug_log_read = False

	def update(self, from_obj):
		#these should never change during the session's lifetime:
		assert self.display == from_obj.display
		assert self.session_type == from_obj.session_type
		assert self.requires_tunnel == self.requires_tunnel
		#password and icons are only set (via dedicated commands), never updated
		ModifiedCallbackObject.update_fields(self, ServerSession.SESSION_UPDATE_FIELDS, from_obj, False, False)
		self.set_status(from_obj.status, False)
		self.touch()

	def close(self):
		Session.close(self)
		self.slog("self=%s, killing %s" % (self, self.onexit_kill_pids))
		if self.onexit_kill_pids:
			names = sorted(self.onexit_kill_pids.keys())			#sort by names (so 00-pulseaudio comes first)
			for name in names:
				pid = self.onexit_kill_pids.get(name)
				self.sdebug("killing %s: %s" % (name, pid))
				if pid==os.getpid():
					self.slog("not killing our own pid '%s': %s" % (name, pid))
					continue
				try:
					from winswitch.util.process_util import send_kill_signal
					pid_int = int(pid)
					send_kill_signal(pid_int, may_be_missing=True)
				except Exception, e:
					se = str(e)
					if se.endswith("No such process"):
						self.slog("%s with pid %s already terminated: %s" % (name, pid, e))
					else:
						self.serror("failed to kill %s with pid %s: %s" % (name, pid, e))

	def remove_kill_pid(self, pid):
		#remove pid from kill list:
		new_kill_list = {}
		for _name,_pid in self.onexit_kill_pids.items():
			if _pid!=pid:
				new_kill_list[_name] = _pid
		self.onexit_kill_pids = new_kill_list


	def get_cwd(self):
		if USER_ID!=0 and "HOME" in os.environ:
			return	os.environ["HOME"]
		if WIN32:
			return	os.path.expanduser("~")
		return	os.path.expanduser("~"+self.user)

	#***************************************************************************************************
	# Environment: filter out
	def get_filtered_env(self):
		"""
		Just keep the variables which are safe to pass to the process we start in the session,
		not keeping things which are specific to the shell this server was started from.
		(things like DISPLAY, DBUS_* etc should *NOT* be passed down!)
		On win32, we just don't filter.
		"""
		if WIN32:
			env = os.environ.copy()
		else:
			env = {"PWD" : self.get_cwd()}
			safe_keys = ["PATH", "USERNAME", "USER", "HOME", "OSTYPE", "HOSTTYPE", "MACHTYPE",
							"TERM", "IFS", "HOSTNAME", "LANG", "SHELL", "XAUTHORITY"]
			for key in safe_keys:
				if key in os.environ:
					env[key] = os.environ[key]
		return	env

	def get_env(self):
		"""
		Combines the environment variables from the current process as sanitised by get_filtered_env(),
		the session specific variables (DISPLAY,print,sound,dbus) from get_extra_env,
		and any overrides/extras specified in extra.
		"""
		env = self.get_filtered_env()
		for k,v in self.get_extra_env().items():
			env[k] = v
		return	env

	def get_extra_env(self):
		"""
		Returns the dict of environment variables for the session.
		Allows for our own dbus/print/sound servers to be used.
		"""
		if self.shadowed_display:
			disp = self.shadowed_display
		else:
			disp = self.display
		return	self.do_get_extra_env(disp, self.ipp_port, self.pulse_address, self.dbus_address, self.xauth_file,
									self.orbit_dir, self.gnome_keyring_env, self.options.get(TIMEZONE), self.options.get(LOCALE))

	def do_get_extra_env(self, display, ipp_port, pulse_address, dbus_address, xauth_file, orbit_dir, gnome_keyring_env, tz_env, locale_env):
		extra_env = {}
		if display:
			extra_env["DISPLAY"] = display
		if not WIN32:
			if ipp_port>0:
				extra_env["CUPS_SERVER"] = "127.0.0.1:%d" % ipp_port
			if pulse_address:
				extra_env["PULSE_SERVER"] = pulse_address
				extra_env["ALSA_PCM_NAME"] = "pulse"
			if dbus_address:
				extra_env["DBUS_SESSION_BUS_ADDRESS"] = dbus_address
			if xauth_file:
				extra_env["XAUTHORITY"] = xauth_file
			if orbit_dir:
				extra_env["ORBIT_SOCKETDIR"] = orbit_dir
			if gnome_keyring_env:
				for k,v in gnome_keyring_env.items():
					extra_env[k] = v
			if tz_env:
				extra_env["TZ"] = tz_env
			if locale_env:
				extra_env["LANG"] = locale_env
		#self.sdebug("=%s" % extra_env, display, ipp_port, pulse_address, dbus_address, xauth_file, orbit_dir, gnome_keyring_env)
		return	extra_env
