#!/usr/bin/python

# Dump/restore functions to allow to backup or upgrade the database

from xml.dom.minidom import getDOMImplementation
import xml.dom
from datetime import datetime

class Reader1_2(object):
    def __init__(self):
        self.tn = dict(
            config = "config",
            users = "users",
            cactivity = "client_activity",
            iqueue = "install_queue",
            octomonlog = "octomon_log",
        )

    def _parse_ts(self, ts):
        return None

    def _to_string(self, val):
        if val == None: return ""
        return str(val)

    def read(self, c, dumper):
        c.execute("select config_key, config_value from %s" % self.tn["config"])
        for row in c:
            dumper.dump_config(row[0], row[1])

        c.execute("select username, action, timestamp from %s" % self.tn["users"])
        for row in c:
            ts = self._parse_ts(row[2])
            dumper.dump_user(row[0], row[1], ts)

        c.execute("select hostname, username, groupname from %s" % self.tn["usergroups"])
        for row in c:
            dumper.dump_usergroup(*row)

        c.execute("select hostname, timestamp, component, value from %s" % self.tn["ccomponent"])
        for row in c:
            ts = self._parse_ts(row[1])
            dumper.dump_clientcomponent(row[0], ts, row[2], row[3])
        
        c.execute("select hostname, timestamp from %s" % self.tn["cactivity"])
        for row in c:
            ts = self._parse_ts(row[1])
            dumper.dump_clientactivity(row[0], ts)

        c.execute("select hostname, package, status, lastlog, inserttime, lastlog_time from %s" % self.tn["iqueue"])
        for row in c:
            it = self._parse_ts(row[4])
            lt = self._parse_ts(row[5])
            dumper.dump_installqueue(row[0], row[1], row[2], row[3], it, lt)

        c.execute("select tsend, tsstart, lines from %s" % self.tn["octomonlog"])
        for row in c:
            dumper.dump_octomonlog(*[self._to_string(x) for x in row])

class Reader1(Reader1_2):
    def __init__(self):
        super(Reader1, self).__init__()
        self.tn.update(
            usergroups = "usergroups",
            ccomponent = "client_components",
        )

    def _parse_ts(self, ts):
        return datetime.utcfromtimestamp(ts)

class Reader2(Reader1_2):
    def __init__(self):
        super(Reader2, self).__init__()
        self.tn.update(
            usergroups = "user_groups",
            ccomponent = "client_component",
        )

    def _parse_ts(self, s):
        # Parse timestamps like 2008-09-19 13:38:57
        return datetime.strptime(s, "%Y-%m-%d %H:%M:%S")


