#!/usr/bin/env python

import sys, os.path
import urllib, shelve
import time, datetime

from string import rjust


CONFIG_USER     = 'portalcheck'
CONFIG_NAME     = 'portal'
CONFIG_DBFILE   = '/var/lib/portalcheck/shelvedb'
CONFIG_CHECKURL = 'https://portal.imn.htwk-leipzig.de'
CONFIG_DICTMAX  = 10

CONFIG_MAIL_FROM = 'toni.uhlig@stud.htwk-leipzig.de'
CONFIG_MAIL_TO   = 'toni.uhlig@stud.htwk-leipzig.de'
CONFIG_MAIL_CC   = [ 'toni.uhlig@stud.htwk-leipzig.de' ]
CONFIG_MAIL_HOST = 'localhost'

CONFIG_MAIL_SUBJ = 'Portal state changed: %s'
CONFIG_MAIL_OFF  = ''+ \
    'Dies ist eine automatisch generierte E-Mail.\n\n'+ \
    'Die URI %s ist seit dem %s nicht mehr erreichbar.\n'+ \
    'Letzter HTTP response code: %s\n\n'+ \
    'Sie werden benachrichtigt, wenn das Portal wieder erreichbar ist.\n'
CONFIG_MAIL_ON   = ''+ \
    'Dies ist eine automatisch generierte E-Mail.\n\n'+ \
    'Die URI %s ist seit %s wieder erreichbar.\n'+ \
    'Offline Dauer: %s\n'

TYPE_ONLINE        = 0
TYPE_ONLINE_AGAIN  = 1
TYPE_OFFLINE       = 2
TYPE_OFFLINE_AGAIN = 3


def dbg(dbgstr):
    if sys.flags.debug:
        print('>>> pcheck: ' + str(dbgstr))


class PortalObject(object):
    def __init__(self, timestamp, httpresp, errstr=None):
        self.timestamp = float(timestamp)
        self.httpresp = int(httpresp)
        self.errstr = str(errstr)


