Below is the file 'yowie.py' from this revision. You can also download the file.

#!/usr/bin/env python2.4

import config
import pipes
import time
import csv
import sha
import sys
import os

class DeviceProxy:
    def __init__(self, device_name, device_config):
        self.config = device_config
        self.device_name = device_name
        for required in ('driver', 'zone'):
            if not self.config.has_key(required):
                raise Exception('Cannot initialize device %s: required config item %s unspecified.' % (device_name, required))

class Table:
    def __init__(self, table_name, column_file):
        self.table_name = table_name
        def load_columns():
            return csv.reader(open(column_file)).next()
        self.columns = load_columns()
        self.hash = sha.new(str(self.columns)).hexdigest()

class TableIO:
    def __init__(self, implement, filename):
        self.implement, self.filename = implement, filename

    def read_last_values(self):
        if os.access(self.filename, os.R_OK):
            for row in csv.reader(open(self.filename)):
                yield row

    def write(self, rows, rowlen):
        fd = open(self.filename, "w")
        writer = csv.writer(fd)
        for row in rows:
            if len(row) != rowlen:
                raise Exception("Row has length %d (must be %d)" % (len(row), rowlen))
            writer.writerow(row)

export_funcs = {}
def export_to_drivers(f):
    export_funcs[f.__name__] = f
    return f

@export_to_drivers
def now():
    return int(time.time())

@export_to_drivers
def log(*args):
    if len(args) == 1:
        args = args[0]
    else:
        args = str(args)
    print "%s : %s" % (time.ctime(), args)

@export_to_drivers
def simple_has_table(supported):
    return lambda a : supported.has_key(a)

@export_to_drivers
def simple_get_table(supported):
    def __get_table(*args, **kwargs):
        return supported[args[0]](*args, **kwargs)
    return __get_table

def monotone_commit():
    def s(*args, **kwargs):
        return os.system(*args, **kwargs) >> 8
    os.chdir(config.data_path)
    pq = pipes.quote
    if not os.access(config.mtn_database, os.R_OK):
        rv = s(config.mtn + ' --db=%s db init' % pq(config.mtn_database))
        if rv != 0:
            raise Exception("Unable to initialise monotone database: %s" % config.mtn_database)
    base_cmd = '%s --db=%s --branch=%s ' % (config.mtn,
                                           pq(config.mtn_database),
                                           pq(config.mtn_branch))

if __name__ == '__main__':
    def init_drivers():
        rv = {}
        orig_path = sys.path
        if hasattr(config, 'plugin_path'):
            sys.path.insert(0, config.plugin_path)
        for name in config.drivers:
            mod = __import__('%s' % name, None, None, None)
            for e in export_funcs:
                setattr(mod, e, export_funcs[e])
            export = {'has_table' : None, 'get_table' : None}
            setattr(mod, 'export', export)
            if hasattr(mod, 'init'):
                mod.init()
            rv[name] = export
        sys.path = orig_path
        return rv

    def init_devices():
        rv = {}
        for name in config.devices:
            p = DeviceProxy(name, config.devices[name])
            rv[name] = p
        return rv

    def init_tables():
        rv = {}
        for table_file in config.tables:
            table_name = os.path.split(table_file)[-1].rsplit('.', 1)[0]
            table = Table(table_name, table_file)
            rv[table_name] = table
        return rv

    def ensure_datadir(device):
        datadir = os.path.join(config.data_path, device)
        if not os.access(datadir, os.R_OK):
            os.mkdir(datadir)
        return datadir

    def get_filename(device, table):
        return os.path.join(config.data_path, device, table) + '.csv'

    def update_device(device):
        log("updating: " + device)
        export = drivers[devices[device].config['driver']]
        has_table, get_table = export.get('has_table'), export.get('get_table')
        if not has_table or not get_table:
            log("device does not implement basic functionality: " + device)
            return
        ensure_datadir(device)
        for table in tables:
            if not has_table(table):
                continue
            log("updating table %s on: %s" % (table, device))
            tio = TableIO(tables[table], get_filename(device, table))
            rowlen = len(tables[table].columns)
            tio.write(get_table(table, tio, devices[device]), rowlen)

    drivers = init_drivers()
    devices = init_devices()
    tables = init_tables()
    map(update_device, devices)
    # commit changes to the data repository
    monotone_commit()