#! /usr/bin/env python
#

"""
PyHeartBeat server
While the BeatLog thread logs UDP each packet in a dictionary,
the main thread periodically scans the dictionary and prints
the IP addresses of the clients that did not send any packet
for more than the timeout
"""

HBPORT = 43278
CHECKWAIT = 30

from socket import socket, gethostbyname, AF_INET, SOCK_DGRAM
from threading import Lock, Thread, Event
from time import time, ctime, sleep

class BeatDict:
    "Manage heartbeat dictionary"

    def __init__(self):
        self.beatDict = {}
        if __debug__:
            self.beatDict['127.0.0.1'] = time()
        self.dictLock = Lock()

    def __repr__(self):
        list = ''
        for key in self.beatDict.keys():
            list = "%sIP address: %s - Last time: %s\n" % \
                (list, key, ctime(self.beatDict[key]))
        return list

    def update(self, entry):
        "Create or update a dictionary entry"
        self.dictLock.acquire()
        self.beatDict[entry] = time()
        self.dictLock.release()

    def extractSilent(self, howPast):
        "Returns a list of entries older than howPast"
        silent = []
        when = time() - howPast
        self.dictLock.acquire()
        for key in self.beatDict.keys():
            if self.beatDict[key] < when:
                silent.append(key)
        self.dictLock.release()
        return silent


class BeatRec(Thread):
    "Receive UDP packets, log them in heartbeat dictionary"

    def __init__(self, goOnEvent, updateDictFunc, port):
        Thread.__init__(self)
        self.goOnEvent = goOnEvent
        self.updateDictFunc = updateDictFunc
        self.hostIP = gethostbyname('localhost')
        self.port = port
        self.recSocket = socket(AF_INET, SOCK_DGRAM)
        self.recSocket.bind((self.hostIP, self.port))

    def __repr__(self):
        return "Server IP: %s\nServer port: %d\n" % \
            (self.hostIP, self.port)

    def run(self):
        while self.goOnEvent.isSet():
            if __debug__:
                print "Waiting to receive..."
            data, addr = self.recSocket.recvfrom(5)
            if __debug__:
                print "Received packet from " + `addr`
            self.updateDictFunc(addr[0])


def main():
    "Listen to the heartbeats and detect inactive clients"

    beatRecGoOnEvent = Event()
    beatRecGoOnEvent.set()
    beatDictObject = BeatDict()
    beatRecThread = BeatRec(beatRecGoOnEvent,
        beatDictObject.update, HBPORT)
    if __debug__:
        print beatRecThread
    beatRecThread.start()
    print "PyHeartBeat server listening on port %d" % HBPORT
    print "\n*** Press Ctrl-C to stop ***\n"
    while 1:
        try:
            if __debug__:
                print "Beat Dictionary"
                print `beatDictObject`
            silent = beatDictObject.extractSilent(CHECKWAIT)
            if silent:
                print "Silent clients"
                print `silent`
            sleep(CHECKWAIT)
        except KeyboardInterrupt:
            print "Exiting."
            beatRecGoOnEvent.clear()
            beatRecThread.join()


if __name__ == '__main__':
    main()