class PortalCheck(object):
    DATETIME_FMT = '%02d.%02d.%04d - %02d:%02d:%02d'
    DICT_LAST = 'last'
    DICT_CURRENT_TYPE = 'cur_type'
    DICT_CURRENT_INDEX = 'cur_index'

    def __init__(self, uri, dbpath):
        self.uri = uri
        dbpath += '-' + CONFIG_NAME
        try:
            self.dbfilename = dbpath
            self.shelvedb = shelve.open(self.dbfilename)
        except:
            self.dbfilename = './' + os.path.basename(dbpath)
            self.shelvedb = shelve.open(self.dbfilename)
        try:
            po = self.shelvedb[self.DICT_LAST]
        except KeyError:
            dbg('Init shelvedb')
            self.shelvedb[self.DICT_LAST] = PortalObject(time.time(), 200)
            self.shelvedb[self.DICT_CURRENT_TYPE] = TYPE_ONLINE
            self.shelvedb[self.DICT_CURRENT_INDEX] = 0
        dbg('Open ' + self.dbfilename)

    def cleanup(self):
        self.shelvedb.close()

    def doTimeFormat(self, tm):
        lt = time.localtime(tm);
        return '%02d.%02d.%04d - %02d:%02d:%02d' % (lt.tm_mday, lt.tm_mon, lt.tm_year, lt.tm_hour, lt.tm_min, lt.tm_sec)

    def sendMail(self):
        typ = self.loadType()
        typstr = 'UNKNOWN'
        mailcon = 'EMPTY'
        last = self.loadLast()
        tdstr = self.doTimeFormat(last.timestamp)
        if typ == TYPE_ONLINE or typ == TYPE_ONLINE_AGAIN:
            typstr = 'ONLINE'
            ind = self.loadIndex()
            forelast = self.loadObject(ind - 1)
            offdays = divmod(last.timestamp - forelast.timestamp, 86400)
            offhrs = divmod(offdays[1], 3600)
            offmins = divmod(offhrs[1], 60)
            offsecs = offmins[1]
            offstr = '%d Tag%c, %d Stunde%c, %d Minute%c, %d Sekunde%c' % \
                (offdays[0], 'e' if offdays[0] != 1 else ' ', \
                 offhrs[0], 'n' if offhrs[0] != 1 else ' ', \
                 offmins[0], 'n' if offmins[0] != 1 else ' ', \
                 offsecs, 'n' if offsecs != 1 else ' ')
            mailcon = CONFIG_MAIL_ON % (self.uri, tdstr, offstr)
        elif typ == TYPE_OFFLINE or typ == TYPE_OFFLINE_AGAIN:
            typstr = 'OFFLINE'
            httpresp = str(last.httpresp) if last.httpresp != -1 else 'connection error (' + str(last.errstr) + ')'
            mailcon = CONFIG_MAIL_OFF % (self.uri, tdstr, httpresp)
        else:
            return
        subj = CONFIG_MAIL_SUBJ % (typstr)
        dbg(str(subj))
        dbg(str(mailcon))
        import smtplib
        from email.mime.text import MIMEText
        msg = MIMEText(str(mailcon))
        msg['Subject'] = str(subj)
        msg['From'] = CONFIG_MAIL_FROM
        msg['To'] = CONFIG_MAIL_TO
        msg['Cc'] = ', '.join(CONFIG_MAIL_CC)
        s = smtplib.SMTP(CONFIG_MAIL_HOST)
        s.sendmail(CONFIG_MAIL_FROM, [CONFIG_MAIL_TO]+CONFIG_MAIL_CC, msg.as_string())

    def doCheck(self):
        sock = urllib.urlopen(str(self.uri))
        resp = sock.getcode()
        sock.close()
        return resp

    def loadLast(self):
        return self.shelvedb[self.DICT_LAST]
    def storeLast(self, portalObject):
        self.shelvedb[self.DICT_LAST] = portalObject

    def loadObject(self, index):
        return self.shelvedb[str( (int(index) % CONFIG_DICTMAX) )]
    def storeObject(self, portalObject):
        ind = self.loadIndex()
        self.shelvedb[str(ind)] = portalObject
        ind = (ind + 1) % CONFIG_DICTMAX
        self.storeIndex(ind)

    def loadType(self):
        return int(self.shelvedb[self.DICT_CURRENT_TYPE])
    def storeType(self, onlineType):
        self.shelvedb[self.DICT_CURRENT_TYPE] = int(onlineType)

    def loadIndex(self):
        return int(self.shelvedb[self.DICT_CURRENT_INDEX] % CONFIG_DICTMAX)
    def storeIndex(self, index):
        self.shelvedb[self.DICT_CURRENT_INDEX] = (index % CONFIG_DICTMAX)

    def listObjects(self):
        for (key, value) in sorted(self.shelvedb.iteritems()):
            if type(value) == PortalObject:
                outdate = self.doTimeFormat(value.timestamp)
                outval = outdate + ' ' + rjust(str(value.httpresp), 3, ' ')
            else:
                outval = str(value)
            print('Key: [%s] | Value: [%s]' % ( rjust(key, 9, ' '), rjust(outval, 25, ' ') ))

    def doPortalCheck(self):
        errstr = None
        try:
            httpresp = self.doCheck()
        except IOError as err:
            httpresp = -1
            errstr = str(err)
        curtime = time.time()
        po = PortalObject(curtime, httpresp, errstr)
        ct = self.loadType()
        if httpresp is 200:
            dbg('Ok')
            if ct == TYPE_ONLINE:
                self.storeType(TYPE_ONLINE_AGAIN)
            elif ct != TYPE_ONLINE_AGAIN:
                self.storeType(TYPE_ONLINE)
                last = self.loadLast()
                self.storeObject(last)
                self.storeLast(po)
                self.sendMail()
            self.shelvedb.sync()
        else:
            dbgstr = str()
            dbgstr += ' (' + errstr + ')' if errstr is not None else ''
            dbg('Err ' + str(httpresp) + dbgstr)
            if ct == TYPE_OFFLINE:
                self.storeType(TYPE_OFFLINE_AGAIN)
            elif ct != TYPE_OFFLINE_AGAIN:
                self.storeType(TYPE_OFFLINE)
                last = self.loadLast()
                self.storeObject(last)
                self.storeLast(po)
                self.sendMail()


if __name__ == '__main__':
    os.umask(0117)
    if os.getuid() == 0:
        oldmask = os.umask(0)
        try:
            os.mkdir(os.path.dirname(CONFIG_DBFILE), 0755)
        except:
            pass
        os.umask(oldmask)
        pc = PortalCheck(CONFIG_CHECKURL, CONFIG_DBFILE)
        dbg('dropping root privileges')
        import pwd
        pwd = pwd.getpwnam(CONFIG_USER)
        os.chown(pc.dbfilename, 0, pwd.pw_gid)
    else:
        pc = PortalCheck(CONFIG_CHECKURL, CONFIG_DBFILE)

    pc.doPortalCheck()
    if sys.flags.debug:
        pc.listObjects()
    pc.cleanup()