def populate_payload(self, _container=None): """ Translates the contents of this object into a payload for sending across to the storage entity. :return: the populated form """ if _container: container = _container else: container = Form() if self.about: container.add_field(var=str(RDF.about), value=str(self.about), ftype='text-single') if len(self._types): container.add_field(var=str(RDF.type), value=self._types, ftype='list-multi') for key, value in self._properties.iteritems(): property_field = container.add_field(var=str(key), value=value, ftype='list-multi') type_stanza = FormValidation() type_stanza['datatype'] = 'xs:string' property_field.append(type_stanza) for key, value in self._references.iteritems(): reference_field = container.add_field(var=str(key), value=value, ftype='list-multi') type_stanza = FormValidation() type_stanza['datatype'] = 'xs:anyURI' reference_field.append(type_stanza) for key, value in self._flags.iteritems(): container.add_field(var=key.var, value=value, ftype=key.field_type) return container
def __init__(self, container=None): """ Constructor. :param container: optional container to populate values from. """ self._results = [] if not container: self._container = Form() else: self._container = container self._unpack_container()
class ResultCollectionPayload: """ Collection of result payloads. """ def __init__(self, container=None): """ Constructor. :param container: optional container to populate values from. """ self._results = [] if not container: self._container = Form() else: self._container = container self._unpack_container() def append(self, *args): """ Append a result to the collection. :param result: :return: """ self._results += args def _unpack_container(self): """ Unpack the container into the internal data structures. """ self._results = [] reported_values = self._container.get_reported() for item in self._container.get_items(): logger.debug('item: %s' % item) about = None types = None flags = dict() columns = dict() for key, value in item.iteritems(): if key == str(RDF.about): about = value elif key == str(RDF.type): types = value else: reported_item = reported_values[key] if reported_item['validate']['datatype']: columns[_ColumnKey(key, reported_item['validate']['datatype'])] = value else: flags[Flag(*(key, reported_item['type'], None))] = value result_payload = ResultPayload(about=about, types=types) for key, value in flags.iteritems(): result_payload.add_flag(key, value) for key, value in columns.iteritems(): result_payload.add_column(key.key, value, key.data_type) self.append(result_payload) def populate_payload(self): """ Populate the data structures into the container for this object, and return it. :return: transmittable data structure. """ self._container.clear() self._container.add_reported(var=str(RDF.about), ftype='list-multi') self._container.add_reported(var=str(RDF.type), ftype='list-multi') additional_flags = set() additional_columns = set() for result in self._results: additional_flags.update(result.flags.keys()) additional_columns.update(result.columns.keys()) for flag_value in additional_flags: self._container.add_reported(var=flag_value.var, ftype=flag_value.field_type) for column_value in additional_columns: reported = self._container.add_reported(var=column_value.key, ftype='list-multi') validation = FormValidation() validation['datatype'] = column_value.data_type reported.append(validation) for result in self._results: parameters = { str(RDF.about): str(result.about), str(RDF.type): result.types } for key, value in result.flags.iteritems(): parameters[key.var] = value for key, value in result.columns.iteritems(): parameters[key.key] = value self._container.add_item(parameters) return self._container @property def results(self): """ Retrieve the results. :return: list of result payloads """ return self._results
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))