#!/usr/bin/env python
#############################################################################
#
#  Linux Desktop Testing Project http://ldtp.freedesktop.org
# 
#  Author:
#     A. Nagappan <nagappan@gmail.com>
# 
#  Copyright 2004 - 2007 Novell, Inc.
# 
#  This library is free software; you can redistribute it and/or
#  modify it under the terms of the GNU Library General Public
#  License as published by the Free Software Foundation; either
#  version 2 of the License, or (at your option) any later version.
# 
#  This library 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
#  Library General Public License for more details.
# 
#  You should have received a copy of the GNU Library General Public
#  License along with this library; if not, write to the
#  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
#  Boston, MA 02110, USA.
#
#############################################################################

__author__ = "Nagappan A"
__maintainer__ = "Nagappan A"
__version__ = "0.8.0"

import re
import os
import sys
import time
import signal
import threading
import traceback

# Let us not register our application under at-spi application list
os.environ ['GTK_MODULES'] = ''

try:
    import pyatspi as atspi, Accessibility
except ImportError:
    import atspi
    import Accessibility

from ldtplib import *
from ldtplib.ldtplibutils import *
from ldtplib.ldtpcommon import *

def __shutdownAndExit (signum, frame):
    global ldtpDebug
    try:
        atspi.Registry ().stop ()
    except RuntimeError:
        pass
    if ldtpDebug:
        print "Goodbye."

class SearchObjInfo:
    def __init__ (self, key = None, window = False, pattern = None):
        self.key     = key
        self.window  = window
        self.pattern = pattern

class UnknLabelProperty:
    def __init__ (self, appmapType = True, childIndex = -1, parentName = None, objName = None):
        self.appmapType = appmapType
        self.childIndex = childIndex
        self.parentName = parentName
        self.objName    = objName

