From 5ab77c745270d7d5c016c1dc7ef2a82533a4b16e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 17 Jul 2014 14:19:04 +0200 Subject: Rename to slixmpp --- slixmpp/roster/__init__.py | 11 + slixmpp/roster/item.py | 497 +++++++++++++++++++++++++++++++++++++++++++++ slixmpp/roster/multi.py | 224 ++++++++++++++++++++ slixmpp/roster/single.py | 337 ++++++++++++++++++++++++++++++ 4 files changed, 1069 insertions(+) create mode 100644 slixmpp/roster/__init__.py create mode 100644 slixmpp/roster/item.py create mode 100644 slixmpp/roster/multi.py create mode 100644 slixmpp/roster/single.py (limited to 'slixmpp/roster') diff --git a/slixmpp/roster/__init__.py b/slixmpp/roster/__init__.py new file mode 100644 index 00000000..9a9b69a1 --- /dev/null +++ b/slixmpp/roster/__init__.py @@ -0,0 +1,11 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.roster.item import RosterItem +from slixmpp.roster.single import RosterNode +from slixmpp.roster.multi import Roster diff --git a/slixmpp/roster/item.py b/slixmpp/roster/item.py new file mode 100644 index 00000000..c1eb574a --- /dev/null +++ b/slixmpp/roster/item.py @@ -0,0 +1,497 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + + +class RosterItem(object): + + """ + A RosterItem is a single entry in a roster node, and tracks + the subscription state and user annotations of a single JID. + + Roster items may use an external datastore to persist roster data + across sessions. Client applications will not need to use this + functionality, but is intended for components that do not have their + roster persisted automatically by the XMPP server. + + Roster items provide many methods for handling incoming presence + stanzas that ensure that response stanzas are sent according to + RFC 3921. + + The external datastore is accessed through a provided interface + object which is stored in self.db. The interface object MUST + provide two methods: load and save, both of which are responsible + for working with a single roster item. A private dictionary, + self._db_state, is used to store any metadata needed by the + interface, such as the row ID of a roster item, etc. + + Interface for self.db.load: + load(owner_jid, jid, db_state): + owner_jid -- The JID that owns the roster. + jid -- The JID of the roster item. + db_state -- A dictionary containing any data saved + by the interface object after a save() + call. Will typically have the equivalent + of a 'row_id' value. + + Interface for self.db.save: + save(owner_jid, jid, item_state, db_state): + owner_jid -- The JID that owns the roster. + jid -- The JID of the roster item. + item_state -- A dictionary containing the fields: + 'from', 'to', 'pending_in', 'pending_out', + 'whitelisted', 'subscription', 'name', + and 'groups'. + db_state -- A dictionary provided for persisting + datastore specific information. Typically, + a value equivalent to 'row_id' will be + stored here. + + State Fields: + from -- Indicates if a subscription of type 'from' + has been authorized. + to -- Indicates if a subscription of type 'to' has + been authorized. + pending_in -- Indicates if a subscription request has been + received from this JID and it has not been + authorized yet. + pending_out -- Indicates if a subscription request has been sent + to this JID and it has not been accepted yet. + subscription -- Returns one of: 'to', 'from', 'both', or 'none' + based on the states of from, to, pending_in, + and pending_out. Assignment to this value does + not affect the states of the other values. + whitelisted -- Indicates if a subscription request from this + JID should be automatically accepted. + name -- A user supplied alias for the JID. + groups -- A list of group names for the JID. + + Attributes: + xmpp -- The main Slixmpp instance. + owner -- The JID that owns the roster. + jid -- The JID for the roster item. + db -- Optional datastore interface object. + last_status -- The last presence sent to this JID. + resources -- A dictionary of online resources for this JID. + Will contain the fields 'show', 'status', + and 'priority'. + + Methods: + load -- Retrieve the roster item from an + external datastore, if one was provided. + save -- Save the roster item to an external + datastore, if one was provided. + remove -- Remove a subscription to the JID and revoke + its whitelisted status. + subscribe -- Subscribe to the JID. + authorize -- Accept a subscription from the JID. + unauthorize -- Deny a subscription from the JID. + unsubscribe -- Unsubscribe from the JID. + send_presence -- Send a directed presence to the JID. + send_last_presence -- Resend the last sent presence. + handle_available -- Update the JID's resource information. + handle_unavailable -- Update the JID's resource information. + handle_subscribe -- Handle a subscription request. + handle_subscribed -- Handle a notice that a subscription request + was authorized by the JID. + handle_unsubscribe -- Handle an unsubscribe request. + handle_unsubscribed -- Handle a notice that a subscription was + removed by the JID. + handle_probe -- Handle a presence probe query. + """ + + def __init__(self, xmpp, jid, owner=None, + state=None, db=None, roster=None): + """ + Create a new roster item. + + Arguments: + xmpp -- The main Slixmpp instance. + jid -- The item's JID. + owner -- The roster owner's JID. Defaults + so self.xmpp.boundjid.bare. + state -- A dictionary of initial state values. + db -- An optional interface to an external datastore. + roster -- The roster object containing this entry. + """ + self.xmpp = xmpp + self.jid = jid + self.owner = owner or self.xmpp.boundjid.bare + self.last_status = None + self.resources = {} + self.roster = roster + self.db = db + self._state = state or { + 'from': False, + 'to': False, + 'pending_in': False, + 'pending_out': False, + 'whitelisted': False, + 'subscription': 'none', + 'name': '', + 'groups': []} + + self._db_state = {} + self.load() + + def set_backend(self, db=None, save=True): + """ + Set the datastore interface object for the roster item. + + Arguments: + db -- The new datastore interface. + save -- If True, save the existing state to the new + backend datastore. Defaults to True. + """ + self.db = db + if save: + self.save() + self.load() + + def load(self): + """ + Load the item's state information from an external datastore, + if one has been provided. + """ + if self.db: + item = self.db.load(self.owner, self.jid, + self._db_state) + if item: + self['name'] = item['name'] + self['groups'] = item['groups'] + self['from'] = item['from'] + self['to'] = item['to'] + self['whitelisted'] = item['whitelisted'] + self['pending_out'] = item['pending_out'] + self['pending_in'] = item['pending_in'] + self['subscription'] = self._subscription() + return self._state + return None + + def save(self, remove=False): + """ + Save the item's state information to an external datastore, + if one has been provided. + + Arguments: + remove -- If True, expunge the item from the datastore. + """ + self['subscription'] = self._subscription() + if remove: + self._state['removed'] = True + if self.db: + self.db.save(self.owner, self.jid, + self._state, self._db_state) + + # Finally, remove the in-memory copy if needed. + if remove: + del self.xmpp.roster[self.owner][self.jid] + + def __getitem__(self, key): + """Return a state field's value.""" + if key in self._state: + if key == 'subscription': + return self._subscription() + return self._state[key] + else: + raise KeyError + + def __setitem__(self, key, value): + """ + Set the value of a state field. + + For boolean states, the values True, 'true', '1', 'on', + and 'yes' are accepted as True; all others are False. + + Arguments: + key -- The state field to modify. + value -- The new value of the state field. + """ + if key in self._state: + if key in ['name', 'subscription', 'groups']: + self._state[key] = value + else: + value = str(value).lower() + self._state[key] = value in ('true', '1', 'on', 'yes') + else: + raise KeyError + + def _subscription(self): + """Return the proper subscription type based on current state.""" + if self['to'] and self['from']: + return 'both' + elif self['from']: + return 'from' + elif self['to']: + return 'to' + else: + return 'none' + + def remove(self): + """ + Remove a JID's whitelisted status and unsubscribe if a + subscription exists. + """ + if self['to']: + p = self.xmpp.Presence() + p['to'] = self.jid + p['type'] = 'unsubscribe' + if self.xmpp.is_component: + p['from'] = self.owner + p.send() + self['to'] = False + self['whitelisted'] = False + self.save() + + def subscribe(self): + """Send a subscription request to the JID.""" + p = self.xmpp.Presence() + p['to'] = self.jid + p['type'] = 'subscribe' + if self.xmpp.is_component: + p['from'] = self.owner + self['pending_out'] = True + self.save() + p.send() + + def authorize(self): + """Authorize a received subscription request from the JID.""" + self['from'] = True + self['pending_in'] = False + self.save() + self._subscribed() + self.send_last_presence() + + def unauthorize(self): + """Deny a received subscription request from the JID.""" + self['from'] = False + self['pending_in'] = False + self.save() + self._unsubscribed() + p = self.xmpp.Presence() + p['to'] = self.jid + p['type'] = 'unavailable' + if self.xmpp.is_component: + p['from'] = self.owner + p.send() + + def _subscribed(self): + """Handle acknowledging a subscription.""" + p = self.xmpp.Presence() + p['to'] = self.jid + p['type'] = 'subscribed' + if self.xmpp.is_component: + p['from'] = self.owner + p.send() + + def unsubscribe(self): + """Unsubscribe from the JID.""" + p = self.xmpp.Presence() + p['to'] = self.jid + p['type'] = 'unsubscribe' + if self.xmpp.is_component: + p['from'] = self.owner + self.save() + p.send() + + def _unsubscribed(self): + """Handle acknowledging an unsubscribe request.""" + p = self.xmpp.Presence() + p['to'] = self.jid + p['type'] = 'unsubscribed' + if self.xmpp.is_component: + p['from'] = self.owner + p.send() + + def send_presence(self, **kwargs): + """ + Create, initialize, and send a Presence stanza. + + If no recipient is specified, send the presence immediately. + Otherwise, forward the send request to the recipient's roster + entry for processing. + + Arguments: + pshow -- The presence's show value. + pstatus -- The presence's status message. + ppriority -- This connections' priority. + pto -- The recipient of a directed presence. + pfrom -- The sender of a directed presence, which should + be the owner JID plus resource. + ptype -- The type of presence, such as 'subscribe'. + pnick -- Optional nickname of the presence's sender. + """ + if self.xmpp.is_component and not kwargs.get('pfrom', ''): + kwargs['pfrom'] = self.owner + if not kwargs.get('pto', ''): + kwargs['pto'] = self.jid + self.xmpp.send_presence(**kwargs) + + def send_last_presence(self): + if self.last_status is None: + pres = self.roster.last_status + if pres is None: + self.send_presence() + else: + pres['to'] = self.jid + if self.xmpp.is_component: + pres['from'] = self.owner + else: + del pres['from'] + pres.send() + else: + self.last_status.send() + + def handle_available(self, presence): + resource = presence['from'].resource + data = {'status': presence['status'], + 'show': presence['show'], + 'priority': presence['priority']} + got_online = not self.resources + if resource not in self.resources: + self.resources[resource] = {} + old_status = self.resources[resource].get('status', '') + old_show = self.resources[resource].get('show', None) + self.resources[resource].update(data) + if got_online: + self.xmpp.event('got_online', presence) + if old_show != presence['show'] or old_status != presence['status']: + self.xmpp.event('changed_status', presence) + + def handle_unavailable(self, presence): + resource = presence['from'].resource + if not self.resources: + return + if resource in self.resources: + del self.resources[resource] + self.xmpp.event('changed_status', presence) + if not self.resources: + self.xmpp.event('got_offline', presence) + + def handle_subscribe(self, presence): + """ + +------------------------------------------------------------------+ + | EXISTING STATE | DELIVER? | NEW STATE | + +------------------------------------------------------------------+ + | "None" | yes | "None + Pending In" | + | "None + Pending Out" | yes | "None + Pending Out/In" | + | "None + Pending In" | no | no state change | + | "None + Pending Out/In" | no | no state change | + | "To" | yes | "To + Pending In" | + | "To + Pending In" | no | no state change | + | "From" | no * | no state change | + | "From + Pending Out" | no * | no state change | + | "Both" | no * | no state change | + +------------------------------------------------------------------+ + """ + if self.xmpp.is_component: + if not self['from'] and not self['pending_in']: + self['pending_in'] = True + self.xmpp.event('roster_subscription_request', presence) + elif self['from']: + self._subscribed() + self.save() + else: + #server shouldn't send an invalid subscription request + self.xmpp.event('roster_subscription_request', presence) + + def handle_subscribed(self, presence): + """ + +------------------------------------------------------------------+ + | EXISTING STATE | DELIVER? | NEW STATE | + +------------------------------------------------------------------+ + | "None" | no | no state change | + | "None + Pending Out" | yes | "To" | + | "None + Pending In" | no | no state change | + | "None + Pending Out/In" | yes | "To + Pending In" | + | "To" | no | no state change | + | "To + Pending In" | no | no state change | + | "From" | no | no state change | + | "From + Pending Out" | yes | "Both" | + | "Both" | no | no state change | + +------------------------------------------------------------------+ + """ + if self.xmpp.is_component: + if not self['to'] and self['pending_out']: + self['pending_out'] = False + self['to'] = True + self.xmpp.event('roster_subscription_authorized', presence) + self.save() + else: + self.xmpp.event('roster_subscription_authorized', presence) + + def handle_unsubscribe(self, presence): + """ + +------------------------------------------------------------------+ + | EXISTING STATE | DELIVER? | NEW STATE | + +------------------------------------------------------------------+ + | "None" | no | no state change | + | "None + Pending Out" | no | no state change | + | "None + Pending In" | yes * | "None" | + | "None + Pending Out/In" | yes * | "None + Pending Out" | + | "To" | no | no state change | + | "To + Pending In" | yes * | "To" | + | "From" | yes * | "None" | + | "From + Pending Out" | yes * | "None + Pending Out | + | "Both" | yes * | "To" | + +------------------------------------------------------------------+ + """ + if self.xmpp.is_component: + if not self['from'] and self['pending_in']: + self['pending_in'] = False + self._unsubscribed() + elif self['from']: + self['from'] = False + self._unsubscribed() + self.xmpp.event('roster_subscription_remove', presence) + self.save() + else: + self.xmpp.event('roster_subscription_remove', presence) + + def handle_unsubscribed(self, presence): + """ + +------------------------------------------------------------------+ + | EXISTING STATE | DELIVER? | NEW STATE | + +------------------------------------------------------------------+ + | "None" | no | no state change | + | "None + Pending Out" | yes | "None" | + | "None + Pending In" | no | no state change | + | "None + Pending Out/In" | yes | "None + Pending In" | + | "To" | yes | "None" | + | "To + Pending In" | yes | "None + Pending In" | + | "From" | no | no state change | + | "From + Pending Out" | yes | "From" | + | "Both" | yes | "From" | + +------------------------------------------------------------------ + """ + if self.xmpp.is_component: + if not self['to'] and self['pending_out']: + self['pending_out'] = False + elif self['to'] and not self['pending_out']: + self['to'] = False + self.xmpp.event('roster_subscription_removed', presence) + self.save() + else: + self.xmpp.event('roster_subscription_removed', presence) + + def handle_probe(self, presence): + if self['from']: + self.send_last_presence() + if self['pending_out']: + self.subscribe() + if not self['from']: + self._unsubscribed() + + def reset(self): + """ + Forgot current resource presence information as part of + a roster reset request. + """ + self.resources = {} + + def __repr__(self): + return repr(self._state) diff --git a/slixmpp/roster/multi.py b/slixmpp/roster/multi.py new file mode 100644 index 00000000..e1a44a08 --- /dev/null +++ b/slixmpp/roster/multi.py @@ -0,0 +1,224 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +from slixmpp.stanza import Presence +from slixmpp.xmlstream import JID +from slixmpp.roster import RosterNode + + +class Roster(object): + + """ + Slixmpp's roster manager. + + The roster is divided into "nodes", where each node is responsible + for a single JID. While the distinction is not strictly necessary + for client connections, it is a necessity for components that use + multiple JIDs. + + Rosters may be stored and persisted in an external datastore. An + interface object to the datastore that loads and saves roster items may + be provided. See the documentation for the RosterItem class for the + methods that the datastore interface object must provide. + + Attributes: + xmpp -- The main Slixmpp instance. + db -- Optional interface object to an external datastore. + auto_authorize -- Default auto_authorize value for new roster nodes. + Defaults to True. + auto_subscribe -- Default auto_subscribe value for new roster nodes. + Defaults to True. + + Methods: + add -- Create a new roster node for a JID. + send_presence -- Shortcut for sending a presence stanza. + """ + + def __init__(self, xmpp, db=None): + """ + Create a new roster. + + Arguments: + xmpp -- The main Slixmpp instance. + db -- Optional interface object to a datastore. + """ + self.xmpp = xmpp + self.db = db + self._auto_authorize = True + self._auto_subscribe = True + self._rosters = {} + + if self.db: + for node in self.db.entries(None, {}): + self.add(node) + + self.xmpp.add_filter('out', self._save_last_status) + + def _save_last_status(self, stanza): + + if isinstance(stanza, Presence): + sfrom = stanza['from'].full + sto = stanza['to'].full + + if not sfrom: + sfrom = self.xmpp.boundjid + + if stanza['type'] in stanza.showtypes or \ + stanza['type'] in ('available', 'unavailable'): + if sto: + self[sfrom][sto].last_status = stanza + else: + self[sfrom].last_status = stanza + with self[sfrom]._last_status_lock: + for jid in self[sfrom]: + self[sfrom][jid].last_status = None + + if not self.xmpp.sentpresence: + self.xmpp.event('sent_presence') + self.xmpp.sentpresence = True + + return stanza + + def __getitem__(self, key): + """ + Return the roster node for a JID. + + A new roster node will be created if one + does not already exist. + + Arguments: + key -- Return the roster for this JID. + """ + if key is None: + key = self.xmpp.boundjid + if not isinstance(key, JID): + key = JID(key) + key = key.bare + + if key not in self._rosters: + self.add(key) + self._rosters[key].auto_authorize = self.auto_authorize + self._rosters[key].auto_subscribe = self.auto_subscribe + return self._rosters[key] + + def keys(self): + """Return the JIDs managed by the roster.""" + return self._rosters.keys() + + def __iter__(self): + """Iterate over the roster nodes.""" + return self._rosters.__iter__() + + def add(self, node): + """ + Add a new roster node for the given JID. + + Arguments: + node -- The JID for the new roster node. + """ + if not isinstance(node, JID): + node = JID(node) + + node = node.bare + if node not in self._rosters: + self._rosters[node] = RosterNode(self.xmpp, node, self.db) + + def set_backend(self, db=None, save=True): + """ + Set the datastore interface object for the roster. + + Arguments: + db -- The new datastore interface. + save -- If True, save the existing state to the new + backend datastore. Defaults to True. + """ + self.db = db + existing_entries = set(self._rosters) + new_entries = set(self.db.entries(None, {})) + + for node in existing_entries: + self._rosters[node].set_backend(db, save) + for node in new_entries - existing_entries: + self.add(node) + + def reset(self): + """ + Reset the state of the roster to forget any current + presence information. Useful after a disconnection occurs. + """ + for node in self: + self[node].reset() + + def send_presence(self, **kwargs): + """ + Create, initialize, and send a Presence stanza. + + If no recipient is specified, send the presence immediately. + Otherwise, forward the send request to the recipient's roster + entry for processing. + + Arguments: + pshow -- The presence's show value. + pstatus -- The presence's status message. + ppriority -- This connections' priority. + pto -- The recipient of a directed presence. + pfrom -- The sender of a directed presence, which should + be the owner JID plus resource. + ptype -- The type of presence, such as 'subscribe'. + pnick -- Optional nickname of the presence's sender. + """ + if self.xmpp.is_component and not kwargs.get('pfrom', ''): + kwargs['pfrom'] = self.jid + self.xmpp.send_presence(**kwargs) + + @property + def auto_authorize(self): + """ + Auto accept or deny subscription requests. + + If True, auto accept subscription requests. + If False, auto deny subscription requests. + If None, don't automatically respond. + """ + return self._auto_authorize + + @auto_authorize.setter + def auto_authorize(self, value): + """ + Auto accept or deny subscription requests. + + If True, auto accept subscription requests. + If False, auto deny subscription requests. + If None, don't automatically respond. + """ + self._auto_authorize = value + for node in self._rosters: + self._rosters[node].auto_authorize = value + + @property + def auto_subscribe(self): + """ + Auto send requests for mutual subscriptions. + + If True, auto send mutual subscription requests. + """ + return self._auto_subscribe + + @auto_subscribe.setter + def auto_subscribe(self, value): + """ + Auto send requests for mutual subscriptions. + + If True, auto send mutual subscription requests. + """ + self._auto_subscribe = value + for node in self._rosters: + self._rosters[node].auto_subscribe = value + + def __repr__(self): + return repr(self._rosters) diff --git a/slixmpp/roster/single.py b/slixmpp/roster/single.py new file mode 100644 index 00000000..07a1f188 --- /dev/null +++ b/slixmpp/roster/single.py @@ -0,0 +1,337 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +import threading + +from slixmpp.xmlstream import JID +from slixmpp.roster import RosterItem + + +class RosterNode(object): + + """ + A roster node is a roster for a single JID. + + Attributes: + xmpp -- The main Slixmpp instance. + jid -- The JID that owns the roster node. + db -- Optional interface to an external datastore. + auto_authorize -- Determines how authorizations are handled: + True -- Accept all subscriptions. + False -- Reject all subscriptions. + None -- Subscriptions must be + manually authorized. + Defaults to True. + auto_subscribe -- Determines if bi-directional subscriptions + are created after automatically authrorizing + a subscription request. + Defaults to True + last_status -- The last sent presence status that was broadcast + to all contact JIDs. + + Methods: + add -- Add a JID to the roster. + update -- Update a JID's subscription information. + subscribe -- Subscribe to a JID. + unsubscribe -- Unsubscribe from a JID. + remove -- Remove a JID from the roster. + presence -- Return presence information for a JID's resources. + send_presence -- Shortcut for sending a presence stanza. + """ + + def __init__(self, xmpp, jid, db=None): + """ + Create a roster node for a JID. + + Arguments: + xmpp -- The main Slixmpp instance. + jid -- The JID that owns the roster. + db -- Optional interface to an external datastore. + """ + self.xmpp = xmpp + self.jid = jid + self.db = db + self.auto_authorize = True + self.auto_subscribe = True + self.last_status = None + self._version = '' + self._jids = {} + self._last_status_lock = threading.Lock() + + if self.db: + if hasattr(self.db, 'version'): + self._version = self.db.version(self.jid) + for jid in self.db.entries(self.jid): + self.add(jid) + + @property + def version(self): + """Retrieve the roster's version ID.""" + if self.db and hasattr(self.db, 'version'): + self._version = self.db.version(self.jid) + return self._version + + @version.setter + def version(self, version): + """Set the roster's version ID.""" + self._version = version + if self.db and hasattr(self.db, 'set_version'): + self.db.set_version(self.jid, version) + + def __getitem__(self, key): + """ + Return the roster item for a subscribed JID. + + A new item entry will be created if one does not already exist. + """ + if key is None: + key = JID('') + if not isinstance(key, JID): + key = JID(key) + key = key.bare + if key not in self._jids: + self.add(key, save=True) + return self._jids[key] + + def __delitem__(self, key): + """ + Remove a roster item from the local storage. + + To remove an item from the server, use the remove() method. + """ + if key is None: + key = JID('') + if not isinstance(key, JID): + key = JID(key) + key = key.bare + if key in self._jids: + del self._jids[key] + + def __len__(self): + """Return the number of JIDs referenced by the roster.""" + return len(self._jids) + + def keys(self): + """Return a list of all subscribed JIDs.""" + return self._jids.keys() + + def has_jid(self, jid): + """Returns whether the roster has a JID.""" + return jid in self._jids + + def groups(self): + """Return a dictionary mapping group names to JIDs.""" + result = {} + for jid in self._jids: + groups = self._jids[jid]['groups'] + if not groups: + if '' not in result: + result[''] = [] + result[''].append(jid) + for group in groups: + if group not in result: + result[group] = [] + result[group].append(jid) + return result + + def __iter__(self): + """Iterate over the roster items.""" + return self._jids.__iter__() + + def set_backend(self, db=None, save=True): + """ + Set the datastore interface object for the roster node. + + Arguments: + db -- The new datastore interface. + save -- If True, save the existing state to the new + backend datastore. Defaults to True. + """ + self.db = db + existing_entries = set(self._jids) + new_entries = set(self.db.entries(self.jid, {})) + + for jid in existing_entries: + self._jids[jid].set_backend(db, save) + for jid in new_entries - existing_entries: + self.add(jid) + + def add(self, jid, name='', groups=None, afrom=False, ato=False, + pending_in=False, pending_out=False, whitelisted=False, + save=False): + """ + Add a new roster item entry. + + Arguments: + jid -- The JID for the roster item. + name -- An alias for the JID. + groups -- A list of group names. + afrom -- Indicates if the JID has a subscription state + of 'from'. Defaults to False. + ato -- Indicates if the JID has a subscription state + of 'to'. Defaults to False. + pending_in -- Indicates if the JID has sent a subscription + request to this connection's JID. + Defaults to False. + pending_out -- Indicates if a subscription request has been sent + to this JID. + Defaults to False. + whitelisted -- Indicates if a subscription request from this JID + should be automatically authorized. + Defaults to False. + save -- Indicates if the item should be persisted + immediately to an external datastore, + if one is used. + Defaults to False. + """ + if isinstance(jid, JID): + key = jid.bare + else: + key = jid + + state = {'name': name, + 'groups': groups or [], + 'from': afrom, + 'to': ato, + 'pending_in': pending_in, + 'pending_out': pending_out, + 'whitelisted': whitelisted, + 'subscription': 'none'} + self._jids[key] = RosterItem(self.xmpp, jid, self.jid, + state=state, db=self.db, + roster=self) + if save: + self._jids[key].save() + + def subscribe(self, jid): + """ + Subscribe to the given JID. + + Arguments: + jid -- The JID to subscribe to. + """ + self[jid].subscribe() + + def unsubscribe(self, jid): + """ + Unsubscribe from the given JID. + + Arguments: + jid -- The JID to unsubscribe from. + """ + self[jid].unsubscribe() + + def remove(self, jid): + """ + Remove a JID from the roster. + + Arguments: + jid -- The JID to remove. + """ + self[jid].remove() + if not self.xmpp.is_component: + return self.update(jid, subscription='remove') + + def update(self, jid, name=None, subscription=None, groups=[], + block=True, timeout=None, callback=None): + """ + Update a JID's subscription information. + + Arguments: + jid -- The JID to update. + name -- Optional alias for the JID. + subscription -- The subscription state. May be one of: 'to', + 'from', 'both', 'none', or 'remove'. + groups -- A list of group names. + block -- Specify if the roster request will block + until a response is received, or a timeout + occurs. Defaults to True. + timeout -- The length of time (in seconds) to wait + for a response before continuing if blocking + is used. Defaults to self.response_timeout. + callback -- Optional reference to a stream handler function. + Will be executed when the roster is received. + Implies block=False. + """ + self[jid]['name'] = name + self[jid]['groups'] = groups + self[jid].save() + + if not self.xmpp.is_component: + iq = self.xmpp.Iq() + iq['type'] = 'set' + iq['roster']['items'] = {jid: {'name': name, + 'subscription': subscription, + 'groups': groups}} + + return iq.send(block, timeout, callback) + + def presence(self, jid, resource=None): + """ + Retrieve the presence information of a JID. + + May return either all online resources' status, or + a single resource's status. + + Arguments: + jid -- The JID to lookup. + resource -- Optional resource for returning + only the status of a single connection. + """ + if resource is None: + return self[jid].resources + + default_presence = {'status': '', + 'priority': 0, + 'show': ''} + return self[jid].resources.get(resource, + default_presence) + + def reset(self): + """ + Reset the state of the roster to forget any current + presence information. Useful after a disconnection occurs. + """ + for jid in self: + self[jid].reset() + + def send_presence(self, **kwargs): + """ + Create, initialize, and send a Presence stanza. + + If no recipient is specified, send the presence immediately. + Otherwise, forward the send request to the recipient's roster + entry for processing. + + Arguments: + pshow -- The presence's show value. + pstatus -- The presence's status message. + ppriority -- This connections' priority. + pto -- The recipient of a directed presence. + pfrom -- The sender of a directed presence, which should + be the owner JID plus resource. + ptype -- The type of presence, such as 'subscribe'. + pnick -- Optional nickname of the presence's sender. + """ + if self.xmpp.is_component and not kwargs.get('pfrom', ''): + kwargs['pfrom'] = self.jid + self.xmpp.send_presence(**kwargs) + + def send_last_presence(self): + if self.last_status is None: + self.send_presence() + else: + pres = self.last_status + if self.xmpp.is_component: + pres['from'] = self.jid + else: + del pres['from'] + pres.send() + + def __repr__(self): + return repr(self._jids) -- cgit v1.2.3