#!/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

#@PydevCodeAnalysisIgnore

import traceback
from threading import Thread

import pywintypes, win32con, winerror		#@UnresolvedImport
from win32event import *
from win32file import *
from win32pipe import *
from win32api import *
from ntsecuritycon import *

# Our imports
from winswitch.util.simple_logger import Logger
from winswitch.net.local_common import PIPE_NAME, COMMAND_EXIT


def ApplyIgnoreError(fn, args):
	try:
		return apply(fn, args)
	except: # Ignore win32api errors.
		return None

class NamedPipeListener(Thread):
	def __init__(self, line_handler_factory):
		Logger(self, log_colour=Logger.YELLOW)
		self.slog(None, line_handler_factory)
		Thread.__init__(self, name="NamedPipeListener")
		self.line_handler_factory = line_handler_factory

		self.hWaitStop = CreateEvent(None, 0, 0, None)
		self.overlapped = pywintypes.OVERLAPPED()
		self.overlapped.hEvent = CreateEvent(None,0,0,None)
		self.thread_handles = []
		self.exit_loop = False
		self.terminated = False

	def stop(self):
		self.slog()
		if self.exit_loop:
			return
		self.exit_loop = True

		#somehow fails to stop cleanly, try to force it if the socket is still listening:
		from winswitch.net.local_namedpipe_client import LocalNamedPipeClient
		client = LocalNamedPipeClient()
		self.sdebug("stopping socket listener using client=%s" % client)
		if client.test_socket(COMMAND_EXIT):
			self.sdebug("successfully sent '%s' to existing instance" % COMMAND_EXIT)

		self.hWaitStop.close()		#this should do it... (but using the client seems to work better..)

	def CreatePipeSecurityObject(self):
		self.slog()
		# Create a security object giving World read/write access,
		# but only "Owner" modify access.
		sa = pywintypes.SECURITY_ATTRIBUTES()
		sidEveryone = pywintypes.SID()
		sidEveryone.Initialize(SECURITY_WORLD_SID_AUTHORITY,1)
		sidEveryone.SetSubAuthority(0, SECURITY_WORLD_RID)
		sidCreator = pywintypes.SID()
		sidCreator.Initialize(SECURITY_CREATOR_SID_AUTHORITY,1)
		sidCreator.SetSubAuthority(0, SECURITY_CREATOR_OWNER_RID)

		acl = pywintypes.ACL()
		acl.AddAccessAllowedAce(FILE_GENERIC_READ|FILE_GENERIC_WRITE, sidEveryone)
		acl.AddAccessAllowedAce(FILE_ALL_ACCESS, sidCreator)

		sa.SetSecurityDescriptorDacl(1, acl, 0)
		return sa

	def run(self):
		try:
			try:
				self.do_run()
			except Exception, e:
				self.serr(None, e)
		finally:
			self.terminated = True

	def do_run(self):
		self.log()
		num_connections = 0
		while not self.exit_loop:
			pipeHandle = CreateNamedPipe(PIPE_NAME,
					PIPE_ACCESS_DUPLEX| FILE_FLAG_OVERLAPPED,
					PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE,
					PIPE_UNLIMITED_INSTANCES,	   # max instances
					0, 0, 6000,
					self.CreatePipeSecurityObject())
			if self.exit_loop:
				break
			try:
				hr = ConnectNamedPipe(pipeHandle, self.overlapped)
			except error, details:
				self.serror("error connecting pipe: %s" % details)
				CloseHandle(pipeHandle)
				break
			self.slog("connected to pipe")
			if self.exit_loop:
				break
			if hr==winerror.ERROR_PIPE_CONNECTED:
				# Client is already connected - signal event
				SetEvent(self.overlapped.hEvent)
			rc = WaitForMultipleObjects((self.hWaitStop, self.overlapped.hEvent), 0, INFINITE)
			self.slog("wait ended with rc=%s, exit_loop=%s" % (rc, self.exit_loop))
			if rc==WAIT_OBJECT_0 or self.exit_loop:
				# Stop event
				break
			else:
				# Pipe event - spawn thread to deal with it.
				t = ProcessClient(self, pipeHandle)
				t.start()
				num_connections = num_connections + 1

		# Sleep to ensure that any new threads are in the list, and then
		# wait for all current threads to finish.
		# What is a better way?
		self.sdebug("waiting for other threads")
		Sleep(500)
		while self.thread_handles:
			self.slog("waiting for %d threads to finish..." % len(self.thread_handles))
			WaitForMultipleObjects(self.thread_handles, 1, 3000)
		self.slog("all threads have terminated")

	def add_thread(self, th):
		self.thread_handles.append(th)
	def remove_thread(self, th):
		self.thread_handles.append(th)


# Thread spawned for each client
class	ProcessClient(Thread):
	def __init__(self, listener, pipeHandle):
		Logger(self, log_colour=Logger.YELLOW)
		self.slog(None, listener, pipeHandle)
		self.pipeHandle = pipeHandle
		self.listener = listener
		self.handler = self.listener.line_handler_factory(self.sendLine, listener.stop)
		Thread.__init__(self, name="ProcessClient")

	def run(self):
		try:
			procHandle = GetCurrentProcess()
			self.thread_handle = DuplicateHandle(procHandle, GetCurrentThread(), procHandle, 0, 0, win32con.DUPLICATE_SAME_ACCESS)
			try:
				self.listener.add_thread(self.thread_handle)
				try:
					return self.DoProcessClient()
				except error, e:
					self.exc(e)
			finally:
				self.listener.remove_thread(self.thread_handle)
		except error, e:
			self.exc(e)

	def DoProcessClient(self):
		self.slog()
		try:
			try:
				# Create a loop, reading large data.  If we knew the data stream
				# was small, a simple ReadFile would do.
				d = ''
				hr = winerror.ERROR_MORE_DATA
				while hr==winerror.ERROR_MORE_DATA:
					hr, thisd = ReadFile(self.pipeHandle, 256)
					d = d + thisd
				self.slog(None, "read '%s'" % d)
				self.handler.handle(d)
				ok = 1
			except error:
				# Client disconnection - do nothing
				ok = 0

			self.sdebug("ok=%s" % ok)
			# A secure service would handle (and ignore!) errors writing to the
			# pipe, but for the sake of this demo we dont (if only to see what errors
			# we can get when our clients break at strange times :-)
			#if ok:
			#	self.sendLine("%s (on thread %d) sent me %s" % (GetNamedPipeHandleState(self.pipeHandle)[4], self.thread_handle, d))
		finally:
			ApplyIgnoreError( DisconnectNamedPipe, (self.pipeHandle,) )
			ApplyIgnoreError( CloseHandle, (self.pipeHandle,) )

	def sendLine(self, txt):
		WriteFile(self.pipeHandle, "%s\n" % txt)




if __name__ == "__main__":
	from winswitch.util.simple_logger import set_log_to_file, set_log_to_tty
	set_log_to_file(False)
	set_log_to_tty(True)
	try :
		listener = NamedPipeListener(None)
		listener.start()
	except Exception, e:
		print('*** Caught except ion: %s: %s' % (e.__cl, e))
		traceback.print_exc()
		if listener:
			listener.stop()