class packet:
	def __init__ (self):
		pass
	def generatenotificationxml (self, windowName, objectName, objectType, eventType,
				     timeElapsed, key = None, data = None, detail1 = None,
				     detail2 = None):
		"""This Function will generate some information to LDTP packet
		INPUT: windowname, objectname, objecttype, eventtype[, key[, data]]

		OUTPUT: returns a string in the LDTP packet (XML lib2) format"""
		_xml = '<?xml version=\"1.0\"?>' ## should this header be there in the packets?
		_xml += '<NOTIFICATION>'
		# Fill action name
		_xml = _xml + '<WINDOWNAME>' + saxutils.escape (windowName) + '</WINDOWNAME>'
		_xml = _xml + '<OBJECTNAME>' + saxutils.escape (objectName) + '</OBJECTNAME>'
		_xml = _xml + '<OBJECTTYPE>' + objectType + '</OBJECTTYPE>'
		_xml = _xml + '<EVENTTYPE>' + eventType + '</EVENTTYPE>'
		_xml = _xml + '<TIMEELAPSED>' + str (timeElapsed) + '</TIMEELAPSED>'
		if key:
			_xml = _xml + '<KEY>' + saxutils.escape (key) + '</KEY>'
		if data:
			_xml = _xml + '<DATA>' + saxutils.escape (data) + '</DATA>'
		if detail1:
			_xml = _xml + '<DETAIL1>' + str (detail1) + '</DETAIL1>'
		if detail2:
			_xml = _xml + '<DETAIL2>' + str (detail2) + '</DETAIL2>'
		_xml += '</NOTIFICATION>'
		return _xml

	def generatekeyboardxml (self, text):
		_xml = '<?xml version=\"1.0\"?>' ## should this header be there in the packets?
		_xml += '<KEYBOARD>'
		# Fill action name
		_xml = _xml + '<DATA>' + saxutils.escape (text) + '</DATA>'
		_xml += '</KEYBOARD>'
		return _xml

	def generateresponsexml (self, requestId, status, data = None):
		"""This Function will generate some information to LDTP packet
		INPUT: windowname, objectname, objecttype, eventtype[, key[, data]]

		OUTPUT: returns a string in the LDTP packet (XML lib2) format"""
	
		_xml = '<?xml version=\"1.0\"?>' ## should this header be there in the packets?
		_xml += '<RESPONSE>'
		# Fill action name
		_xml = _xml + '<ID>' + requestId + '</ID>'
		_xml = _xml + '<STATUS><CODE>' + str (status [0]) + '</CODE>'
		if status [0] != 0:
			_xml = _xml + '<MESSAGE>' + status [1] + '</MESSAGE>'
		_xml = _xml + '</STATUS>'
		if data:
			_xml = _xml + '<DATA>' + saxutils.escape (data) + '</DATA>'
		_xml += '</RESPONSE>'
		return _xml

	def parsexml (self, xmlpacket):
		"""Returns the value obtained from the server's return LDTP packet"""
		global ldtpDebug
		_requestCommand = 0
		_name           = None
		_requestId      = None
		_requestObj     = None

		try:
			dom = parseString (xmlpacket)
			try:
				_requestObj  = dom.getElementsByTagName ('REQUEST')[0]
			except IndexError:
				if ldtpDebug:
					print 'Invalid request'
				return None
			try:
				_requestCommand = int (getText (_requestObj.getElementsByTagName ('COMMAND') [0].childNodes))
			except ValueError:
				if ldtpDebug:
					print 'Invalid Command'
				return None
			except IndexError:
				if ldtpDebug:
					print 'Invalid Command'
				return None
			try:
				_name = getText (_requestObj.getElementsByTagName ('NAME') [0].childNodes)
			except ValueError:
				pass
			except IndexError:
				# Data tag may not be present
				pass
			try:
				_requestId  = getText (_requestObj.getElementsByTagName ('ID') [0].childNodes)
			except IndexError:
				# On notification _requestId will be empty
				pass
		except ExpatError, msg:
			if msg.code == xml.parsers.expat.errors.XML_ERROR_NO_ELEMENTS:
				return None
			if xml.parsers.expat.ErrorString (msg.code) == xml.parsers.expat.errors.XML_ERROR_NO_ELEMENTS:
				return None
		# Return all the respective values, let the calling function decide what to do with the values
		return _requestId, _requestCommand, _name

	# Send given packet to server
	def sendpacket (self, msg):
		global ldtpDebug, clientFd
		if clientFd is None:
			return
		flag = False
		try:
			timedelay = os.getenv ('LDTP_DELAY_CMD')
			sendLck.acquire ()
			if timedelay != None:
				try:
					# Delay each command by 'timedelay' seconds
					time.sleep (int (timedelay))
				except IndexError:
					# Default to 5 seconds delay if LDTP_DELAY_CMD
					# env variable is set
					time.sleep (5)
			flag = True
			# Encode the message in UTF-8 so we don't break on extended
			# characters in the application GUIs
			buf = msg.encode ('utf8')

			_length =  len (buf)
			# Pack length (integer value) in network byte order
			format = '!i%ds' % _length
			_msg = struct.pack (format, _length, buf)
			# Send message
			clientFd.send (_msg)
			ldtpDebug = os.getenv ('LDTP_DEBUG')
			if ldtpDebug != None and ldtpDebug == '2':
				print 'Send packet', buf
			sendLck.release ()
		except socket.error, msg:
			if flag == True:
				# Reason for using the flag:
				# 'Do not call this method when the lock is unlocked.'
				sendLck.release ()
			#raise LdtpExecutionError ('Client aborted')
			return

	def recvpacket (self, sockfd = None):
		global ldtpDebug, clientFd
		flag = False
		try:
			recvLck.acquire ()
			flag = True
			client = None
			# Get client socket fd based on thread id
			if sockfd == None:
				client = clientFd
			else:
				client = sockfd
			_responsePacket = None
			client.settimeout (5.0)
			# Hardcoded 4 bytes, as the server sends 4 bytes as packet length
			data = client.recv (4, 0)
			if data == '' or data == None:
				if flag == True:
					# Reason for using the flag:
					# 'Do not call this method when the lock is unlocked.'
					recvLck.release ()
				return None
			_packetSize, = struct.unpack('!i', data)
			if ldtpDebug != None and ldtpDebug == '2':
				print 'Received packet size', _packetSize
			# MSG_PEEK
			# This flag causes the receive operation to return data from the beginning
			# of the receive queue without removing that data from  the  queue.
			# Thus, a subsequent receive call will return the same data.

			_responsePacket = client.recv (_packetSize, 0)
			_pattern = re.compile ('\<\?xml')
			_searchObj = re.search (_pattern, _responsePacket)
			_finalPacket = _responsePacket[_searchObj.start () :]
			_responsePacket = _finalPacket
			recvLck.release ()
			if ldtpDebug != None and ldtpDebug == '2':
				print 'Received response Packet', _responsePacket
			return _responsePacket
		except struct.error, msg:
			if flag == True:
				# Reason for using the flag:
				# 'Do not call this method when the lock is unlocked.'
				recvLck.release ()
			raise LdtpExecutionError ('Invalid packet length ' + str (msg))
		except AttributeError, msg:
			if flag == True:
				# Reason for using the flag:
				# 'Do not call this method when the lock is unlocked.'
				recvLck.release ()
			raise LdtpExecutionError ('Error while receiving packet ' + str (msg))
		except socket.timeout:
			if flag == True:
				# Reason for using the flag:
				# 'Do not call this method when the lock is unlocked.'
				recvLck.release ()
			if ldtpDebug != None and ldtpDebug == '2':
				print 'Timeout'
			return ''
		except socket.error, msg:
			if flag == True:
				# Reason for using the flag:
				# 'Do not call this method when the lock is unlocked.'
				recvLck.release ()
			raise LdtpExecutionError ('Error while receiving packet ' + str (msg))
		except MemoryError, msg:
			if flag == True:
				# Reason for using the flag:
				# 'Do not call this method when the lock is unlocked.'
				recvLck.release ()
			raise LdtpExecutionError ('Error while receiving packet ' + str (msg))
		except:
			if flag == True:
				# Reason for using the flag:
				# 'Do not call this method when the lock is unlocked.'
				recvLck.release ()
			raise LdtpExecutionError ('Error while receiving packet')