class Dumper:
    def make_text_el(self, name, value):
        el = self.doc.createElement(name)
        el.appendChild(self.doc.createTextNode(value))
        return el

    def format_datetime(self, dt):
        if dt == None: return ""
        # Convert the timestamp to UTC
        if dt.tzinfo:
            utc = (dt - dt.utcoffset()).replace(tzinfo=None)
        else:
            utc = dt
        formatted = utc.strftime("%Y-%m-%d %H:%M:%S")
        if utc.microsecond:
            formatted += ".%06d" % utc.microsecond
        return formatted

    def __init__(self):
        self.impl = getDOMImplementation()
        self.doc = self.impl.createDocument(None, "octofuss", None)
        self.root = self.doc.documentElement

    def __del__(self):
        self.doc.unlink()

    def dump_config(self, key, value):
        el = self.doc.createElement("config")
        self.root.appendChild(el)
        el.appendChild(self.make_text_el("key", key))
        el.appendChild(self.make_text_el("value", value))
        
    def dump_user(self, name, action, timestamp):
        el = self.doc.createElement("user")
        self.root.appendChild(el)
        el.appendChild(self.make_text_el("username", name))
        el.appendChild(self.make_text_el("action", action))
        el.appendChild(self.make_text_el("timestamp", self.format_datetime(timestamp)))

    def dump_usergroup(self, hostname, username, groupname):
        el = self.doc.createElement("usergroup")
        self.root.appendChild(el)
        el.appendChild(self.make_text_el("hostname", hostname))
        el.appendChild(self.make_text_el("username", username))
        el.appendChild(self.make_text_el("groupname", groupname))

    def dump_clientcomponent(self, hostname, timestamp, component, value):
        el = self.doc.createElement("clientcomponent")
        self.root.appendChild(el)
        el.appendChild(self.make_text_el("hostname", hostname))
        el.appendChild(self.make_text_el("timestamp", self.format_datetime(timestamp)))
        el.appendChild(self.make_text_el("component", component))
        el.appendChild(self.make_text_el("value", value))
        
    def dump_clientactivity(self, hostname, timestamp):
        el = self.doc.createElement("clientactivity")
        self.root.appendChild(el)
        el.appendChild(self.make_text_el("hostname", hostname))
        el.appendChild(self.make_text_el("timestamp", self.format_datetime(timestamp)))

    def dump_installqueue(self, hostname, package, status, lastlog, ts_inserttime, ts_lastlog_time):
        el = self.doc.createElement("installqueue")
        self.root.appendChild(el)
        el.appendChild(self.make_text_el("hostname", hostname))
        el.appendChild(self.make_text_el("package", package))
        el.appendChild(self.make_text_el("status", status))
        el.appendChild(self.make_text_el("lastlog", lastlog))
        el.appendChild(self.make_text_el("inserttime", self.format_datetime(ts_inserttime)))
        el.appendChild(self.make_text_el("lastlog_time", self.format_datetime(ts_lastlog_time)))

    def dump_octomonlog(self, tsend, tsstart, lines):
        el = self.doc.createElement("octomonlog")
        self.root.appendChild(el)
        el.appendChild(self.make_text_el("tsend", tsend))
        el.appendChild(self.make_text_el("tsstart", tsstart))
        el.appendChild(self.make_text_el("lines", lines))

    def write(self, fd):
        #self.doc.writexml(fd, encoding="UTF-8", addindent=" ", newl="\n")
        self.doc.writexml(fd, encoding="UTF-8")

    def to_string(self):
        return self.doc.toxml(encoding="UTF-8")


class Restorer:
#   def make_text_el(self, name, value):
#       el = self.doc.createElement(name)
#       el.appendChild(self.doc.createTextNode(value))
#       return el

    def _to_dict(self, node):
        "Convert a DOM node to a dict"
        res = dict()
        for n in node.childNodes:
            if n.nodeType != xml.dom.Node.ELEMENT_NODE: continue
            key = n.nodeName
            val = None
            for t in n.childNodes:
                if t.nodeType == xml.dom.Node.TEXT_NODE:
                    val = t.data.strip()
                    break
            if val != "":
                res[key] = val
        return res

    def _timestamp(self, val):
        if val == None or val == "":
            return None
        dt = datetime.strptime(val[:19], "%Y-%m-%d %H:%M:%S")
        if len(val) > 7 and val[-7] == ".":
            dt = dt.replace(microsecond=int(val[-6:]))
        return dt
    
    def _db_ts(self, val):
        dt = self._timestamp(val)
        if dt == None: return None
        return dt.strftime("%Y-%m-%d %H:%M:%S")

    def _db_str(self, val):
        if val == None: return ""
        return str(val)

    def _db_int(self, val):
        if val == None or val == "":
            return None
        return int(val)

    def _db_float(self, val):
        if val == None or val == "":
            return None
        return float(val)

    def __init__(self, cur):
        self.cur = cur

    def read(self, dom):
        for n in dom.childNodes:
            if n.nodeType != xml.dom.Node.ELEMENT_NODE: continue
            data = self._to_dict(n)
            if n.nodeName == "user":
                self.cur.execute("insert into users (username, action, timestamp) values (?, ?, ?)",
                    (self._db_str(data.get("username")),
                    self._db_str(data.get("action")),
                    self._db_ts(data.get("timestamp"))))
            elif n.nodeName == "config":
                if data.get("key", None) == "VERSION": continue
                self.cur.execute("insert into config (config_key, config_value) values (?, ?)",
                    (self._db_str(data.get("key")),
                    self._db_str(data.get("value"))))
            elif n.nodeName == "usergroup":
                self.cur.execute("insert into user_groups (hostname, username, groupname) values (?, ?, ?)",
                    (self._db_str(data.get("hostname")),
                    self._db_str(data.get("username")),
                    self._db_str(data.get("groupname"))))
            elif n.nodeName == "clientcomponent":
                self.cur.execute("insert into client_component (hostname, timestamp, component, value) values (?, ?, ?, ?)",
                    (self._db_str(data.get("hostname")),
                    self._db_ts(data.get("timestamp")),
                    self._db_str(data.get("component")),
                    self._db_str(data.get("value"))))
            elif n.nodeName == "clientactivity":
                self.cur.execute("insert into client_activity (hostname, timestamp) values (?, ?)",
                    (self._db_str(data.get("hostname")),
                    self._db_ts(data.get("timestamp"))))
            elif n.nodeName == "installqueue":
                self.cur.execute("insert into install_queue (hostname, package, status, lastlog, inserttime, lastlog_time) values (?, ?, ?, ?, ?, ?)",
                    (self._db_str(data.get("hostname")),
                    self._db_str(data.get("package")),
                    self._db_int(data.get("status")),
                    data.get("lastlog", None),
                    self._db_ts(data.get("inserttime", None)),
                    self._db_ts(data.get("lastlog_time", None))))
            elif n.nodeName == "octomonlog":
                self.cur.execute("insert into octomon_log (tsend, tsstart, lines) values (?, ?, ?)",
                    (self._db_float(data.get("tsend", None)),
                    self._db_float(data.get("tsstart", None)),
                    self._db_int(data.get("lines", None))))

