############################################################################# ## 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 to the database of the specified zone. """ return self.zones[zone].register(object, name) def deregister(self, object, zone='default'): """Removes from the . Specify zone=None to remove it from whichever zone(s) in which it is present.""" if zone: self.zones[zone].deregister(object) else: for zone in self.zones.values(): zone.deregister(object) def has(self, object, zone='default'): """Does exist in ? zone=None for all zones.""" if zone: return self.zones[zone].has(object) else: return any([zone.has(object) for zone in self.zones.values()]) def where(self,object): """In which zone if any does exist?""" for zone in self.zones: if zone.has(object): return zone.name return None def getzone(self,name): return self.zones[name]