diff options
| -rw-r--r-- | skype/skyped.py | 165 | 
1 files changed, 129 insertions, 36 deletions
| diff --git a/skype/skyped.py b/skype/skyped.py index f15245ed..bf35b9ce 100644 --- a/skype/skyped.py +++ b/skype/skyped.py @@ -25,7 +25,6 @@ import os  import signal  import locale  import time -import gobject  import socket  import getopt  import Skype4Py @@ -33,6 +32,8 @@ import hashlib  from ConfigParser import ConfigParser, NoOptionError  from traceback import print_exception  import ssl +import select +import threading  __version__ = "0.1.1" @@ -41,8 +42,7 @@ def eh(type, value, tb):  	if type != KeyboardInterrupt:  		print_exception(type, value, tb) -	gobject.MainLoop().quit() -	options.conn.close() +	if options.conn: options.conn.close()  	# shut down client if it's running  	try:  		skype.skype.Client.Shutdown() @@ -52,66 +52,104 @@ def eh(type, value, tb):  sys.excepthook = eh -def input_handler(fd, io_condition): +def wait_for_lock(lock, timeout_to_print, timeout, msg): +	start = time.time() +	locked = lock.acquire(0) +	while not(locked): +		time.sleep(0.5) +		if timeout_to_print and (time.time() - timeout_to_print > start): +			dprint("%s: Waited %f seconds" % \ +					(msg, time.time() - start)) +			timeout_to_print = False +		if timeout and (time.time() - timeout > start): +			dprint("%s: Waited %f seconds, giving up" % \ +					(msg, time.time() - start)) +			return False +		locked = lock.acquire(0) +	return True + +def input_handler(fd):  	global options +	global skype  	if options.buf:  		for i in options.buf:  			skype.send(i.strip())  		options.buf = None -	else: -		try: -			input = fd.recv(1024) -		except Exception, s: -			dprint("Warning, receiving 1024 bytes failed (%s)." % s) -			fd.close() -			return False -		for i in input.split("\n"): -			skype.send(i.strip())  		return True +	else: +		close_socket = False +		if wait_for_lock(options.lock, 3, 10, "input_handler"): +			try: +					input = fd.recv(1024) +					options.lock.release() +			except Exception, s: +				dprint("Warning, receiving 1024 bytes failed (%s)." % s) +				fd.close() +				options.conn = False +				options.lock.release() +				return False +			for i in input.split("\n"): +				if i.strip() == "SET USERSTATUS OFFLINE": +					close_socket = True +				skype.send(i.strip()) +		return not(close_socket)  def skype_idle_handler(skype):  	try:  		c = skype.skype.Command("PING", Block=True)  		skype.skype.SendCommand(c) +		dprint("... skype pinged")  	except Skype4Py.SkypeAPIError, s:  		dprint("Warning, pinging Skype failed (%s)." % (s))  	return True  def send(sock, txt): -	from time import sleep +	global options  	count = 1  	done = False -	while (not done) and (count < 10): -		try: -			sock.send(txt) -			done = True -		except Exception, s: -			count += 1 -			dprint("Warning, sending '%s' failed (%s). count=%d" % (txt, s, count)) -			sleep(1) +	while (not done) and (count < 10) and options.conn: +		if wait_for_lock(options.lock, 3, 10, "socket send"): +			try: +				if options.conn: sock.send(txt) +				options.lock.release() +				done = True +			except Exception, s: +				options.lock.release() +				count += 1 +				dprint("Warning, sending '%s' failed (%s). count=%d" % (txt, s, count)) +				time.sleep(1)  	if not done: -		options.conn.close() +		if options.conn: options.conn.close() +		options.conn = False +	return done  def bitlbee_idle_handler(skype): +	global options +	done = False  	if options.conn:  		try:  			e = "PING" -			send(options.conn, "%s\n" % e) +			done = send(options.conn, "%s\n" % e) +			dprint("... pinged Bitlbee")  		except Exception, s:  			dprint("Warning, sending '%s' failed (%s)." % (e, s)) -			options.conn.close() -	return True +			if options.conn: options.conn.close() +			options.conn = False +			done = False +	return done -def server(host, port): +def server(host, port, skype):  	global options  	sock = socket.socket()  	sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  	sock.bind((host, port))  	sock.listen(1) -	gobject.io_add_watch(sock, gobject.IO_IN, listener) +	dprint("Waiting for connection...") +	listener(sock, skype) -def listener(sock, *args): +def listener(sock, skype):  	global options +	if not(wait_for_lock(options.lock, 3, 10, "listener")): return False  	rawsock, addr = sock.accept()  	options.conn = ssl.wrap_socket(rawsock,  		server_side=True, @@ -122,6 +160,7 @@ def listener(sock, *args):  		try:  			options.conn.handshake()  		except Exception: +			options.lock.release()  			dprint("Warning, handshake failed, closing connection.")  			return False  	ret = 0 @@ -135,15 +174,21 @@ def listener(sock, *args):  	except Exception, s:  		dprint("Warning, receiving 1024 bytes failed (%s)." % s)  		options.conn.close() +		options.conn = False +		options.lock.release()  		return False  	if ret == 2:  		dprint("Username and password OK.")  		options.conn.send("PASSWORD OK\n") -		gobject.io_add_watch(options.conn, gobject.IO_IN, input_handler) +		options.lock.release() +		serverloop(options, skype)  		return True  	else:  		dprint("Username and/or password WRONG.")  		options.conn.send("PASSWORD KO\n") +		options.conn.close() +		options.conn = False +		options.lock.release()  		return False  def dprint(msg): @@ -187,16 +232,21 @@ class SkypeApi:  			# messages.. so here it is: always use utf-8 then  			# everybody will be happy  			e = i.encode('UTF-8') -			dprint('<< ' + e)  			if options.conn: +				dprint('<< ' + e)  				try: +					# I called the send function really_send  					send(options.conn, e + "\n")  				except Exception, s:  					dprint("Warning, sending '%s' failed (%s)." % (e, s)) -					options.conn.close() +					if options.conn: options.conn.close() +					options.conn = False +			else: +				dprint('---' + e)  	def send(self, msg_text):  		if not len(msg_text) or msg_text == "PONG": +			if msg_text == "PONG": options.last_bitlbee_pong = time.time()  			return  		try:  			encoding = locale.getdefaultlocale()[1] @@ -252,6 +302,49 @@ Options:  	-v	--version	display version information""" % (self.cfgpath, self.host, self.port)  		sys.exit(ret) +def serverloop(options, skype): +	timeout = 1; # in seconds +	skype_ping_period = 5 +	bitlbee_ping_period = 10 +	bitlbee_pong_timeout = 30 +	now = time.time() +	skype_ping_start_time = now +	bitlbee_ping_start_time = now +	options.last_bitlbee_pong = now +	in_error = [] +	handler_ok = True +	while (len(in_error) == 0) and handler_ok and options.conn: +		ready_to_read, ready_to_write, in_error = \ +			select.select([options.conn], [], [options.conn], \ +				timeout) +		now = time.time() +		handler_ok = len(in_error) == 0 +		if (len(ready_to_read) == 1) and handler_ok: +			handler_ok = input_handler(ready_to_read.pop()) +			# don't ping bitlbee/skype if they already received data +			now = time.time() # allow for the input_handler to take some time +			bitlbee_ping_start_time = now +			skype_ping_start_time = now +			options.last_bitlbee_pong = now +		if (now - skype_ping_period > skype_ping_start_time) and handler_ok: +			handler_ok = skype_idle_handler(skype) +			skype_ping_start_time = now +		if now - bitlbee_ping_period > bitlbee_ping_start_time: +			handler_ok = bitlbee_idle_handler(skype) +			bitlbee_ping_start_time = now +			if options.last_bitlbee_pong: +				if (now - options.last_bitlbee_pong) > bitlbee_pong_timeout: +					dprint("Bitlbee pong timeout") +					# TODO is following line necessary? Should there be a options.conn.unwrap() somewhere? +					# options.conn.shutdown() +					if options.conn: options.conn.close() +					options.conn = False +				else: +					dprint("%f seconds since last PONG" % (now - options.last_bitlbee_pong)) +			else: +				options.last_bitlbee_pong = now +	dprint("Serverloop done") +  if __name__=='__main__':  	options = Options()  	try: @@ -316,11 +409,11 @@ if __name__=='__main__':  			sys.exit(0)  	else:  		dprint('skyped is started on port %s' % options.port) -	server(options.host, options.port)  	try:  		skype = SkypeApi()  	except Skype4Py.SkypeAPIError, s:  		sys.exit("%s. Are you sure you have started Skype?" % s) -	gobject.timeout_add(2000, skype_idle_handler, skype) -	gobject.timeout_add(60000, bitlbee_idle_handler, skype) -	gobject.MainLoop().run() +	while 1: +		options.conn = False +		options.lock = threading.Lock() +		server(options.host, options.port, skype) | 