def dump(fd = None):
    """
    Dump to a file descriptor or a string
    """
    dumper = Dumper()
    if fd:
        dumper.write(fd)
        return None
    else:
        return dumper.to_string()

def restore(fd = None, string = None):
    """
    Restore from a file descriptor or a string
    """
    pass


if __name__ == "__main__":
    import sys
    from optparse import OptionParser

    VERSION="0.1"

    class Parser(OptionParser):
        def __init__(self, *args, **kwargs):
            OptionParser.__init__(self, *args, **kwargs)

        def error(self, msg):
            sys.stderr.write("%s: error: %s\n\n" % (self.get_prog_name(), msg))
            self.print_help(sys.stderr)
            sys.exit(2)

    parser = Parser(usage="usage: %prog [options] database",
                    version="%prog "+ VERSION,
                    description="Dump or restore an octofuss database")
    parser.add_option("--dump", action="store_true", help="dump database contents")
    parser.add_option("--restore", action="store_true", help="restore database contents from dump")
    (opts, args) = parser.parse_args()

    if len(args) == 0:
        parser.error("you need to specify the database name")

    try:
        import sqlite3
        db = sqlite3.connect(args[0])
        cur = db.cursor()
        cur.execute("select config_value from config where config_key = 'VERSION'")
    except sqlite3.DatabaseError:
        import sqlite
        db = sqlite.connect(args[0])
        cur = db.cursor()
        cur.execute("select config_value from config where config_key = 'VERSION'")

    ver = [x[0] for x in cur]
    if len(ver) == 0:
        raise RuntimeError("Database has no version information")
    ver = ver[0]

    if ver == "1":
        reader = Reader1()
    elif ver == "2":
        reader = Reader2()
    else:
        raise RuntimeError("Unsupported database version '%s'" % ver)

    if opts.dump:
        dumper = Dumper()
        reader.read(cur, dumper)
        dumper.write(sys.stdout)
    elif opts.restore:
        if len(args) > 1:
            dom = xml.dom.minidom.parse(args[1])
        else:
            dom = xml.dom.minidom.parse(sys.stdin)
        restorer = Restorer(cur)
        restorer.read(dom.documentElement)
        db.commit()


    #import sqlite
    #db = sqlite.connect(args[0])
#   try:
#   except sqlite3.DatabaseError::
#       raise e
#       import sqlite3
#       db = sqlite3.connect(args[0])



#try:
#  import sqlite as sqlitedb
#except:
#  import sqlite3 as sqlitedb
#(12:35:04) cgabriel: e poi lo stesso con la sqlitedb.Connection()