class PollServer (threading.Thread):
	def __init__ (self):
		threading.Thread.__init__ (self)
		self._events = None
	def run (self):
		try:
			self.start_polling_server ()
		except:
			try:
				raise LdtpExecutionError (str (traceback.print_exc ()))
			except AttributeError:
				pass
			except:
				pass
	def start_polling_server (self):
		self._serverDisconnected = False
		global _serverpoll, mainsock, clientFd
		_serverpoll = select.poll ()
		_serverpoll.register (mainsock, select.POLLIN)

		while True:
			try:
				self._events = _serverpoll.poll ()
			except socket.error, msg:
				break
			except:
				_serverpoll.unregister (mainsock)
				mainsock = None
				sys.exit ()
			try:
				if self._events == None:
					break
				for i in range (0, len (self._events)):
					if (self._events [i][1] & select.POLLIN or self._events [i][1] & select.POLLPRI):
						fd = socket.fromfd (self._events[i][0],
								    socket.AF_UNIX,
								    socket.SOCK_STREAM)
						if self._events [i][0] == mainsock.fileno ():
							clientFd, addr = mainsock.accept ()
							_serverpoll.register (clientFd, select.POLLIN)
							continue
						try:
							if self.handlePacket () == None:
								_serverpoll.unregister (self._events[i][0])
								continue
						except LdtpExecutionError, msg:
							self._serverDisconnected = True
							break
						except SystemExit:
							break
						except:
							print traceback.print_exc ()
					elif (self._events[i][1] & select.POLLNVAL):
						# Unregister newly created socket from polling once its completed
						_serverpoll.unregister (self._events[i][0])
					else:
						self._serverDisconnected = True
						break
				if self._serverDisconnected == True:
					break
			# Checking this exception due to bug # 333090 comment # 6
			except TypeError:
				_serverpoll.unregister (mainsock)
				sys.exit ()
	def handlePacket (self):
		global pckt, recEngine, ldtpDebug
		try:
			self._packet = pckt.recvpacket ()
			if ldtpDebug:
				print self._packet
		except KeyboardInterrupt:
			return None
		except LdtpExecutionError:
			return ''
		try:
			if self._packet == None:
				return None
			if self._packet != '':
				_requestId, _commandCode, _name = pckt.parsexml (self._packet)
				if _commandCode == command.INVALID:
					return ''
				if _commandCode == command.STOP:
					_status = {}
					_status [0] = 0
					_status [1] = 'Success'
					_xml = pckt.generateresponsexml (_requestId, _status)
					pckt.sendpacket (_xml)
					stop ()
					thread.exit ()
				if _commandCode == command.WINDOWEXIST:
					_status = {}
					_status [0] = 0
					_status [1] = 'Success'
					_xml = ''
					if ldtplib.ldtprecorder.recorder.doesWindowExist (_name):
						_xml = pckt.generateresponsexml (_requestId, _status)
					else:
						_status [0] = 1
						_status [1] = 'Window does not exist'
						_xml = pckt.generateresponsexml (_requestId, _status)
					pckt.sendpacket (_xml)
					return ''
				if _commandCode == command.GETWINDOWNAME:
					if ldtpDebug:
						print 'get window name'
					return ''
				if _commandCode == command.GETOBJECTNAME:
					if ldtpDebug:
						print 'get object name'
					_status = {}
					_status [0] = 0
					_status [1] = 'Success'
					_xml = ''
					_objName = ldtplib.ldtprecorder.recorder.getObjectName (_name)
					if _objName != '':
						_xml = pckt.generateresponsexml (_requestId, _status, _objName)
					else:
						_status [0] = 1
						_status [1] = 'Object does not exist'
						_xml = pckt.generateresponsexml (_requestId, _status)
					pckt.sendpacket (_xml)
					return ''
		except TypeError:
			return ''
		except LdtpExecutionError:
			return ''
		except KeyboardInterrupt:
			return None
		return ''
	def shutdown (self):
		global _serverpoll, mainsock
		if mainsock:
			mainsock.close ()
			mainsock = None

