StropheCappuccino API 1.0.0
/tmp/tempDoc.doc/TNStropheRoster.j
Go to the documentation of this file.
00001 /*
00002  * TNStropheRoster.j
00003  *
00004  * Copyright (C) 2010  Antoine Mercadal <antoine.mercadal@inframonde.eu>
00005  *
00006  * This library is free software; you can redistribute it and/or
00007  * modify it under the terms of the GNU Lesser General Public
00008  * License as published by the Free Software Foundation; either
00009  * version 3.0 of the License, or (at your option) any later version.
00010  *
00011  * This library is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014  * Lesser General Public License for more details.
00015  *
00016  * You should have received a copy of the GNU Lesser General Public
00017  * License along with this library; if not, write to the Free Software
00018  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
00019  */
00020 
00021 
00022 
00023 TNStropheRosterRosterDelimiter = @"::";
00024 
00025 TNStropheRosterContactAddedNotification                 = @"TNStropheRosterContactAddedNotification";
00026 TNStropheRosterContactRemovedNotification               = @"TNStropheRosterContactRemovedNotification";
00027 TNStropheRosterGroupAddedNotification                   = @"TNStropheRosterGroupAddedNotification";
00028 TNStropheRosterGroupRemovedNotification                 = @"TNStropheRosterGroupRemovedNotification";
00029 TNStropheRosterGroupRenamedNotification                 = @"TNStropheRosterGroupRenamedNotification";
00030 TNStropheRosterPushAddedContactNotification             = @"TNStropheRosterPushAddedContactNotification";
00031 TNStropheRosterPushNotification                         = @"TNStropheRosterPushNotification";
00032 TNStropheRosterPushRemovedContactNotification           = @"TNStropheRosterPushRemovedContactNotification";
00033 TNStropheRosterPushUpdatedContactNotification           = @"TNStropheRosterPushUpdatedContactNotification";
00034 TNStropheRosterRetrievedNotification                    = @"TNStropheRosterRetrievedNotification";
00035 TNStropheRosterSubGroupDelimiterReceivedNotification    = @"TNStropheRosterSubGroupDelimiterReceivedNotification";
00036 
00037 
00041 @implementation TNStropheRoster : TNStropheRosterBase
00042 {
00043     CPDictionary    _pendingPresence;
00044 
00045     BOOL            _rosterRetrieved;
00046 }
00047 
00048 
00049 #pragma mark -
00050 #pragma mark Class methods
00051 
00052 + (id)rosterWithConnection:(TNStropheConnection)aConnection
00053 {
00054     return [[TNStropheRoster alloc] initWithConnection:aConnection];
00055 }
00056 
00057 
00058 #pragma mark -
00059 #pragma mark Initialization
00060 
00064 - (id)initWithConnection:(TNStropheConnection)aConnection
00065 {
00066     if (self = [super initWithConnection:aConnection])
00067     {
00068         _pendingPresence        = [CPDictionary dictionary];
00069 
00070         var rosterPushParams    = [CPDictionary dictionaryWithObjectsAndKeys:@"iq", @"name", Strophe.NS.ROSTER, @"namespace", @"set", @"type"],
00071             presenceParams      = [CPDictionary dictionaryWithObjectsAndKeys:@"presence", @"name", [[_connection JID] bare] || [CPNull null], @"to"];
00072 
00073         [_connection registerSelector:@selector(_didReceiveRosterPush:) ofObject:self withDict:rosterPushParams];
00074         [_connection registerSelector:@selector(_didReceivePresence:) ofObject:self withDict:presenceParams];
00075     }
00076 
00077     return self;
00078 }
00079 
00082 - (void)clear
00083 {
00084     for (var i = 0; i < [_groupCache count]; i++)
00085         [[_groupCache objectAtIndex:i] removeSubGroups];
00086 
00087     [_groupCache removeAllObjects];
00088     [_pendingPresence removeAllObjects];
00089     _rosterRetrieved = NO;
00090     [super clear];
00091 }
00092 
00093 
00094 #pragma mark -
00095 #pragma mark Nested roster groups
00096 
00099 - (void)getSubGroupDelimiter
00100 {
00101     var uid     = [_connection getUniqueId],
00102         stanza  = [TNStropheStanza iqWithAttributes:{@"id": uid, @"type": @"get"}],
00103         params  = [CPDictionary dictionaryWithObjectsAndKeys:uid, @"id"];
00104 
00105     [stanza addChildWithName:@"query" andAttributes:{@"xmlns": @"jabber:iq:private"}];
00106     [stanza addChildWithName:@"roster" andAttributes:{@"xmlns": @"roster:delimiter"}];
00107     [_connection registerSelector:@selector(_didReceiveSubGroupDelimiter:) ofObject:self withDict:params];
00108     [_connection send:stanza];
00109 }
00110 
00114 - (BOOL)_didReceiveSubGroupDelimiter:(TNStropheStanza)aStanza
00115 {
00116     if ([aStanza type] == @"result")
00117     {
00118         var delimiter = [[aStanza firstChildWithName:@"roster"] text];
00119 
00120         if ((!delimiter == @"") && (!delimiter == @" "))
00121             TNStropheRosterRosterDelimiter = delimiter;
00122         else
00123             [self setSubGroupDelimiter:TNStropheRosterRosterDelimiter];
00124     }
00125     else
00126     {
00127         CPLog.error("Cannot get the roster delimiter")
00128     }
00129 
00130     [[CPNotificationCenter defaultCenter] postNotificationName:TNStropheRosterSubGroupDelimiterReceivedNotification object:self];
00131 
00132     return NO;
00133 }
00134 
00138 - (void)setSubGroupDelimiter:(CPString)aDelimiter
00139 {
00140     var uid     = [_connection getUniqueId],
00141         stanza  = [TNStropheStanza iqWithAttributes:{@"id": uid, @"type": @"set"}],
00142         params  = [CPDictionary dictionaryWithObjectsAndKeys:uid, @"id"];
00143 
00144     [stanza addChildWithName:@"query" andAttributes:{@"xmlns": @"jabber:iq:private"}];
00145     [stanza addChildWithName:@"roster" andAttributes:{@"xmlns": @"roster:delimiter"}];
00146     [stanza addTextNode:aDelimiter];
00147     [_connection registerSelector:@selector(_didsetSubGroupDelimiter:) ofObject:self withDict:params];
00148     [_connection send:stanza];
00149 }
00150 
00154 - (BOOL)_didsetSubGroupDelimiter:(TNStropheStanza)aStanza
00155 {
00156     if ([aStanza type] == @"result")
00157         CPLog.info("roster's subgroup delimiter has been set");
00158     else
00159         CPLog.error("Cannot get the roster delimiter")
00160 
00161     return NO;
00162 }
00163 
00164 
00165 #pragma mark -
00166 #pragma mark Fetch / Push
00167 
00170 - (void)getRoster
00171 {
00172     var uid                 = [_connection getUniqueIdWithSuffix:@"roster"],
00173         rosteriq            = [TNStropheStanza iqWithAttributes:{@"id": uid, @"type": @"get"}],
00174         rosterResultParams  = [CPDictionary dictionaryWithObjectsAndKeys:uid, @"id"];
00175 
00176     [rosteriq addChildWithName:@"query" andAttributes:{'xmlns':Strophe.NS.ROSTER}];
00177     [_connection registerSelector:@selector(_didReceiveRosterResult:) ofObject:self withDict:rosterResultParams];
00178     [_connection send:rosteriq];
00179 }
00180 
00184 - (BOOL)_didReceiveRosterResult:(id)aStanza
00185 {
00186     var items = [aStanza childrenWithName:@"item"];
00187 
00188     for (var i = 0; i < [items count]; i++)
00189     {
00190         var item            = [items objectAtIndex:i],
00191             subscription    = [item valueForAttribute:@"subscription"],
00192             allowedSubs     = [CPArray arrayWithObjects:@"none", @"to", @"from", @"both"];
00193 
00194         if (!subscription || ![allowedSubs containsObject:subscription])
00195             [item setValue:@"none" forAttribute:@"subscription"];
00196 
00197         if (![self containsJID:[TNStropheJID stropheJIDWithString:[item valueForAttribute:@"jid"]]])
00198             [self _addContactFromRosterItem:item];
00199     }
00200 
00201     _rosterRetrieved = YES;
00202 
00203     [[CPNotificationCenter defaultCenter] postNotificationName:TNStropheRosterRetrievedNotification object:self];
00204 
00205     return NO;
00206 }
00207 
00211 - (void)_sendRosterSet:(id)anObject
00212 {
00213     var contents;
00214     switch ([anObject class])
00215     {
00216         case TNStropheContact:
00217             contents = [CPArray arrayWithObject:anObject];
00218             break;
00219         case _CPJavaScriptArray:
00220         case CPArray:
00221             contents = anObject;
00222             break;
00223         default:
00224             CPLog.error("_sendRosterSet: only accepts TNStropheContact or CPArray. given " + [anObject class]);
00225             return;
00226     }
00227 
00228     var stanza = [TNStropheStanza iqWithAttributes:{"type": "set"}];
00229 
00230     [stanza addChildWithName:@"query" andAttributes:{'xmlns':Strophe.NS.ROSTER}];
00231 
00232     for (var i = 0; i < [contents count]; i++)
00233     {
00234         var contact = [contents objectAtIndex:i];
00235         [stanza addChildWithName:@"item" andAttributes:{"jid": [[contact JID] bare], "name": [contact nickname]}];
00236 
00237         for (var j = 0; j < [[contact groups] count]; j++)
00238         {
00239             [stanza addChildWithName:@"group"];
00240             [stanza addTextNode:[[[contact groups] objectAtIndex:j] path]];
00241             [stanza up];
00242         }
00243 
00244         [stanza up];
00245     }
00246     [_connection send:stanza];
00247 }
00248 
00252 - (void)_sendRosterUnset:(id)anObject
00253 {
00254     var contents;
00255     switch ([anObject class])
00256     {
00257         case TNStropheContact:
00258             contents = [CPArray arrayWithObject:anObject];
00259             break;
00260         case _CPJavaScriptArray:
00261         case CPArray:
00262             contents = anObject;
00263             break;
00264         default:
00265             CPLog.error("_sendRosterUnset: only accepts TNStropheContact or CPArray. given " + [anObject class]);
00266             return;
00267     }
00268     var stanza  = [TNStropheStanza iqWithAttributes:{"type": "set"}];
00269 
00270     [stanza addChildWithName:@"query" andAttributes: {'xmlns':Strophe.NS.ROSTER}];
00271 
00272     for (var j = 0; j < [contents count]; j++)
00273     {
00274         var contact = [contents objectAtIndex:j];
00275 
00276         [stanza addChildWithName:@"item" andAttributes:{'jid': [[contact JID] bare], 'subscription': 'remove'}];
00277         [stanza up];
00278     }
00279 
00280     [_connection send:stanza];
00281 }
00282 
00286 - (BOOL)_didReceiveRosterPush:(TNStropheStanzas)aStanza
00287 {
00288     if (!_rosterRetrieved)
00289         return;
00290 
00291     var item            = [aStanza firstChildWithName:@"item"],
00292         theJID          = [TNStropheJID stropheJIDWithString:[item valueForAttribute:@"jid"]],
00293         subscription    = [item valueForAttribute:@"subscription"],
00294         allowedSubs     = [CPArray arrayWithObjects:@"none", @"to", @"from", @"both", @"remove"],
00295         response        = [TNStropheStanza iqTo:[aStanza from] withAttributes:{@"id": [aStanza ID], @"type": @"result"}];
00296 
00301     if ([aStanza from] && (![[aStanza from] bareEquals:[_connection JID]]))
00302         return YES;
00303 
00307     [_connection send:response];
00308 
00309     if (!subscription || ![allowedSubs containsObject:subscription])
00310         [item setValue:@"none" forAttribute:@"subscription"];
00311 
00312     switch (subscription)
00313     {
00314         case @"remove":
00315             [self _deleteContactFromRosterItem:item];
00316             [[CPNotificationCenter defaultCenter] postNotificationName:TNStropheRosterPushRemovedContactNotification object:self userInfo:theJID];
00317             break;
00318 
00319         default:
00320             if ([self containsJID:theJID])
00321             {
00322                 [self _updateContactFromRosterItem:item];
00323                 [[CPNotificationCenter defaultCenter] postNotificationName:TNStropheRosterPushUpdatedContactNotification object:self userInfo:theJID];
00324             }
00325             else
00326             {
00327                 [self _addContactFromRosterItem:item];
00328                 [[CPNotificationCenter defaultCenter] postNotificationName:TNStropheRosterPushAddedContactNotification object:self userInfo:theJID];
00329             }
00330             break;
00331     }
00332 
00333     [[CPNotificationCenter defaultCenter] postNotificationName:TNStropheRosterPushNotification object:self];
00334 
00335     return YES;
00336 }
00337 
00342 - (void)_addContactFromRosterItem:(TNXMLNode)aRosterItem
00343 {
00344     var theJID = [TNStropheJID stropheJIDWithString:[aRosterItem valueForAttribute:@"jid"]];
00345 
00346     if ([theJID bareEquals:[_connection JID]])
00347         return;
00348 
00349     var contact         = [TNStropheContact contactWithConnection:_connection JID:theJID group:nil],
00350         nickname        = [aRosterItem valueForAttribute:@"name"] || [theJID node],
00351         groupNodes      = [aRosterItem childrenWithName:@"group"],
00352         queuedPresence  = [_pendingPresence valueForKey:[theJID bare]],
00353         subscription    = [aRosterItem valueForAttribute:@"subscription"];
00354 
00355     [contact setDelegate:_delegate];
00356 
00357     if ([groupNodes count])
00358     {
00359         for (var i = 0; i < [groupNodes count]; i++)
00360         {
00361             var groupsLine = [[[groupNodes objectAtIndex:i] text] uppercaseString],
00362                 currentGroup = [self groupWithPath:groupsLine orCreate:YES];
00363 
00364             [self addContact:contact inGroup:currentGroup push:NO];
00365         }
00366     }
00367     else
00368     {
00369         [_content addObject:contact];
00370     }
00371 
00372     for (var j = 0; j < [queuedPresence count]; j++)
00373         [contact _didReceivePresence:[queuedPresence objectAtIndex:j]];
00374 
00375     [contact setNickname:nickname];
00376     [contact setSubscription:subscription];
00377     [contact getMessages];
00378 
00379     [self cacheContact:contact];
00380 
00381     return contact;
00382 }
00383 
00388 - (void)_updateContactFromRosterItem:(TNXMLNode)aRosterItem
00389 {
00390     var theJID = [TNStropheJID stropheJIDWithString:[aRosterItem valueForAttribute:@"jid"]];
00391 
00392     if ([theJID bareEquals:[_connection JID]])
00393         return;
00394 
00395     var contact         = [self contactWithJID:theJID],
00396         subscription    = [aRosterItem valueForAttribute:@"subscription"];
00397 
00398     var nickname    = [aRosterItem valueForAttribute:@"name"] || nil,
00399         groupNodes  = [aRosterItem childrenWithName:@"group"],
00400         groups      = [CPArray array],
00401         oldGroups   = [[contact groups] copy];
00402 
00403     if (nickname && nickname != [contact nickname])
00404         [contact setNickname:nickname];
00405 
00406     // flush current groups or from root area
00407     if ([_content containsObject:contact])
00408         [_content removeObject:contact];
00409 
00410     for (var i = 0; i < [oldGroups count]; i++)
00411         [self removeContact:contact fromGroup:[oldGroups objectAtIndex:i] push:NO];
00412 
00413     // readd contact in new groups
00414     for (var i = 0; i < [groupNodes count]; i++)
00415     {
00416         var currentGroupName = [[[groupNodes objectAtIndex:i] text] uppercaseString],
00417             currentGroup = [self groupWithPath:currentGroupName orCreate:YES];
00418         [self addContact:contact inGroup:currentGroup push:NO];
00419     }
00420 
00421     // if not in any groups, readd contcat in root area
00422     if ([[contact groups] count] == 0)
00423         [_content addObject:contact];
00424 
00425     return contact;
00426 }
00427 
00431 - (void)_deleteContactFromRosterItem:(TNXMLNode)aRosterItem
00432 {
00433     var theJID = [TNStropheJID stropheJIDWithString:[aRosterItem valueForAttribute:@"jid"]],
00434         contact = [self contactWithJID:theJID],
00435         groups = [contact groups];
00436 
00437     if (!groups || [groups count] == 0)
00438         [_content removeObject:contact];
00439     else
00440         for (var i = 0; i < [groups count]; i++)
00441             [[[groups objectAtIndex:i] contacts] removeObject:contact];
00442 
00443     [self uncacheContact:contact];
00444 }
00445 
00446 
00447 #pragma mark -
00448 #pragma mark Presence
00449 
00454 - (BOOL)_didReceivePresence:(TNStropheStanza)aStanza
00455 {
00456     if ([aStanza type] === @"subscribe")
00457     {
00458         if ([_delegate respondsToSelector:@selector(roster:receiveSubscriptionRequest:)])
00459             [_delegate roster:self receiveSubscriptionRequest:aStanza];
00460         return YES;
00461     }
00462 
00463     if ([self containsJID:[aStanza from]])
00464         [[self contactWithJID:[aStanza from]] _didReceivePresence:aStanza];
00465     else
00466     {
00467         var from = [aStanza fromBare];
00468         if (![_pendingPresence containsKey:from])
00469             [_pendingPresence setValue:[CPArray array] forKey:from];
00470         [[_pendingPresence valueForKey:from] addObject:aStanza];
00471     }
00472 
00473     return YES;
00474 }
00475 
00476 
00477 #pragma mark -
00478 #pragma mark Groups
00479 
00484 - (TNStropheGroup)groupWithPath:(CPString)aPath
00485 {
00486     var path = [aPath uppercaseString].split(TNStropheRosterRosterDelimiter),
00487         currentGroup = [self rootGroupWithName:path[0]],
00488         lastGroup = [self _subGroupWithPath:[path copy].splice(1, path.length - 1) relativeTo:currentGroup];
00489     return ([lastGroup path] == aPath) ? lastGroup : nil;
00490 }
00491 
00494 - (TNStropheGroup)_subGroupWithPath:(CPArray)aPath relativeTo:(TNStropheGroup)aGroup
00495 {
00496     var subGroup = [aGroup subGroupWithName:[aPath[0] uppercaseString]];
00497     if (subGroup)
00498         return [self _subGroupWithPath:aPath.splice(1, aPath.length - 1) relativeTo:subGroup];
00499     else
00500         return aGroup;
00501 }
00502 
00508 - (TNStropheGroup)groupWithPath:(CPArray)aPath orCreate:(BOOL)shouldCreate
00509 {
00510     aPath = [aPath uppercaseString];
00511     var group = [self groupWithPath:aPath];
00512     if (!group && shouldCreate)
00513     {
00514         [self addGroupWithPath:aPath];
00515         group = [self groupWithPath:aPath];
00516     }
00517 
00518     return group;
00519 }
00520 
00525 - (void)addGroupWithPath:(CPString)aPath
00526 {
00527     aPath = [aPath uppercaseString];
00528     var path = [aPath uppercaseString].split(TNStropheRosterRosterDelimiter);
00529 
00530     if ([self groupWithPath:aPath])
00531         return;
00532 
00533     for (var i = 0; i < [path count]; i++)
00534     {
00535         var currentPath = [path copy].splice(0, i + 1).join(TNStropheRosterRosterDelimiter),
00536             parentPath = [path copy].splice(0, i).join(TNStropheRosterRosterDelimiter),
00537             currentGroup = [self groupWithPath:currentPath],
00538             parentGroup = [self groupWithPath:parentPath];
00539 
00540         if (!currentGroup)
00541         {
00542             var tokens = [currentPath uppercaseString].split(TNStropheRosterRosterDelimiter),
00543                 groupName = [tokens lastObject],
00544                 currentGroup =  [TNStropheGroup stropheGroupWithName:groupName];
00545 
00546             [_groupCache addObject:currentGroup];
00547 
00548             if (parentGroup)
00549                 [parentGroup addSubGroup:currentGroup];
00550             else
00551                 [_content addObject:currentGroup];
00552         }
00553     }
00554 
00555     [[CPNotificationCenter defaultCenter] postNotificationName:TNStropheRosterGroupAddedNotification object:[self groupWithPath:aPath]];
00556 }
00557 
00562 - (void)removeGroupWithPath:(CPString)aPath
00563 {
00564     aPath = [aPath uppercaseString];
00565     var group = [self groupWithPath:aPath],
00566         parentGroup = [group parentGroup];
00567 
00568     if (!parentGroup)
00569     {
00570         [group removeSubGroups];
00571         [_content removeObject:group]
00572     }
00573     else
00574     {
00575         [parentGroup removeSubGroup:group];
00576     }
00577 
00578     [_groupCache removeObject:group];
00579     [[CPNotificationCenter defaultCenter] postNotificationName:TNStropheRosterGroupRemovedNotification object:group];
00580 }
00581 
00585 - (void)removeGroup:(TNStropheGroup)aGroup
00586 {
00587     [self removeGroupWithPath:[[aGroup path] uppercaseString]]
00588 }
00589 
00594 - (void)changeName:(CPString)aName ofGroup:(TNStropheGroup)aGroup
00595 {
00596     var affectedContacts = [self getAllContactsTreeFromGroup:aGroup];
00597 
00598     [aGroup setName:[aName uppercaseString]];
00599     [self _sendRosterSet:[self getAllContactsTreeFromGroup:aGroup]];
00600     [[CPNotificationCenter defaultCenter] postNotificationName:TNStropheRosterGroupRenamedNotification object:self];
00601 }
00602 
00607 - (TNStropheGroup)rootGroupWithName:(CPString)aGroupName
00608 {
00609     aGroupName = [aGroupName uppercaseString];
00610 
00611     for (var i = 0; i < [_content count]; i++)
00612     {
00613         if (![[_content objectAtIndex:i] isKindOfClass:TNStropheGroup])
00614             continue;
00615 
00616         var group = [_content objectAtIndex:i];
00617 
00618         if ([group name] == aGroupName)
00619             return group;
00620     }
00621     return nil;
00622 }
00623 
00624 
00625 #pragma mark -
00626 #pragma mark Contacts
00627 
00633 - (void)addContact:(TNStropheJID)aJID withName:(CPString)aName inGroupWithPath:(CPString)aGroupPath
00634 {
00635     if ([self containsJID:aJID])
00636     {
00637         CPLog.warn("Cannot add JID %@. already in roster", aJID);
00638         return;
00639     }
00640 
00641     if (!aName)
00642         aName = [aJID bare];
00643 
00644     var group       = aGroupPath ? [self groupWithPath:aGroupPath orCreate:YES] : nil,
00645         contact     = [TNStropheContact contactWithConnection:_connection JID:aJID group:group];
00646 
00647     [contact setDelegate:_delegate];
00648     [contact setNickname:aName];
00649     [self _sendRosterSet:contact];
00650 }
00651 
00655 - (void)removeContact:(TNStropheContact)aContact
00656 {
00657     [self _sendRosterUnset:aContact];
00658 }
00659 
00665 - (void)changeNickname:(CPString)newNickname ofContact:(TNStropheContact)aContact
00666 {
00667     var oldNickname = [aContact nickname];
00668     [aContact setNickname:newNickname];
00669     [self _sendRosterSet:aContact];
00670     [[CPNotificationCenter defaultCenter] postNotificationName:TNStropheContactNicknameUpdatedNotification object:aContact];
00671     [aContact setNickname:oldNickname];
00672 }
00673 
00678 - (CPArray)getAllContactsTreeFromGroup:(TNStropheGroup)aGroup
00679 {
00680     var returnArray = [CPArray array];
00681     return [self _getAllContactsTreeFromGroup:aGroup array:returnArray];
00682 }
00683 
00686 - (CPArray)_getAllContactsTreeFromGroup:(TNStropheGroup)aGroup array:(CPArray)returnArray
00687 {
00688     var subgroups = [aGroup subGroups];
00689     [returnArray addObjectsFromArray:[aGroup contacts]];
00690     for (var i = 0; i < [aGroup subGroupsCount]; i++)
00691         returnArray = [self _getAllContactsTreeFromGroup:[[aGroup subGroups] objectAtIndex:i] array:returnArray];
00692     return returnArray;
00693 }
00694 
00695 
00696 #pragma mark -
00697 #pragma mark Group assignation
00698 
00704 - (void)addContact:(TNStropheContact)aContact inGroup:(TNStropheGroup)aGroup push:(BOOL)shouldPush
00705 {
00706     if ([[aContact groups] containsObject:aGroup])
00707         return;
00708 
00709     [[aContact groups] addObject:aGroup];
00710     [[aGroup contacts] addObject:aContact];
00711 
00712     if (shouldPush)
00713     {
00714         [self _sendRosterSet:aContact];
00715         [[aContact groups] removeObject:aGroup];
00716         [[aGroup contacts] removeObject:aContact];
00717     }
00718 
00719     [[CPNotificationCenter defaultCenter] postNotificationName:TNStropheContactGroupUpdatedNotification object:aContact];
00720 }
00721 
00727 - (void)removeContact:(TNStropheContact)aContact fromGroup:(TNStropheGroup)aGroup push:(BOOL)shouldPush
00728 {
00729     var groupsOfContact = [aContact groups];
00730 
00731     if (![groupsOfContact containsObject:aGroup])
00732         return;
00733 
00734     [groupsOfContact removeObject:aGroup];
00735     [[aGroup contacts] removeObject:aContact];
00736 
00737     if (shouldPush)
00738     {
00739         [self _sendRosterSet:aContact];
00740         [groupsOfContact addObject:aGroup];
00741         [[aGroup contacts] addObject:aContact];
00742     }
00743 
00744     [[CPNotificationCenter defaultCenter] postNotificationName:TNStropheContactGroupUpdatedNotification object:aContact];
00745 }
00746 
00751 - (void)setGroups:(CPArray)someGroups ofContact:(TNStropheContact)aContact
00752 {
00753     var groupsOfContact = [aContact groups],
00754         oldGroups = [groupsOfContact copy];
00755 
00756     [aContact setGroups:someGroups || [CPArray array]];
00757     [self _sendRosterSet:aContact];
00758     [aContact setGroups:oldGroups];
00759 }
00760 
00766 - (void)moveGroup:(TNStropheGroup)aGroup intoGroup:(TNStropheGroup)anotherGroup
00767 {
00768     if (aGroup === anotherGroup)
00769         return;
00770 
00771     if ([aGroup parentGroup])
00772         [[aGroup parentGroup] removeSubGroup:aGroup];
00773     else
00774         [_content removeObject:aGroup];
00775 
00776     if (anotherGroup)
00777         [anotherGroup addSubGroup:aGroup];
00778     else if (![_content containsObject:aGroup])
00779         [_content addObject:aGroup];
00780 
00781     [self _sendRosterSet:[self getAllContactsTreeFromGroup:anotherGroup]];
00782 }
00783 
00784 
00785 #pragma mark -
00786 #pragma mark Subscriptions
00787 
00791 - (void)authorizeJID:(TNStropheJID)aJID
00792 {
00793     var contact = [self contactWithJID:aJID];
00794 
00795     if (!contact)
00796         return;
00797 
00798     [contact subscribe];
00799 }
00800 
00804 - (void)unauthorizeJID:(TNStropheJID)aJID
00805 {
00806     var contact = [self contactWithJID:aJID];
00807 
00808     if (!contact)
00809         return;
00810 
00811     [contact unsubscribe];
00812 }
00813 
00817 - (void)askAuthorizationTo:(TNStropheJID)aJID
00818 {
00819     var contact = [self contactWithJID:aJID];
00820 
00821     if (!contact)
00822         return;
00823 
00824     [contact askSubscription];
00825 }
00826 
00831 - (void)answerAuthorizationRequest:(id)aStanza answer:(BOOL)theAnswer
00832 {
00833     var requester   = [aStanza from],
00834         nick        = [aStanza firstChildWithName:@"nick"];
00835 
00836     if (theAnswer == YES)
00837     {
00838         [self authorizeJID:requester];
00839         [self askAuthorizationTo:requester];
00840 
00841         if (![self containsJID:requester])
00842             [self addContact:requester withName:(nick ? [nick text] : [requester bare]) inGroupWithPath:nil];
00843     }
00844     else
00845         [self unauthorizeJID:requester];
00846 }
00847 
00848 @end
00849 
00850 @implementation TNStropheRoster (CPSynthesizedAccessors)
00851 
00855 - (CPDictionary)pendingPresence
00856 {
00857     return _pendingPresence;
00858 }
00859 
00860 @end
 All Classes Namespaces Files Functions Variables