#############################################################################
## This file is part of Ostendi ##
##-------------------------------------------------------------------------##
## Copyright 2007-2009 Bryce Schroeder http://www.ostendi.org ##
## bryce.schroeder@gmail.com ##
## http://www.ferazelhosting.net/~bryce/ ##
##-------------------------------------------------------------------------##
## This program is free software: you can redistribute it and/or modify ##
## it under the terms of the GNU General Public License as published by ##
## the Free Software Foundation, either version 3 of the License, or ##
## (at your option) any later version. ##
## This program is distributed in the hope that it will be useful, ##
## but WITHOUT ANY WARRANTY; without even the implied warranty of ##
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ##
## GNU General Public License for more details. ##
## You should have received a copy of the GNU General Public License ##
## along with this program. If not, see . ##
#############################################################################
# first got this working on 2 April 2009, 23:21. Seemed like an important
# event since this project has been basically blocked on that for the last
# year!
#
# Anyway, this is a summary of what remains to be done in net and db for
# basic functionality to be complete:
# Correct security, reading and writing modes (db.rw etc)
# db.update / rpc
# support for attributes other than int, float and string!
# at a minimum, we need a nice way to transmit lists dicts and tuples,
# references to other db objects, and references to ostendo resources.
# regenerating local variables when an object is incarnated by the server,
# - make sure this works.
import net
# Standard database messages
class MsgDBNew(net.Message):
kind='DBNEW'
parameters={'zone': net.litstr,'name': net.litstr, 'classname': net.litstr}
class MsgDBUpdate(net.Message):
kind="DBUPDATE"
parameters={'zone': net.litstr, 'name': net.litstr, 'attrname': net.litstr,
'attrval': net.typed}
class MsgDBReady(net.Message):
"""Identify a zone as ready for use."""
kind="DBREADY"
parameters={'zone': net.litstr}
class MsgDBPriv(net.Message):
kind="DBPRIV"
parameters={'name': net.litstr, 'priv': int}
class MsgDBPull(net.Message):
"""Join a zone."""
kind="DBPULL"
parameters={'zone': net.litstr}
rw, r, w, private = 'rw','r','w','private'
update = 'update'
class DBType(object):
"""
This is the parent class of the various database type objects.
"""
_implements_dbtype_ = True
def __init__(self, *props):
self.props = props
def check(self, nvalue):
"""This function is called when the object to which the variable
belongs recieves a request to change it via __setattr__. The
change be checked for local validity here."""
# FIXME: type and local permission error check would go here.
return True
def bind(self, db, name, host):
self.db=db
self.name = name
self.host = host
class datom(DBType):
pass
class dint(datom):
pass
class dfloat(datom):
pass
class dstr(datom):
pass
class dtuple(DBType):
pass
class dlist(DBType):
pass
class ddict(DBType):
pass
class dobj(DBType):
pass
class dres(DBType):
pass
class ddtyp(DBType):
pass
class DBHandler(net.NetHandler):
"""
This class handles the interfacing between the net module and the
database module.
"""
def __init__(self, database):
self.db = database
def DBPULL(self, msg):
print "Received DBPULL, zone=<%s>."%msg.zone
self.db.push_zone(msg.get_sender(), msg.zone)
def DBNEW(self, msg):
print "Received DBNEW, %s/%s = %s()"%(msg.zone,msg.name,msg.classname)
self.db.create(msg.zone, msg.name, msg.classname)
def DBUPDATE(self,msg):
#print "msg", msg, msg.__dict__
print "Received DBUPDATE, %s/%s/%s = <%s>"%(
msg.zone, msg.name, msg.attrname, msg.attrval)
self.db.update(msg.zone, msg.name, msg.attrname, msg.attrval)
def DBREADY(self,msg):
print "Received DBREADY for zone %s"%msg.zone
self.db.getzone(msg.zone).ready()
class DBObject(object):
"""
This class is to be the basis for all objects which are to be found
in the database.
"""
_db_bound = False
def __init__(self, *args, **kwargs):
self.dbatr = {}
self.dbargs = args
self.dbkwargs = kwargs
def _db_bind(self, db, zone):
self.db = db
self.dbzone = zone
for name,thing in self.__class__.__dict__.items():
#print "*", name, thing, hasattr(thing, '_implements_dbtype_')
if hasattr(thing, '_implements_dbtype_'):
thing.bind(db,name,self)
self.dbatr[name] = thing
self.init(*self.dbargs, **self.dbkwargs)
del self.dbargs
del self.dbkwargs
self._db_bound = True
def _db_push(self, netw, requestor):
netw.emit(MsgDBNew(name=self.name, zone=self.dbzone.name,
classname=self.__class__.__name__), requestor)
for name,atr in self.dbatr.items():
netw.emit(MsgDBUpdate(zone=self.dbzone.name,
name=self.name, attrname=name,
attrval=self.__dict__[name]),
requestor)
def _db_update(self, atr, value):
object.__setattr__(self, atr, value)
def __setattr__(self, name, value):
#print "SETATTR", self._db_bound
#if self._db_bound: print self.dbatr
if self._db_bound and name in self.dbatr:
if self.dbatr[name].check(value):
self.db.changed(self.dbzone, self, self.dbatr[name], value)
object.__setattr__(self, name, value)
else:
object.__setattr__(self, name, value)
def init(self):
pass
class DBZone(object):
"""
This class represents a zone in the database.
"""
def __init__(self, name, db):
self.name = name
self.db = db
self.content = {}
# the members are the clients that need to be kept apprised of
# updates in this zone.
self.members = []
self.complete = bool(db.server)
def register(self, object, name=None):
if not name: name = self.db.gsn()
object.name = name
object._db_bind(self.db, self)
self.content[object.name] = object
return name
def ready(self):
self.complete = True
def is_ready(self):
return self.complete
def push(self,requestor):
"""Adds a client to this zone."""
self.members.append(requestor)
for name,obj in self.content.items():
obj._db_push(self.net,requestor)
self.db.net.emit(MsgDBReady(zone=self.name))
def exit(self,client):
"""Removes a client from the list of zone members."""
self.members.remove(client)
def has(self, object):
"""Returns True if this object is in the zone."""
return object in content.values()
def hasn(self, oname):
"""Returns True if an object of oname is in the zone."""
return oname in content
def update(self, obj, atr, nval):
self.content[obj]._db_update(atr, nval)
def items(self):
return self.content.items()
class DB(object):
"""
This class represents a networked database in this program.
"""
def __init__(self, *classes):
self.classes = dict([(clas.__name__, clas) for clas in classes])
self.server = not self.classes
self.zones = {}
self.sn = 0
def create(self, zone, obj, oclass):
nobj = self.classes[oclass]()
self.register(nobj, zone, obj)
def gsn(self):
self.sn += 1
return str(self.sn)
def bind(self, network, hClass = DBHandler):
self.net = network
network.extend_manifest([MsgDBReady,MsgDBPriv,MsgDBNew,
MsgDBPull,MsgDBUpdate])
self.net.attach_handler(hClass(self))
if self.server: self.mkzone('default')
def join(self, zone='default'):
"""As a client, join a zone of the connected server."""
self.net.emit(MsgDBPull(zone=zone))
self.mkzone(zone)
return self.zones[zone]
def push_zone(self, requestor, zone):
"""Push out the contents of a zone to the requestor."""
self.zones[zone].push(requestor)
def changed(self, zone, obj, atr, nval):
"""This is called anytime anything changes from inside..."""
print "MESSAGE: %s/%s/%s = <%s>"%(zone.name,obj.name,atr.name,nval)
self.net.emit(MsgDBUpdate(zone=zone.name,name=obj.name,
attrname=atr.name, attrval=nval))
def update(self, zone, obj, atr, nval):
self.zones[zone].update(obj, atr, nval)
print "UPDATEM: %s/%s/%s = <%s>"%(zone,obj,atr,nval)
def mkzone(self, name):
"""Create a new zone in this database. mkzone on the name of an
already existing zone will clear it."""
self.zones[name] = DBZone(name,self)
self.zones[name].net = self.net
def register(self, object, zone='default', name=None):
"""Adds