class ldtpengine:
    def __init__ (self, pckt = None):
        self.windowName = None
        self.windowType = None
        self.objectName = None
        self.objectType = None
        self.objectInst = objInst ()
        self.registry = atspi.Registry ()
        self.appmap = appmap (self.objectInst, self.registry)
        self.hashmap = None

    def start (self):
        try:
            self.registry.start ()
        except:
            if ldtpDebug and ldtpDebug == '3':
                print traceback.print_exc ()
            pass

    def windowListenerCallback (self, event):
        role = event.source.getRole ()
        if role == Accessibility.ROLE_FRAME or role == Accessibility.ROLE_DIALOG or \
               role == Accessibility.ROLE_ALERT  or role == Accessibility.ROLE_WINDOW:
            if self.textContent != '':
                _xml = self.pckt.generatekeyboardxml (self.textContent)
                self.pckt.sendpacket (_xml)
                self.textContent = ''
            self.windowName = self.getWinNameAppmapFormat (event.source.name, role)
            self.windowType = event.source.getRoleName ()
            global ldtpDebug
            if ldtpDebug and ldtpDebug == '2':
                print '***WINDOW', event.type, event.source.name, \
                      event.detail1, event.detail2,  \
                      event.any_data
            parent = event.source.parent
            name = None
            if parent is not None:
                name = parent.name
                parent.unref ()
            try:
                self.objectInst.reset ()
                self.remap.dumpTree (event.source, name)
                if self.pckt and (event.type == 'window:create' or event.type == 'window:destroy'):
                    _xml = self.pckt.generatenotificationxml (self.windowName, \
                                                              '', \
                                                              self.windowType, event.type, \
                                                              self.timeElapsed ())
                    self.pckt.sendpacket (_xml)
            except:
                print traceback.print_exc ()

    def nameChangeListenerCallback (self, event):
        global ldtpDebug
        _role = event.source.getRole ()
        if _role == Accessibility.ROLE_FRAME or _role == Accessibility.ROLE_DIALOG or _role == Accessibility.ROLE_ALERT:
            self.windowName = self.getWinNameAppmapFormat (event.source.name, _role)
            if ldtpDebug and ldtpDebug == '2':
                print 'NAMECHANGE', event.type, event.source.name, \
                      event.detail1, event.detail2,  \
                      event.any_data
            _parent = event.source.parent
            _name = None
            if _parent is not None:
                _name = _parent.name
                _parent.unref ()
            try:
                self.objectInst.reset ()
                self.remap.dumpTree (event.source, _name)
            except:
                print traceback.print_exc ()

    def registerWindowListener (self):
        eventTypes = [
            "window:activate",
            "window:create",
            "window:destroy",
            ]
        for eventType in eventTypes:
            self.registry.registerEventListener (self.windowListenerCallback, eventType)

    def registerNameChangeListener (self):
        eventTypes = [
            "object:property-change:accessible-name"
            ]
        for eventType in eventTypes:
            self.registry.registerEventListener (self.nameChangeListenerCallback, eventType)

# To start the server, just call this function once
def start (pckt = None):
    global ldtpDebug, ldtpengine
    engine = ldtpengine (pckt)
    engine.registerWindowListener ()
    engine.registerNameChangeListener ()

    try:
        engine.start ()
    except KeyboardInterrupt:
        stop ()
        raise KeyboardInterrupt ()
    except:
        print traceback.print_exc ()
        stop ()

# To stop server, just call this function once
def stop ():
    __shutdownAndExit (None, None)

server = None
ldtpDebug = os.getenv ('LDTP_DEBUG') # Enable debugging

# If this file is directly called like
# $ python server.py
if __name__ == "__main__":
    signal.signal (signal.SIGINT, __shutdownAndExit)
    signal.signal (signal.SIGQUIT, __shutdownAndExit)
    start ()
