#!/usr/bin/env python 
#############################################################################
#
#  Linux Desktop Testing Project http://ldtp.freedesktop.org
# 
#  Author:
#      S.Theyagarajan (theyaga@gmail.com)   
#      K.Harishankaran (sp2hari@gmail.com)
# 
#  Copyright 2004 - 2006 Novell, Inc.
# 
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU Lesser General Public
#  License as published by the Free Software Foundation; either
#  version 2 of the License, or (at your option) any later version.
# 
#  This program 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
#  Lesser General Public License for more details.
# 
#  You should have received a copy of the GNU Lesser 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.
#
#Example Usage python record.py start gedit  all
#Example Usage pythin record.py stop <filepath>
#The filename.py will be the resultant Python script generated which can be used to play back 
#
#############################################################################

from optparse import OptionParser

#Defining the command line arguments
usage = "Usage:\n %prog -c start [-a application] [-p prefs] [-t]\n %prog -c stop -f file"
parser = OptionParser (usage)
parser.add_option ("-c", "--command", dest="command",
                  help="Command to start or stop the server", metavar="start/stop")
parser.add_option ("-a", "--application", dest="application", default="all", 
                  help="Application to be recorded", metavar="all/application")
parser.add_option ("-p", "--prefs", dest="prefs",
                  help="Preferences while recording ", metavar="all/mouse/keybrd", default="all")
parser.add_option ("-f", "--file", dest="filename", metavar="filepath",
                  help="File where the recorded script is written")
parser.add_option ("-t","--timer", action="store_true",
                  help="Record time delays also")
(options, args) = parser.parse_args ()

#Checking for the command line arguments
if options.command == None:
	parser.error ("Command start or stop must be provided")
if options.command != "start" and options.command != "stop":
	parser.error ("Invalid command . Please give start or stop")
if options.command == "stop" and options.filename == None :
	parser.error ("File name must be given for stop command")

#Assigning the variables to be used in the file
if options.command == "start" :
	actionName = 'startrecord'
elif options.command == "stop" :
	actionName = 'stoprecord'

filename        = options.filename
arguments       = options.prefs
applicationName = options.application

from ldtpcodegen import *
from xml.dom.minidom import *
from xml.parsers.expat import ExpatError
import socket, os, sys, struct, time, threading, re, random, thread, traceback

ldtpDebug = os.getenv ('LDTP_DEBUG')

class ConnectionLost (Exception):
	def __init__(self, value):
		self.value = value
	def __str__(self):
		return repr (self.value)

# generate XML content
def generatexml (actionName, _requestId, application = None, argument = None):
	_xml = '<?xml version=\"1.0\"?>'
	_xml += '<REQUEST>'
        _xml += '<RECORD>'
	# Fill action name
	_xml = _xml + '<ID>' + _requestId + '</ID>'
	_xml = _xml + '<ACTION>' + actionName + '</ACTION>'
	if application != None:
		_xml = _xml + '<APPLICATION>' + application + '</APPLICATION>'
	_xml = _xml + '<ARGUMENTS>'
	_xml = _xml + '<ARGUMENT>ALL</ARGUMENT>'
	_xml = _xml + '</ARGUMENTS>'
	_xml = _xml + '</RECORD>'
	_xml += '</REQUEST>'
	return _xml

# Send given packet to server
def sendpacket (msg):
	try:
		# Get client socket fd based on thread id
		client = _sockFdPool.get (threading.currentThread ())
		# Pack length (integer value) in network byte order
		msglen = struct.pack ('!i', len (msg))
		#FIXME::msglen not taken using struct.pack as in ldtp.py
		# Send message length
		client.send (msglen)
		# Send message
		client.send (msg)
		# print len (msg), msg
		if ldtpDebug != None and ldtpDebug == '2':
			print 'send packet', msg
	except socket.error, msg:
		raise LdtpExecutionError ('Server aborted')

