1 Xmpp4Js.Lang.namespace( "Xmpp4Js.Roster" );
  2 
  3 /**
  4  * @constructor
  5  */
  6 Xmpp4Js.Roster.RosterItemManager = function() {
  7     /**
  8      * organized by jid, including resource
  9      * @type Xmpp4Js.Roster.RosterEntry Array of Xmpp4Js.Roster.RosterEntry.
 10      */
 11     this.map = {};
 12       
 13     this.addEvents({
 14         /**
 15          * A new entry was added.
 16          * @param {Xmpp4Js.Roster.RosterEntry} newEntry
 17          */
 18         "add" : true,
 19         /**
 20          * An entry was updated.
 21          * @param {Xmpp4Js.Roster.RosterEntry} oldEntry
 22          * @param {Xmpp4Js.Roster.RosterEntry} newEntry
 23          */
 24         "update" : true,
 25         /**
 26          * An entry was removed.
 27          * @param {Xmpp4Js.Roster.RosterEntry} deletedEntry
 28          */
 29         "remove" : true
 30     });
 31 }
 32 
 33 Xmpp4Js.Roster.RosterItemManager.prototype = {
 34     /**
 35      * Get a roster entry by JID. Roster entries are not stored by resource, 
 36      * so it is always stripped to a bare JID.
 37      *
 38      * @param {String or Xmpp4Js.Jid} jid
 39      * @return Xmpp4Js.Roster.RosterEntry
 40      */
 41     get: function(jid) {
 42         jid = new Xmpp4Js.Jid(jid).withoutResource().toString();
 43     
 44         return this.map[jid];
 45     },
 46     
 47     /**
 48      * Get a roster group by name. See documentation on
 49      * return type for exact details.
 50      *
 51      * @param {String} name The name of the group
 52      * @return Xmpp4Js.Roster.RosterGroup
 53      */
 54     getGroup: function(name) {
 55         var groups = this.getGroups();
 56         for( var i = 0; i < groups.length; i++ ) {
 57             if( groups[i].name == name ) {
 58                 return groups[i];
 59             }
 60         }
 61     },
 62     
 63     /**
 64      * Get an array of all groups
 65      *
 66      * @return Xmpp4Js.Roster.RosterGroup[] an array of Groups
 67      */
 68     getGroups: function() {
 69         var retGroups = [];
 70         var groupNames = {};
 71         
 72         retGroups.push( this.getUnfiledContacts() );
 73         
 74         for( var k in this.map) {
 75             var entry = this.map[k];
 76             for(var i = 0; i < entry.groups.length; i++) {
 77                 var groupName = entry.groups[i];
 78                 if( groupNames[groupName] == undefined ) {
 79                     groupNames[ groupName ] = 1;
 80                     retGroups.push( new Xmpp4Js.Roster.RosterGroup( groupName, this ) );
 81                 }
 82             };
 83             
 84         };
 85         
 86         return retGroups;
 87     },
 88     
 89     /**
 90      * Get the special unfiled contacts group, and all entries that
 91      * are not in any group.
 92      *
 93      * @return Xmpp4Js.Roster.RosterGroup
 94      */
 95     getUnfiledContacts: function(){
 96         return new Xmpp4Js.Roster.UnfiledEntriesRosterGroup(this);
 97     },
 98     
 99     /**
100      * Fires events after add/modify and before delete.
101      * Items are stored without taking resource into account.
102      * 
103      * @param {String or Xmpp4Js.Jid} jid
104      * @param {String} alias
105      * @param {String} subscription
106      * @param {String} ask
107      * @param {String Array} groups
108      */
109     update: function(jid, alias, subscription, ask, groups) {
110         jid = new Xmpp4Js.Jid(jid).withoutResource().toString();
111     
112         // TODO find out if this is ever sent from the server
113         if( subscription == "remove" ) {
114             this.remove(jid);
115             return;
116         }
117         var newEntry = new Xmpp4Js.Roster.RosterEntry(jid, alias, subscription, ask, groups, this);
118         var currentEntry = this.map[jid];
119         
120         // replace current entry with new one.
121         this.map[jid] = newEntry;
122         
123         if( currentEntry == undefined ) {
124             this.fireEvent( "add", newEntry );
125         } else {
126             this.fireEvent( "update", currentEntry, newEntry );
127         }
128         
129         return newEntry;
130     },
131     
132     /**
133      * Remove a contact from the roster. Fires the remove event and
134      * deletes the entry from the local map.
135      */
136     remove: function(jid) {
137         var entry = this.map[jid];
138 
139         this.fireEvent( "remove", entry );
140         
141         delete this.map[jid];
142     },
143 
144     /**
145      * Listens for regular roster packets and calls update as needed.
146      *
147      * FIXME This must be manually added to a connection.
148      *
149      * @param {Xmpp4Js.Packet.IQ} packet Could be  class of roster packet.
150      */
151     rosterPacketListener: function( packet ) {
152         var itemNodes = packet.getQuery().getElementsByTagName("item");
153         for ( var i=0; i < itemNodes.getLength(); i++ ) {
154             var item = itemNodes.item(i);
155     
156             var jid = item.getAttribute( "jid" ).toString();
157             var name = item.getAttribute( "name" ).toString();
158             var subscription = item.getAttribute( "subscription" ).toString(); // none, to, from, both, remove
159             var ask = item.getAttribute( "ask" ).toString(); // subscribe, unsubscribe         
160     
161             var groups = [];
162     
163             var groupNodes = item.getElementsByTagName("group");
164             for( var j = 0; j < groupNodes.getLength(); j++ ) {
165                 var node = groupNodes.item(j);
166                 groups.push( node.getStringValue() );
167             }
168 
169             this.update( jid, name, subscription, ask, groups );
170         }
171         
172     },
173 
174     /**
175      * Listens for roster information from legacy services. That is, 
176      * these items exist on a transport's legacy roster but not necessarily 
177      * in this account's native jabber roster. Calls update as needed.
178      *
179      * FIXME This must be manually added to a connection.
180      *
181      *
182      * See this document: http://delx.net.au/projects/pymsnt/jep/roster-subsync/
183      *
184      * @param {Xmpp4Js.Packet.IQ} packet Could be  class of roster packet.
185      */
186     rosterSubSyncPacketListener: function( presence ) {
187         var subsyncNode = presence.getNode().getElementsByTagNameNS("http://jabber.org/protocol/roster-subsync", "x").item(0);
188         // TODO create a subsync packet filter.
189         if( !subsyncNode ) {
190             return;
191         }
192         
193         // in subsync the JID is on the presence element, and not the item element.
194         var jid = presence.getFrom();
195         
196         var itemNodes = subsyncNode.getElementsByTagName("item");
197             
198         for ( var i=0; i < itemNodes.getLength(); i++ ) {
199             var item = itemNodes.item(i);
200     
201             var name = item.getAttribute( "name" ).toString();
202             var subscription = item.getAttribute( "subscription" ).toString(); // none, to, from, both, remove
203             var ask = item.getAttribute( "ask" ).toString(); // subscribe, unsubscribe         
204     
205             var groups = [];
206     
207             var groupNodes = item.getElementsByTagName("group");
208             for( var j = 0; j < groupNodes.getLength(); j++ ) {
209                 var node = groupNodes.item(j);
210                 groups.push( node.getStringValue() );
211             }
212 
213             this.update( jid, name, subscription, ask, groups );
214         }
215     }
216 }
217 
218 Xmpp4Js.Lang.extend( Xmpp4Js.Roster.RosterItemManager, Xmpp4Js.Event.EventProvider, Xmpp4Js.Roster.RosterItemManager.prototype);
219