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) |