def recvpacket (peek_flag = 0, sockfd = None):
  	#print 'recv packet'
	flag = False
	try:
		flag = True
		client = None
		# Get client socket fd based on thread id
		if sockfd == None:
			client = _sockFdPool.get (threading.currentThread ())
		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, peek_flag)
		if data == '' or data == None:
			return None
		_packetSize, = struct.unpack('!i', data)
		if peek_flag == 0 and 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.

		if peek_flag != 0:
			# MSG_PEEK
			_responsePacket = client.recv (4 + _packetSize, peek_flag)
		else:
			_responsePacket = client.recv (_packetSize, peek_flag)
		if peek_flag != 0:
			_pattern = re.compile ('\<\?xml')
			_searchObj = re.search (_pattern, _responsePacket)
			_finalPacket = _responsePacket[_searchObj.start () :]
			_responsePacket = _finalPacket
		if peek_flag == 0 and ldtpDebug != None and ldtpDebug == '2':
			print 'Received response Packet', _responsePacket
		return _responsePacket
	except struct.error, msg:
		raise LdtpExecutionError ('Invalid packet length ' + str (msg))
	except AttributeError, msg:
		raise LdtpExecutionError ('Error while receiving packet ' + str (msg))
	except socket.timeout:
		if ldtpDebug != None and ldtpDebug == '2':
			print 'Timeout'
		return ''
	except socket.error, msg:
		raise LdtpExecutionError ('Error while receiving packet ' + str (msg))
	except MemoryError, msg:
		raise LdtpExecutionError ('Error while receiving packet ' + str (msg))
	except:
		raise LdtpExecutionError ('Error while receiving packet')

def peekresponse ():
	return recvpacket (socket.MSG_PEEK)

def getresponse (packetId):
	setflag = True
	while setflag:
		peekResponsePacket = peekresponse ()
		if peekResponsePacket == None:
			continue
		_responseType, _responseStatus, _responseData , _serverResponseLen, _requestFilePath = parsexml (peekResponsePacket)
		# For precautions, we are just checking, whether the packet is notification or not
		setflag = False
		if _responseType == None:
			continue
		if _responseStatus[2] == packetId:
			if _responseStatus[0] != 0 and ldtpDebug != None:
				print '*** ' + _responseStatus[1]
			packet = recvpacket ()
			# As we have already parsed the packet,
			# we are just returning the parsed packet
	       	return _responseStatus, _responseData

socketpath = '/tmp/ldtp-record-' + os.getenv ('USER') + '-' + os.getenv ('DISPLAY')

# Socket fd pool
_sockFdPool = {}

if socketpath == '':
	raise ConnectionLost ('Server not running')
try:
	# Create a client socket
	mainsock = socket.socket (socket.AF_UNIX, socket.SOCK_STREAM)
except socket.error,msg:
	raise LdtpExecutionError ('Error while creating UNIX socket  ' + str (msg))

# Let us retry connecting to the server for 3 times
retryCount = 0

while True:
	try:
		try:
			# Connect to server socket
			mainsock.connect (socketpath)
			break
		except TypeError:
			raise ConnectionLost ('Environment LDTP_AUTH_SOCK variable not set')
	except socket.error, msg:
		if retryCount == 3:
			raise ConnectionLost ('Could not establish connection ' + str (msg))
		retryCount += 1
		_pid = os.fork ()
		if _pid == 0:
			try:
				os.execvpe ('ldtp', (), os.environ)
			except OSError:
				raise LdtpExecutionError ('ldtp executable not in PATH')
		else:
			# Let us wait for 1 second, let the server starts
			time.sleep (1)

_sockFdPool[threading.currentThread ()] = mainsock

def shutdown ():
	if threading.activeCount () > 1:
		thread.exit ()

def getText (nodelist):
	rc = ""
	for node in nodelist:
		if node.nodeType == node.TEXT_NODE:
			rc = rc + node.data
	return rc

def getCData (nodelist):
	rc = ""
	for node in nodelist:
		if node.nodeType == node.CDATA_SECTION_NODE:
			rc = rc + node.data
	return rc

#This function generates the run.xml used as input to gldap.py 
def generaterunxml (filename):
	_runxml = '<?xml version=\"1.0\"?>'
	_runxml += '<ldtp>'
        _runxml += '<logfileoverwrite> 1 </logfileoverwrite>'
	_runxml += '<logfile>'
	_runxml += os.getcwd() + '/log.xml'
	_runxml += '</logfile>'
	# Fill action name
	_runxml = _runxml + '<group>' 
	_runxml = _runxml + '<script>' 
	_runxml = _runxml + '<name>' + filename +".py" '</name>'
	_runxml = _runxml + '</script>'
	_runxml = _runxml + '</group>'
	_runxml = _runxml + '</ldtp>'
	#print _runxml
	output_file = file("run.xml","w")
	output_file.write( _runxml)
	output_file.close()
	if ldtpDebug != None and ldtpDebug == '2':
		print "Run.xml created ..."

