class BaseNode(object): nodetype = 'leaf' affiliationtypes = ('owner', 'publisher', 'member', 'outcast', 'pending') default_config = Form(None, title='Leaf Config Form') default_config.addField( 'FORM_TYPE', 'hidden', value='http://jabber.org/protocol/pubsub#node_config') ntype = default_config.addField('pubsub#node_type', 'list-single', label='Select the node type', value='leaf') ntype.addOption('leaf', 'Leaf') default_config.addField('pubsub#title', label='A friendly name for the node') default_config.addField('pubsub#deliver_notifications', 'boolean', label='Deliver event notifications', value=True) default_config.addField('pubsub#deliver_payloads', 'boolean', label='Deliver payloads with event notifications', value=True) default_config.addField( 'pubsub#notify_config', 'boolean', label='Notify subscribers when the node configuration changes', value=False) default_config.addField( 'pubsub#notify_delete', 'boolean', label='Notify subscribers when the node is deleted', value=False) default_config.addField( 'pubsub#notify_retract', 'boolean', label='Notify subscribers when items are removed from the node', value=False) default_config.addField( 'pubsub#notify_sub', 'boolean', label='Notify owners about new subscribers and unsubscribes', value=False) default_config.addField('pubsub#persist_items', 'boolean', label='Persist items in storage', value=False) default_config.addField('pubsub#max_items', label='Max # of items to persist', value='100') default_config.addField('pubsub#expire', label='Expire') default_config.addField('pubsub#subscribe', 'boolean', label='Whether to allow subscriptions', value=True) default_config.addField('pubsub#collection', 'text-multi', label="This node in collections") default_config.addField('sleek#saveonchange', 'boolean', label='Save on every change', value=False) default_config.addField('sleek#dupesubscriptions', 'boolean', label='Allow dupe subscriptions', value=True) model = default_config.addField('pubsub#access_model', 'list-single', label='Specify the subscriber model', value='open') #model.addOption('authorize', 'Authorize') # not yet implemented model.addOption('open', 'Open') model.addOption('whitelist', 'whitelist') model = default_config.addField('pubsub#publish_model', 'list-single', label='Specify the publisher model', value='publishers') model.addOption('publishers', 'Publishers') model.addOption('subscribers', 'Subscribers') model.addOption('open', 'Open') model = default_config.addField('pubsub#send_last_published_item', 'list-single', label='Send last published item', value='never') model.addOption('never', 'Never') model.addOption('on_sub', 'On Subscription') model.addOption('on_sub_and_presence', 'On Subscription And Presence') default_config.addField( 'pubsub#presence_based_delivery', 'boolean', label='Deliver notification only to available users', value=False) del ntype del model item_class = Item itemevent_class = ItemEvent def __init__(self, pubsub, db, name, config=None, owner=None, fresh=False, use_db=True): self.new_owner = owner self.use_db = use_db self.fresh = fresh self.pubsub = pubsub self.xmpp = self.pubsub.xmpp self.db = db self.name = name self.collections = [] self.config = config or copy.copy(self.default_config) self.subscription_form = {} self.publish_form = {} self.items = {} self.itemorder = [] self.synch = True self.state = '' #self.affiliations = {'owner': [], 'publisher': [], 'member': [], 'outcast': [], 'pending': []} self.affiliations = {} for afftype in self.affiliationtypes: self.affiliations[afftype] = [] self.subscriptions = {} self.subscriptionsbyjid = {} if self.new_owner is not None: self.affiliations['owner'].append(self.new_owner) self.dbLoad() self.lastsaved = time.time() if self.pubsub.config['settings'][ 'node_creation'] == 'createonsubscribe': self.use_db = False self.updates_per_second = 0.0 self.recent_updates = 0 self.recent_update_time = time.time() def dbLoad(self): if not self.use_db: return if not self.fresh: self.affiliations = self.db.getAffiliations(self.name) self.items = self.db.getItems(self.name) self.config = pickle.loads(self.db.getNodeConfig(self.name)) parentset = self._checkconfigcollections(self.config, False) if not parentset: logging.warning("Was not able to set all parents in %s" % self.name) subs = self.db.getSubscriptions(self.name) for jid, subid, config in subs: self.subscriptions[subid] = Subscription( self, jid, subid, config) self.subscriptionsbyjid[jid] = self.subscriptions[subid] else: self.db.createNode(self.name, self.config, self.affiliations, self.items) def dbDump(self, save=False): if not self.use_db: return if save: self.db.synch(self.name, pickle.dumps(self.config), self.affiliations, self.items, subscriptions=self.subscriptions) else: self.db._synch(self.name, pickle.dumps(self.config), self.affiliations, self.items, subscriptions=self.subscriptions) self.lastsaved = time.time() def save(self): if not self.use_db: return logging.info("Saving %s" % self.name) #self.dbDump(True) self.db._synch(self.name, pickle.dumps(self.config), self.affiliations, self.items, subscriptions=self.subscriptions, newdb=True) def discoverItems(self): pass def getSubscriptions(self): return self.subscriptions def getAffiliations(self): return self.affiliations def notifyItemState(self, xml, item_id=None, who=None): return msg = self.xmpp.Message() msg['psstate_event']['psstate']['node'] = self.name msg['psstate_event']['psstate']['item'] = item_id msg['psstate_event']['psstate']['payload'] = xml for step in self.eachSubscriber(): for jid, mto in step: msg['from'] = mto msg['to'] = jid msg.send() for affiliation in self.affiliations: if affiliation == 'member': continue for barejid in self.affiliations[affiliation]: resources = self.xmpp.roster.get( barejid, {'presence': {}})['presence'].keys() if resources: for resource in resources: msg['from'] = self.xmpp.boundjid msg['to'] = "%s/%s" % (barejid, resource) msg.send() else: msg['from'] = self.xmpp.boundjid msg['to'] = barejid msg.send() def subscribe(self, jid, who=None, config=None, to=None): if ((who is None or self.xmpp.getjidbare(who) in self.affiliations['owner'] or who.startswith(jid)) and (self.config['pubsub#access_model'] == 'open' or (self.config['pubsub#access_model'] == 'whitelist' and jid in self.affiliations['member']) or (self.xmpp.getjidbare(who) in self.affiliations['owner']))): if not self.config[ 'sleek#dupesubscriptions'] and jid in self.subscriptionsbyjid: return False subid = uuid.uuid4().hex if config is not None: config = ET.tostring(config.getXML('submit')) self.subscriptions[subid] = Subscription(self, jid, subid, config, to) self.subscriptionsbyjid[jid] = self.subscriptions[subid] if self.config['sleek#saveonchange'] and self.use_db: self.db.addSubscription(self.name, jid, subid, config, to) if self.config['pubsub#send_last_published_item'] in ( 'on_sub', 'on_sub_and_presence'): if len(self.itemorder) > 0: event = ItemEvent(self.name, self.items[self.itemorder[0]]) if len(self.itemorder) > 1: for item in self.itemorder[1:]: event.addItem(self.items[item]) self.notifyItem(event, jid) return subid else: return False #TODO modify affiliation def unsubscribe(self, jid, who=None, subid=None): if subid is None: try: subid = self.subscriptionsbyjid[jid].getid() except KeyError: return False if self.config['sleek#saveonchange'] and self.use_db: self.db.deleteSubscription(self.name, jid, subid) try: del self.subscriptions[subid] if self.subscriptionsbyjid[jid].getid() == subid: del self.subscriptionsbyjid[jid] except IndexError(): return False #TODO add error cases #TODO add ACL return True def getSubscriptionOptions(self): pass def setSubscriptionOptions(self): pass def getItems(self, max=0): pass def getLastItem(self, node): pass def eachSubscriber(self, step=1, filterjid=None): "Generator for subscribers." if step < 1: raise ValueError subscriptions = self.subscriptions.keys() result = [] idx = 0 # would enumerate, but num of results isn't necessary the same as len(subscriptions) for subid in subscriptions: subscriber = self.subscriptions[subid] jid = subscriber.getjid() mto = subscriber.getto() if not (filterjid is not None and (filterjid != jid)): if '/' in jid or not self.config.get( 'pubsub#presence_based_delivery', False): result.append((jid, mto)) if idx % step == 0: yield result result = [] idx += 1 else: resources = self.xmpp.roster.get( jid, {'presence': {}})['presence'].keys() random.shuffle(resources) for resource in resources: result.append(("%s/%s" % (jid, resource), mto)) if idx % step == 0: yield result result = [] idx += 1 def publish(self, item, item_id=None, options=None, who=None): self.recent_updates += 1 spent = time.time() - self.recent_update_time if spent >= 10.0: self.updates_per_second = float(self.recent_updates) / spent self.recent_update_time = time.time() self.recent_updates = 1 if not item_id: item_id = uuid.uuid4().hex self.xmpp.schedule("%s::%s::publish" % (self.name, item_id), 0, self._publish, (item, item_id, options, who)) return item_id def _publish(self, item, item_id=None, options=None, who=None): if item.tag == '{http://jabber.org/protocol/pubsub}item': payload = item.getchildren()[0] else: payload = item item_inst = self.item_class(self, item_id, who, payload, options) if self.config.get('pubsub#persist_items', False): if self.config['sleek#saveonchange'] and self.use_db: self.db.setItem(self.name, item_id, payload) self.items[item_id] = item_inst if item_id not in self.itemorder: self.itemorder.append(item_id) else: self.itemorder.append( self.itemorder.pop(self.itemorder.index(item_id))) event = self.itemevent_class(self.name, item_inst) self.notifyItem(event) max_items = int(self.config.get('pubsub#max_items', 0)) if max_items != 0 and len(self.itemorder) > max_items: self.deleteItem(self.itemorder[0]) def deleteItem(self, id): if id in self.items: item = self.items[id] del self.items[id] self.itemorder.pop(self.itemorder.index(id)) self.notifyDelete(ItemEvent(self.name, item)) #TODO: DB def _checkconfigcollections(self, config, reconfigure=True): collections = [] passed = True nodes = config.get('pubsub#collection', []) if type(nodes) != type([]): nodes = [nodes] for node in nodes: if node not in self.pubsub.nodes: passed = False else: collections.append(node) if not reconfigure or passed: self.collections = collections return passed def create(self, config=None): pass def getConfig(self, default=False): return self.config def configure(self, config): if not self._checkconfigcollections(config): raise XMPPError() #TODO make this the right error self.config.update(config) # we do this regardless of cache settings if self.use_db: self.db.synch(self.name, config=pickle.dumps(self.config)) def setState(self, state, who): pass def setItemState(self, item_id, state, who=None): if item_id in self.items: return self.items[item_id].setState(state, who) return False def _saveState(self, xml): pass def _saveItemState(self, node, xml): pass def purgeNodeItems(self): pass def approvePendingSubscription(self, jid): pass def modifySubscriptions(self, jids={}): pass def modifyAffiliations(self, affiliations={}, who=None): if who is not None and who not in self.affiliations['owner']: return False for key in affiliations: if key not in self.affiliationtypes: return False self.affiliations.update(affiliations) if self.config['sleek#saveonchange'] and self.use_db: self.db.synch(self.name, affiliations=self.affiliations) return True def getAffiliations(self, who=None): if who is not None and who not in self.affiliations['owner']: return False return self.affiliations def notifyItem(self, event, filterjid=None): if event.hasNode(self.name): return False event.addNode(self.name) jid = '' msg = self.xmpp.Message() msg['to'] = jid msg['from'] = self.xmpp.boundjid xevent = ET.Element('{http://jabber.org/protocol/pubsub#event}event') items = ET.Element('items', {'node': event.originalnode}) for itemi in event.item: item_id = itemi.name payload = itemi.payload item = ET.Element('item', {'id': item_id}) item.append(payload) items.append(item) xevent.append(items) if payload.tag == '{jabber:client}body': msg['body'] = payload.text msg['type'] = 'chat' else: msg.append(xevent) for toset in self.eachSubscriber(filterjid=filterjid): jid, mto = toset[0] if not event.hasJid(jid): event.addJid(jid) msg['to'] = jid msg['from'] = mto or self.xmpp.boundjid self.xmpp.send(msg) for parent in self.collections: if parent in self.pubsub.nodes: self.pubsub.nodes[parent].notifyItem(event, jid) def notifyConfig(self): pass def notifyDelete(self, event): if event.hasNode(self.name): return False event.addNode(self.name) jid = '' msg = self.xmpp.Message() msg['to'] = jid msg['from'] = self.xmpp.boundjid xevent = ET.Element('{http://jabber.org/protocol/pubsub#event}event') items = ET.Element('items', {'node': event.originalnode}) item = ET.Element('retract', {'id': event.item[0].name}) #item.append(payload) items.append(item) xevent.append(items) msg.append(xevent) for toset in self.eachSubscriber(): jid, mto = toset[0] if not event.hasJid(jid): event.addJid(jid) msg['to'] = jid print "WHAT THE HELL IS", mto, type(mto) msg['from'] = mto or self.xmpp.boundjid self.xmpp.send(msg) for parent in self.collections: if parent in self.pubsub.nodes: self.pubsub.nodes[parent].notifyDelete(event) def delete(self): for sub in self.subscriptions.keys(): del self.subscriptions[sub] for sub in self.subscriptionsbyjid.keys(): del self.subscriptionsbyjid[sub] for item in self.items.keys(): del self.items[item] for collection in self.collections: self.collections.pop(self.collections.index(collection))