#!/usr/bin/env python

import sys, os
import subprocess
import signal

from winswitch.util.simple_logger import Logger, set_log_to_tty, set_log_to_file, set_log_filename, get_log_file
DAEMON = "--daemon" in sys.argv
PRINT_PID = "--print-pid" in sys.argv
LOGFILE = None
lx = None
for x in sys.argv:
	if lx and lx=="--log-file":
		LOGFILE = x
	lx = x
if LOGFILE:
	set_log_to_file(True)
	set_log_filename(LOGFILE)
else:
	set_log_to_file(False)
if PRINT_PID:
	set_log_to_tty(False)
SIGNAL_ONLY = False

logger = Logger("delayed_start")
logger.sdebug("import session")
from winswitch.objects.session import Session
logger.sdebug("import server_session")
from winswitch.objects.server_session import ServerSession
logger.sdebug("import config")
from winswitch.util.config import load_session_from_file, modify_object_properties
logger.sdebug("import daemonize")
from winswitch.util.daemonize import daemonize
logger.sdebug("import main_loop")
from winswitch.util.main_loop import loop_init, loop_run, loop_exit


class DelayedStart:
	"""
	This can be used by some session types (xpra, vnc) to pre-launch a display.
	This process then waits for the session file to be updated (when the preload flag is set to False)
	and then launches the real application.

	This saves time at session startup as the X server is already running (as well as other utilities like dbus, pulseaudio, etc).
	In the meantime it just sits there idle, just consuming a little bit of memory which is swapped out	to disk if needed.
	It uses gio.File to only wake up and check for session status changes when the file is modified on disk (by the server).
	"""
	def __init__(self, display, session_file):
		Logger(self, log_colour=Logger.HIGHLIGHTED_BROWN)
		self.session = None
		self.pending = True
		self.started = False
		self.session_file = None
		self.display = None
		self.mainloop_started = None

		self.slog("argv=%s" % str(sys.argv), display)
		if not display:
			self.abort("display not set!")
		displayNo = -1
		try:
			displayNo = int(display[1:])
		except:
			pass
		if display[0]!=':' or displayNo<0:
			self.abort("invalid display: %s" % display)
		self.session_file = session_file
		self.display = display
		self.session = load_session_from_file(self.session_file, False, ServerSession)
		if not self.session:
			self.abort("DelayedStart(%s) could not load file '%s'" % (display, self.session_file))
		if self.session.display!=self.display:
			self.abort("DelayedStart(%s) session file '%s' is for display %s and not %s" % (display, self.session_file, self.session.display, self.display))

	def abort(self, msg):
		"""
		Print error message and exit
		"""
		self.serror(None, msg)
		self.stop(1)

	def wait(self):
		"""
		Wait for the session_file to be modified, fire modified() when it is.
		"""
		self.check_status()
		signal.signal(signal.SIGHUP, self.sighup)
		if not SIGNAL_ONLY:
			from winswitch.util.common import CAN_USE_GIO
			if CAN_USE_GIO:
				self.gio_wait()
			else:
				self.poll_wait()
		loop_init(False)
		self.mainloop_started = True
		loop_run()
		self.slog("loop_run() exited")

	def gio_wait(self):
		import gio
		gfile = gio.File(self.session_file)
		mfile = gfile.monitor_file()
		mfile.connect("changed", self.modified)

	def poll_wait(self):
		from winswitch.util.main_loop import callLater
		callLater(1, self.modified)

	def stop(self, exit_code=0):
		self.serror(None, exit_code)
		if self.mainloop_started:
			self.mainloop_started = False
			loop_exit()
		if self.session:
			self.update_status(Session.STATUS_CLOSED)
		else:
			self.slog("no session", exit_code)
		sys.exit(exit_code)

	def sighup(self, *args):
		self.slog(None)
		self.modified()

	def modified(self, *args):
		self.slog("file %s modified" % self.session_file, args)
		self.check_status()
		from winswitch.util.common import CAN_USE_GIO
		if not CAN_USE_GIO:
			self.poll_wait()

	def check_status(self):
		self.session = load_session_from_file(self.session_file, False, ServerSession)
		if not self.session:
			self.abort("file %s has gone missing!" % self.session_file)
		return self.do_check_status()

	def do_check_status(self):
		self.slog("session data = %s" % (self.session))
		if not self.session:
			self.abort("invalid session, no data found for display %s" % self.display)
		self.slog("session status = %s" % self.session.status)
		if self.pending:
			#check status
			if self.session.status in [Session.STATUS_CLOSED, Session.STATUS_SUSPENDED, Session.STATUS_SUSPENDING]:
				self.pending = False
				self.abort("invalid state: %s - terminating" % self.session.status)

		if self.session.preload:
			if self.started:
				self.slog("session already started, ignoring: '%s'" % self.session.status)
			else:
				self.slog("session still pending for display %s " % self.display)
			return	False

		#FIXME: should set status to started?
		self.do_start()
		return	True

	def update_status(self, new_status):
		"""
		Updates the session status and update the session file on disk (changing just the status).
		"""
		self.session.set_status(new_status)
		modify_object_properties(self.session_file, self.session, ["status"])


	def do_start(self):
		self.slog("session=%s" % self.session)
		self.pending = False

		self.slog("command=%s" % self.session.command)
		cmd = self.session.command
		args = []
		space = cmd.find(" ")
		if space > 0:
			cmd = self.session.command[:space]
			args_str = self.session.command[space:]
			args = args_str.split(" ")
		if cmd.startswith("delayed_start"):
			self.abort("invalid command: %s" % cmd)

		if LOGFILE:
			stdout=get_log_file()
		else:
			stdout=subprocess.PIPE
		stderr=stdout
		self.slog("exec cmd=%s, args=%s, stdout=%s, stderr=%s, env=%s" % (cmd, args, stdout, stderr, os.environ))
		process = subprocess.Popen(self.session.command, shell=True, stdin=None, stdout=stdout, stderr=stderr, close_fds=True)
		self.started = True
		if not process or process.poll():
			self.update_status(Session.STATUS_CLOSED)
		else:
			self.update_status(Session.STATUS_AVAILABLE)
		self.slog("process=%s" % process)
		if self.session.status == Session.STATUS_AVAILABLE:
			try:
				retcode = process.wait()
				self.slog("type=%s, retcode=%s" % (self.session.session_type, retcode))
			except Exception, e:
				#ie: KeyboardInterrupt
				self.serr(None, e)
				retcode = 1
			self.update_status(Session.STATUS_CLOSED)
			sys.stdout.flush()
			sys.stderr.flush()

			#now tell the server about it:
			from winswitch.util.file_io import get_local_server_socket
			from winswitch.net.commands import CLOSE_SESSION
			from winswitch.util.format_util import format_message
			from winswitch.net.unix_socket_util import unix_socket_send
			message = "%s\n" % format_message(CLOSE_SESSION, [self.session.actor, self.session.ID])
			path = get_local_server_socket()
			unix_socket_send(path, message)
		else:
			retcode = 1
		self.stop(retcode)




def main():
	if "--disable-gio" in sys.argv:
		#this is already parsed by common.py, ignore it here
		sys.argv = [x for x in sys.argv if x!="--disable-gio"]
	if len(sys.argv)<2 or len(sys.argv)>6:
		logger.serror("illegal number of arguments")
		logger.serror("usage:")
		logger.serror("%s /path/to/session_file [--daemon [--print-pid]] [--log-file /path/to/logfile] [--disable-gio]" % sys.argv[0])
		sys.exit(1)
	DISPLAY = os.environ["DISPLAY"]
	logger.sdebug("DISPLAY=%s" % DISPLAY)
	ds = DelayedStart(DISPLAY, sys.argv[1])
	if DAEMON:
		daemonize(PRINT_PID)
		if PRINT_PID:
			ds.slog("printed pid=%s to stdout" % os.getpid())
	ds.do_check_status()
	ds.wait()

if __name__ == "__main__":
	main()