def parsexml (xmldata):
	"""Returns the value obtained from the server's return LDTP packet"""
	_statusMsg      = None
	_statusCode     = None
	_serverResponse = None
	_responseType   = None
	_requestId      = None
	_requestFilePath = None
	_serverResponse = None
	_responseObj    = None
	_serverResponseLen = 0

	try:
		if ldtpDebug != None and ldtpDebug == '2':
			print xmldata
		dom = parseString (xmldata)
		try:
			_responseObj   = dom.getElementsByTagName ('RESPONSE')[0]
			_responseType = 'record'
		except IndexError:
			raise LdtpExecutionError ('Invalid response')
		try:
			_responseStatusObj = _responseObj.getElementsByTagName ('STATUS')[0]
			_statusCode = int (getText (_responseStatusObj.getElementsByTagName ('CODE')[0].childNodes))
			_statusMsg  = getText (_responseStatusObj.getElementsByTagName ('MESSAGE')[0].childNodes)
		except ValueError:
			raise LdtpExecutionError ('Invalid Status')
		except IndexError:
			raise LdtpExecutionError ('Invalid Status')
		try:
			_requestId  = getText (_responseObj.getElementsByTagName ('ID')[0].childNodes)
		except IndexError:
			# On notification _requestId will be empty
			pass
		try:
			data = _responseObj.getElementsByTagName ('DATA')[0]
			_serverResponse    = getCData (data.getElementsByTagName ('VALUE')[0].childNodes).encode ('utf-8')
			_serverResponseLen = int (getText (data.getElementsByTagName ('LENGTH')[0].childNodes))
		except ValueError:
			raise LdtpExecutionError ('Invalid Data Length')
		except IndexError:
			_serverResponse = None
			# Data tag may not be present
			pass
		try:
			_requestFilePath  = getText (_responseObj.getElementsByTagName ('FILEPATH')[0].childNodes)
		except IndexError:
			_requestFilePath = None
			# On notification _requestId will be empty
			pass
	except ExpatError, msg:
		if xml.parsers.expat.ErrorString (msg.code) == xml.parsers.expat.errors.XML_ERROR_NO_ELEMENTS:
			return None
		raise LdtpExecutionError ('Parsing XML error: ' + str (msg))

	# Return all the respective values, let the calling function decide what to do with the values
	if ldtpDebug != None and ldtpDebug == '2':
	 	print "Response Length:", _serverResponseLen
		print "Status", _statusCode, _statusMsg
	if _requestFilePath != None:
		print "XML File from server:", _requestFilePath #, "- Note: When filing bug attach this file, apart from steps to reproduce the bug"
		print "Created python file path ", filename + ".py"
		print "Created XML file path path ", filename + ".xml"

	if (_statusCode == 0):
		generate_python_code (_requestFilePath, filename)
		generate_data_xml (_requestFilePath, filename)
	return _responseType, (_statusCode, _statusMsg, _requestId), (_serverResponseLen, _serverResponse), _requestFilePath, _serverResponseLen

def startrecord (applicationName = None, arguments = None):
	try:
		_requestId  = str (random.randint (0, sys.maxint))
		_message = generatexml ('startrecord', _requestId, applicationName, arguments)
		sendpacket (_message)
	except LdtpExecutionError, msg:
		 pass
	except LdtpExecutionError, msg:
		raise LdtpExecutionError (str (msg))

def stoprecord (filename):
	try:
		_requestId  = str (random.randint (0, sys.maxint))
		_message = generatexml ('stoprecord', _requestId)
		sendpacket (_message)
		_responseStatus, _responseData = getresponse (_requestId)
	except LdtpExecutionError, msg:
		raise LdtpExecutionError (str (msg) + str (traceback.format_exc ()))

if actionName == "startrecord" :
	startrecord (applicationName, arguments)
else :
	stoprecord (filename)

