- Update
This commit is contained in:
1 parent
d281d765ea
commit
5b074a5176
1261 files changed
+199158
-7303
No files matched your search
@@ -0,0 +1,130 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "XMPPRoom.h"
|
||||
#import "XMPPRoomOccupant.h"
|
||||
#import "XMPPRoomMessageMemoryStorageObject.h"
|
||||
#import "XMPPRoomOccupantMemoryStorageObject.h"
|
||||
|
||||
|
||||
|
||||
@interface XMPPRoomMemoryStorage : NSObject <XMPPRoomStorage>
|
||||
|
||||
- (id)init;
|
||||
|
||||
@property (readonly) XMPPRoom *parent;
|
||||
|
||||
/**
|
||||
* You can optionally extend the XMPPRoomMessageMemoryStorageObject and XMPPRoomOccupantMemoryStorageObject classes.
|
||||
* Then just set the classes here, and your subclasses will automatically get used.
|
||||
*
|
||||
* You must set your desired class(es), if different from default, before you begin using the storage class.
|
||||
**/
|
||||
@property (assign, readwrite) Class messageClass;
|
||||
@property (assign, readwrite) Class occupantClass;
|
||||
|
||||
/**
|
||||
* Returns the occupant with the given full JID.
|
||||
**/
|
||||
- (XMPPRoomOccupantMemoryStorageObject *)occupantForJID:(XMPPJID *)jid;
|
||||
|
||||
/**
|
||||
* Returns the messages in sorted order.
|
||||
* The messages are sorted via the [messageClass compare:] method, which may optionally be overriden in subclasses.
|
||||
**/
|
||||
- (NSArray *)messages;
|
||||
|
||||
/**
|
||||
* Returns the occupants in sorted order.
|
||||
* The occupants are sorted via the [occupantsClass compare:] method, which may optionally be overriden in subclasses.
|
||||
**/
|
||||
- (NSArray *)occupants;
|
||||
|
||||
/**
|
||||
* This method is designed for subclasses of XMPPRoomMessageMemoryStorageObject
|
||||
* which may dynamically change the operation of the overriden compare: method.
|
||||
*
|
||||
* If changed, this method should be invoked to force a resort.
|
||||
**/
|
||||
- (NSArray *)resortMessages;
|
||||
|
||||
/**
|
||||
* This method is designed for subclasses of XMPPRoomOccupantMemoryStorageObject
|
||||
* which may dynamically change the operation of the overriden compare: method.
|
||||
*
|
||||
* If changed, this method should be invoked to force a resort.
|
||||
**/
|
||||
- (NSArray *)resortOccupants;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol XMPPRoomMemoryStorageDelegate <NSObject>
|
||||
@optional
|
||||
|
||||
//
|
||||
// XMPPRoomMemoryStorage automatically uses the delegate(s) of its parent XMPPRoom.
|
||||
//
|
||||
|
||||
/**
|
||||
* Similar to XMPPRoomDelegate's xmppRoom:occupantDidJoin:withPresence: method.
|
||||
*
|
||||
* This method provides the delegate with the occupant storage instance,
|
||||
* as well as the current snapshot of the occupants array,
|
||||
* and the index at which the new occupant was inserted.
|
||||
*
|
||||
* The given occupant will be included in the given array at the given index.
|
||||
**/
|
||||
- (void)xmppRoomMemoryStorage:(XMPPRoomMemoryStorage *)sender
|
||||
occupantDidJoin:(XMPPRoomOccupantMemoryStorageObject *)occupant
|
||||
atIndex:(NSUInteger)index
|
||||
inArray:(NSArray *)allOccupants;
|
||||
|
||||
/**
|
||||
* Similar to XMPPRoomDelegate's xmppRoom:occupantDidLeave:withPresence: method.
|
||||
*
|
||||
* This method provides the delegate with the occupant storage instance,
|
||||
* as well as the current snapshot of the occupants array,
|
||||
* and the index at which the occupant used to reside.
|
||||
*
|
||||
* The given occupant will not be included in the given array.
|
||||
* It's previous location is given by the index.
|
||||
**/
|
||||
- (void)xmppRoomMemoryStorage:(XMPPRoomMemoryStorage *)sender
|
||||
occupantDidLeave:(XMPPRoomOccupantMemoryStorageObject *)occupant
|
||||
atIndex:(NSUInteger)index
|
||||
fromArray:(NSArray *)allOccupants;
|
||||
|
||||
/**
|
||||
* Similar to XMPPRoomDelegate's xmppRoom:occupantDidUpdate:withPresence: method.
|
||||
*
|
||||
* This method provides the delegate with the occupant storage instance,
|
||||
* as well as the current snapshot of the occupants array,
|
||||
* including the old and new index of the occupant.
|
||||
*
|
||||
* The given occupant will be included in the given array at the given newIndex.
|
||||
* If the location of the occupant didn't change, then the oldIndex and newIndex will be the same.
|
||||
**/
|
||||
- (void)xmppRoomMemoryStorage:(XMPPRoomMemoryStorage *)sender
|
||||
occupantDidUpdate:(XMPPRoomOccupantMemoryStorageObject *)occupant
|
||||
fromIndex:(NSUInteger)oldIndex
|
||||
toIndex:(NSUInteger)newIndex
|
||||
inArray:(NSArray *)allOccupants;
|
||||
|
||||
/**
|
||||
* Similar to XMPPRoomDelegate's xmppRoom:didReceiveMessage:fromOccupant: method.
|
||||
*
|
||||
* This method provides the delegate with the occupant and message storage instance,
|
||||
* as well as the current snapshot of the messages array,
|
||||
* including the new index of the message.
|
||||
*
|
||||
* The given message will be included in the given array at the given index.
|
||||
**/
|
||||
- (void)xmppRoomMemoryStorage:(XMPPRoomMemoryStorage *)sender
|
||||
didReceiveMessage:(XMPPRoomMessageMemoryStorageObject *)message
|
||||
fromOccupant:(XMPPRoomOccupantMemoryStorageObject *)occupant
|
||||
atIndex:(NSUInteger)index
|
||||
inArray:(NSArray *)allMessages;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,707 @@
|
||||
#import "XMPPRoomMemoryStorage.h"
|
||||
#import "XMPPRoomPrivate.h"
|
||||
#import "XMPP.h"
|
||||
#import "NSXMLElement+XEP_0203.h"
|
||||
#import "XMPPLogging.h"
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
// Log levels: off, error, warn, info, verbose
|
||||
#if DEBUG
|
||||
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; // | XMPP_LOG_FLAG_TRACE;
|
||||
#else
|
||||
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
|
||||
#endif
|
||||
|
||||
#define AssertPrivateQueue() \
|
||||
NSAssert(dispatch_get_specific(parentQueueTag), @"Private method: MUST run on parentQueue");
|
||||
|
||||
#define AssertParentQueue() \
|
||||
NSAssert(dispatch_get_specific(parentQueueTag), @"Private protocol method: MUST run on parentQueue");
|
||||
|
||||
@interface XMPPRoomMemoryStorage ()
|
||||
{
|
||||
#if __has_feature(objc_arc_weak)
|
||||
__weak XMPPRoom *parent;
|
||||
#else
|
||||
__unsafe_unretained XMPPRoom *parent;
|
||||
#endif
|
||||
dispatch_queue_t parentQueue;
|
||||
void *parentQueueTag;
|
||||
|
||||
NSMutableArray * messages;
|
||||
NSMutableArray * occupantsArray;
|
||||
NSMutableDictionary * occupantsDict;
|
||||
|
||||
Class messageClass;
|
||||
Class occupantClass;
|
||||
}
|
||||
|
||||
@property (readonly) dispatch_queue_t parentQueue;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation XMPPRoomMemoryStorage
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
messages = [[NSMutableArray alloc] init];
|
||||
occupantsArray = [[NSMutableArray alloc] init];
|
||||
occupantsDict = [[NSMutableDictionary alloc] init];
|
||||
|
||||
messageClass = [XMPPRoomMessageMemoryStorageObject class];
|
||||
occupantClass = [XMPPRoomOccupantMemoryStorageObject class];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)configureWithParent:(XMPPRoom *)aParent queue:(dispatch_queue_t)queue
|
||||
{
|
||||
NSParameterAssert(aParent != nil);
|
||||
NSParameterAssert(queue != NULL);
|
||||
|
||||
BOOL result = NO;
|
||||
|
||||
@synchronized(self)
|
||||
{
|
||||
if ((parent == nil) && (parentQueue == NULL))
|
||||
{
|
||||
parent = aParent;
|
||||
parentQueue = queue;
|
||||
parentQueueTag = &parentQueueTag;
|
||||
dispatch_queue_set_specific(parentQueue, parentQueueTag, parentQueueTag, NULL);
|
||||
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_retain(parentQueue);
|
||||
#endif
|
||||
|
||||
result = YES;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (GCDMulticastDelegate <XMPPRoomMemoryStorageDelegate> *)multicastDelegate
|
||||
{
|
||||
return (GCDMulticastDelegate <XMPPRoomMemoryStorageDelegate> *)[parent multicastDelegate];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
if (parentQueue)
|
||||
dispatch_release(parentQueue);
|
||||
#endif
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Properties
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@synthesize messageClass;
|
||||
@synthesize occupantClass;
|
||||
|
||||
- (XMPPRoom *)parent
|
||||
{
|
||||
XMPPRoom *result = nil;
|
||||
|
||||
@synchronized(self) // synchronized with configureWithParent:queue:
|
||||
{
|
||||
result = parent;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (dispatch_queue_t)parentQueue
|
||||
{
|
||||
dispatch_queue_t result = NULL;
|
||||
|
||||
@synchronized(self) // synchronized with configureWithParent:queue:
|
||||
{
|
||||
result = parentQueue;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Internal API
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (BOOL)existsMessage:(XMPPMessage *)message
|
||||
{
|
||||
NSDate *remoteTimestamp = [message delayedDeliveryDate];
|
||||
|
||||
if (remoteTimestamp == nil)
|
||||
{
|
||||
// When the xmpp server sends us a room message, it will always timestamp delayed messages.
|
||||
// For example, when retrieving the discussion history, all messages will include the original timestamp.
|
||||
// If a message doesn't include such timestamp, then we know we're getting it in "real time".
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
if ([messages count] == 0)
|
||||
{
|
||||
// Safety net for binary search algorithm used below
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
// Does this message already exist in the messages array?
|
||||
// How can we tell if two XMPPRoomMessages are the same?
|
||||
//
|
||||
// 1. Same jid
|
||||
// 2. Same text
|
||||
// 3. Same remoteTimestamp
|
||||
// 4. Approximately the same localTimestamps (if existing message doesn't have set remoteTimestamp)
|
||||
//
|
||||
// This is actually a rather difficult question.
|
||||
// What if the same user sends the exact same message multiple times?
|
||||
//
|
||||
// If we first received the message while already in the room, it won't contain a remoteTimestamp.
|
||||
// Returning to the room later and downloading the discussion history will return the same message,
|
||||
// this time with a remote timestamp.
|
||||
//
|
||||
// So if the message doesn't have a remoteTimestamp,
|
||||
// but it's localTimestamp is approximately the same as the remoteTimestamp,
|
||||
// then this is enough evidence to consider the messages the same.
|
||||
|
||||
// Algorithm overview:
|
||||
//
|
||||
// Since the clock of the client and server may be out of sync,
|
||||
// a localTimestamp and remoteTimestamp may be off by several seconds.
|
||||
// So we're going to search a range of messages, bounded by a min and max localTimestamp.
|
||||
//
|
||||
// We find the first message that has a localTimestamp >= minLocalTimestamp.
|
||||
// We then search from there to the first message that has a localTimestamp > maxLocalTimestamp.
|
||||
//
|
||||
// This represents our range of messages to search.
|
||||
// Then we can simply iterate over these messages to see if any have the same jid and text.
|
||||
|
||||
NSDate *minLocalTimestamp = [remoteTimestamp dateByAddingTimeInterval:-60];
|
||||
NSDate *maxLocalTimestamp = [remoteTimestamp dateByAddingTimeInterval: 60];
|
||||
|
||||
// Use binary search to locate first message with localTimestamp >= minLocalTimestamp.
|
||||
|
||||
NSInteger mid;
|
||||
NSInteger min = 0;
|
||||
NSInteger max = [messages count] - 1;
|
||||
|
||||
while (YES)
|
||||
{
|
||||
mid = (min + max) / 2;
|
||||
XMPPRoomMessageMemoryStorageObject *currentMessage = messages[mid];
|
||||
|
||||
NSComparisonResult cmp = [minLocalTimestamp compare:[currentMessage localTimestamp]];
|
||||
if (cmp == NSOrderedAscending)
|
||||
{
|
||||
// minLocalTimestamp < currentMessage.localTimestamp
|
||||
|
||||
if (mid == min)
|
||||
break;
|
||||
else
|
||||
max = mid - 1;
|
||||
}
|
||||
else // Descending || Same
|
||||
{
|
||||
// minLocalTimestamp >= currentMessage.localTimestamp
|
||||
|
||||
if (mid == max) {
|
||||
mid++;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
min = mid + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The 'mid' variable now points to the index of the first message in the sorted messages array
|
||||
// that has a localTimestamp >= minLocalTimestamp.
|
||||
//
|
||||
// We're going to start looking for matching messages here,
|
||||
// and break if we find a message with localTimestamp <= maxLocalTimestamp.
|
||||
|
||||
XMPPJID *messageJid = [message from];
|
||||
NSString *messageBody = [[message elementForName:@"body"] stringValue];
|
||||
|
||||
NSInteger index;
|
||||
for (index = mid; index < [messages count]; index++)
|
||||
{
|
||||
XMPPRoomMessageMemoryStorageObject *currentMessage = messages[index];
|
||||
|
||||
NSComparisonResult cmp = [maxLocalTimestamp compare:[currentMessage localTimestamp]];
|
||||
if (cmp != NSOrderedAscending)
|
||||
{
|
||||
// maxLocalTimestamp >= currentMessage.localTimestamp
|
||||
break;
|
||||
}
|
||||
|
||||
if ([currentMessage.jid isEqualToJID:messageJid])
|
||||
{
|
||||
if ([currentMessage.body isEqualToString:messageBody])
|
||||
{
|
||||
if (currentMessage.remoteTimestamp)
|
||||
{
|
||||
if ([currentMessage.remoteTimestamp isEqualToDate:remoteTimestamp])
|
||||
{
|
||||
// 1. jid matches
|
||||
// 2. body matches
|
||||
// 3. remoteTimestamp matches
|
||||
//
|
||||
// => Incoming message already exists in the array.
|
||||
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 1. jid matches
|
||||
// 2. body matches
|
||||
// 3. existing message in array doesn't have set remoteTimestamp
|
||||
// 4. existing message has approximately the same localTimestamp
|
||||
//
|
||||
// => Incoming message already exists in the array.
|
||||
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSUInteger)insertMessage:(XMPPRoomMessageMemoryStorageObject *)message
|
||||
{
|
||||
NSUInteger count = [messages count];
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
[messages addObject:message];
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Shortcut - Most (if not all) messages are inserted at the end
|
||||
|
||||
XMPPRoomMessageMemoryStorageObject *lastMessage = messages[count - 1];
|
||||
if ([message compare:lastMessage] != NSOrderedAscending)
|
||||
{
|
||||
[messages addObject:message];
|
||||
return count;
|
||||
}
|
||||
|
||||
// Shortcut didn't work.
|
||||
// Find location using binary search algorithm.
|
||||
|
||||
NSInteger mid;
|
||||
NSInteger min = 0;
|
||||
NSInteger max = count - 1;
|
||||
|
||||
while (YES)
|
||||
{
|
||||
mid = (min + max) / 2;
|
||||
XMPPRoomMessageMemoryStorageObject *currentMessage = messages[mid];
|
||||
|
||||
NSComparisonResult cmp = [message compare:currentMessage];
|
||||
if (cmp == NSOrderedAscending)
|
||||
{
|
||||
// message < currentMessage
|
||||
|
||||
if (mid == min)
|
||||
break;
|
||||
else
|
||||
max = mid - 1;
|
||||
}
|
||||
else // Descending || Same
|
||||
{
|
||||
// message >= currentMessage
|
||||
|
||||
if (mid == max) {
|
||||
mid++;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
min = mid + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Algorithm check:
|
||||
//
|
||||
// Insert in array[length] at index (index)
|
||||
// : min, mid, max (cmp_result)
|
||||
//
|
||||
//
|
||||
// Insert in array[3] at index (0)
|
||||
// : 0, 1, 2 (asc)
|
||||
// : 0, 0, 0 (asc) break
|
||||
//
|
||||
// Insert in array[3] at index (1)
|
||||
// : 0, 1, 2 (asc)
|
||||
// : 0, 0, 0 (dsc) mid++, break
|
||||
//
|
||||
// Insert in array[3] at index (2)
|
||||
// : 0, 1, 2 (dsc)
|
||||
// : 2, 2, 2 (asc) break
|
||||
//
|
||||
// Insert in array[3] at index (3)
|
||||
// : 0, 1, 2 (dsc)
|
||||
// : 2, 2, 2 (dsc) mid++, break
|
||||
|
||||
[messages insertObject:message atIndex:mid];
|
||||
return (NSUInteger)mid;
|
||||
}
|
||||
|
||||
- (void)addMessage:(XMPPRoomMessageMemoryStorageObject *)roomMsg
|
||||
{
|
||||
NSUInteger index = [self insertMessage:roomMsg];
|
||||
|
||||
XMPPRoomOccupantMemoryStorageObject *occupant = occupantsDict[[roomMsg jid]];
|
||||
|
||||
XMPPRoomMessageMemoryStorageObject *roomMsgCopy = [roomMsg copy];
|
||||
XMPPRoomOccupantMemoryStorageObject *occupantCopy = [occupant copy];
|
||||
NSArray *messagesCopy = [[NSArray alloc] initWithArray:messages copyItems:YES];
|
||||
|
||||
[[self multicastDelegate] xmppRoomMemoryStorage:self
|
||||
didReceiveMessage:roomMsgCopy
|
||||
fromOccupant:occupantCopy
|
||||
atIndex:index
|
||||
inArray:messagesCopy];
|
||||
}
|
||||
|
||||
- (NSUInteger)insertOccupant:(XMPPRoomOccupantMemoryStorageObject *)occupant
|
||||
{
|
||||
NSUInteger count = [occupantsArray count];
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
[occupantsArray addObject:occupant];
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Find location using binary search algorithm.
|
||||
|
||||
NSInteger mid;
|
||||
NSInteger min = 0;
|
||||
NSInteger max = count - 1;
|
||||
|
||||
while (YES)
|
||||
{
|
||||
mid = (min + max) / 2;
|
||||
XMPPRoomOccupantMemoryStorageObject *currentOccupant = occupantsArray[mid];
|
||||
|
||||
NSComparisonResult cmp = [occupant compare:currentOccupant];
|
||||
if (cmp == NSOrderedAscending)
|
||||
{
|
||||
// occupant < currentOccupant
|
||||
|
||||
if (mid == min)
|
||||
break;
|
||||
else
|
||||
max = mid - 1;
|
||||
}
|
||||
else // Descending || Same
|
||||
{
|
||||
// occupant >= currentOccupant
|
||||
|
||||
if (mid == max) {
|
||||
mid++;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
min = mid + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[occupantsArray insertObject:occupant atIndex:mid];
|
||||
return (NSUInteger)mid;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Public API
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (XMPPRoomOccupantMemoryStorageObject *)occupantForJID:(XMPPJID *)jid
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
if (self.parentQueue == NULL)
|
||||
{
|
||||
// Haven't been attached to parent yet
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (dispatch_get_specific(parentQueueTag))
|
||||
{
|
||||
return occupantsDict[jid];
|
||||
}
|
||||
else
|
||||
{
|
||||
__block XMPPRoomOccupantMemoryStorageObject *occupant = nil;
|
||||
|
||||
dispatch_sync(parentQueue, ^{ @autoreleasepool {
|
||||
|
||||
occupant = [occupantsDict[jid] copy];
|
||||
}});
|
||||
|
||||
return occupant;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)messages
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
if (self.parentQueue == NULL)
|
||||
{
|
||||
// Haven't been attached to parent yet
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (dispatch_get_specific(parentQueueTag))
|
||||
{
|
||||
return messages;
|
||||
}
|
||||
else
|
||||
{
|
||||
__block NSArray *result = nil;
|
||||
|
||||
dispatch_sync(parentQueue, ^{ @autoreleasepool {
|
||||
|
||||
result = [[NSArray alloc] initWithArray:messages copyItems:YES];
|
||||
}});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)occupants
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
if (self.parentQueue == NULL)
|
||||
{
|
||||
// Haven't been attached to parent yet
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (dispatch_get_specific(parentQueueTag))
|
||||
{
|
||||
return occupantsArray;
|
||||
}
|
||||
else
|
||||
{
|
||||
__block NSArray *result = nil;
|
||||
|
||||
dispatch_sync(parentQueue, ^{ @autoreleasepool {
|
||||
|
||||
result = [[NSArray alloc] initWithArray:occupantsArray copyItems:YES];
|
||||
}});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)resortMessages
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
if (self.parentQueue == NULL)
|
||||
{
|
||||
// Haven't been attached to parent yet
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (dispatch_get_specific(parentQueueTag))
|
||||
{
|
||||
[messages sortUsingSelector:@selector(compare:)];
|
||||
return messages;
|
||||
}
|
||||
else
|
||||
{
|
||||
__block NSArray *result = nil;
|
||||
|
||||
dispatch_sync(parentQueue, ^{ @autoreleasepool {
|
||||
|
||||
[messages sortUsingSelector:@selector(compare:)];
|
||||
result = [[NSArray alloc] initWithArray:messages copyItems:YES];
|
||||
}});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)resortOccupants
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
if (self.parentQueue == NULL)
|
||||
{
|
||||
// Haven't been attached to parent yet
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (dispatch_get_specific(parentQueueTag))
|
||||
{
|
||||
[occupantsArray sortUsingSelector:@selector(compare:)];
|
||||
return occupantsArray;
|
||||
}
|
||||
else
|
||||
{
|
||||
__block NSArray *result = nil;
|
||||
|
||||
dispatch_sync(parentQueue, ^{ @autoreleasepool {
|
||||
|
||||
[occupantsArray sortUsingSelector:@selector(compare:)];
|
||||
result = [[NSArray alloc] initWithArray:occupantsArray copyItems:YES];
|
||||
}});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark XMPPRoomStorage Protocol
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)handlePresence:(XMPPPresence *)presence room:(XMPPRoom *)room
|
||||
{
|
||||
XMPPLogTrace();
|
||||
AssertParentQueue();
|
||||
|
||||
XMPPJID *from = [presence from];
|
||||
|
||||
if ([[presence type] isEqualToString:@"unavailable"])
|
||||
{
|
||||
XMPPRoomOccupantMemoryStorageObject *occupant = occupantsDict[from];
|
||||
if (occupant)
|
||||
{
|
||||
// Occupant did leave - remove
|
||||
|
||||
NSUInteger index = [occupantsArray indexOfObjectIdenticalTo:occupant];
|
||||
|
||||
[occupantsArray removeObjectAtIndex:index];
|
||||
[occupantsDict removeObjectForKey:from];
|
||||
|
||||
// Notify delegate(s)
|
||||
|
||||
XMPPRoomOccupantMemoryStorageObject *occupantCopy = [occupant copy];
|
||||
NSArray *occupantsCopy = [[NSArray alloc] initWithArray:occupantsArray copyItems:YES];
|
||||
|
||||
[[self multicastDelegate] xmppRoomMemoryStorage:self
|
||||
occupantDidLeave:occupantCopy
|
||||
atIndex:index
|
||||
fromArray:occupantsCopy];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
XMPPRoomOccupantMemoryStorageObject *occupant = occupantsDict[from];
|
||||
if (occupant == nil)
|
||||
{
|
||||
// Occupant did join - add
|
||||
|
||||
occupant = [[self.occupantClass alloc] initWithPresence:presence];
|
||||
|
||||
NSUInteger index = [self insertOccupant:occupant];
|
||||
occupantsDict[from] = occupant;
|
||||
|
||||
// Notify delegate(s)
|
||||
|
||||
XMPPRoomOccupantMemoryStorageObject *occupantCopy = [occupant copy];
|
||||
NSArray *occupantsCopy = [[NSArray alloc] initWithArray:occupantsArray copyItems:YES];
|
||||
|
||||
[[self multicastDelegate] xmppRoomMemoryStorage:self
|
||||
occupantDidJoin:occupantCopy
|
||||
atIndex:index
|
||||
inArray:occupantsCopy];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Occupant did update - move
|
||||
|
||||
[occupant updateWithPresence:presence];
|
||||
|
||||
NSUInteger oldIndex = [occupantsArray indexOfObjectIdenticalTo:occupant];
|
||||
[occupantsArray removeObjectAtIndex:oldIndex];
|
||||
NSUInteger newIndex = [self insertOccupant:occupant];
|
||||
|
||||
// Notify delegate(s)
|
||||
|
||||
XMPPRoomOccupantMemoryStorageObject *occupantCopy = [occupant copy];
|
||||
NSArray *occupantsCopy = [[NSArray alloc] initWithArray:occupantsArray copyItems:YES];
|
||||
|
||||
[[self multicastDelegate] xmppRoomMemoryStorage:self
|
||||
occupantDidUpdate:occupantCopy
|
||||
fromIndex:oldIndex
|
||||
toIndex:newIndex
|
||||
inArray:occupantsCopy];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleOutgoingMessage:(XMPPMessage *)message room:(XMPPRoom *)room
|
||||
{
|
||||
XMPPLogTrace();
|
||||
AssertParentQueue();
|
||||
|
||||
XMPPJID *msgJID = room.myRoomJID;
|
||||
|
||||
XMPPRoomMessageMemoryStorageObject *roomMsg;
|
||||
roomMsg = [[self.messageClass alloc] initWithOutgoingMessage:message jid:msgJID];
|
||||
|
||||
[self addMessage:roomMsg];
|
||||
}
|
||||
|
||||
- (void)handleIncomingMessage:(XMPPMessage *)message room:(XMPPRoom *)room
|
||||
{
|
||||
XMPPLogTrace();
|
||||
AssertParentQueue();
|
||||
|
||||
XMPPJID *myRoomJID = room.myRoomJID;
|
||||
XMPPJID *messageJID = [message from];
|
||||
|
||||
if ([myRoomJID isEqualToJID:messageJID])
|
||||
{
|
||||
if (![message wasDelayed])
|
||||
{
|
||||
// Ignore - we already stored message in handleOutgoingMessage:room:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ([self existsMessage:message])
|
||||
{
|
||||
XMPPLogVerbose(@"%@: %@ - Duplicate message", THIS_FILE, THIS_METHOD);
|
||||
}
|
||||
else
|
||||
{
|
||||
XMPPRoomMessageMemoryStorageObject *roomMessage = [[self.messageClass alloc] initWithIncomingMessage:message];
|
||||
[self addMessage:roomMessage];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleDidLeaveRoom:(XMPPRoom *)room
|
||||
{
|
||||
XMPPLogTrace();
|
||||
AssertParentQueue();
|
||||
|
||||
[occupantsDict removeAllObjects];
|
||||
[occupantsArray removeAllObjects];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,34 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "XMPPRoomMessage.h"
|
||||
|
||||
|
||||
@interface XMPPRoomMessageMemoryStorageObject : NSObject <XMPPRoomMessage, NSCopying, NSSecureCoding>
|
||||
|
||||
- (id)initWithIncomingMessage:(XMPPMessage *)message;
|
||||
- (id)initWithOutgoingMessage:(XMPPMessage *)message jid:(XMPPJID *)myRoomJID;
|
||||
|
||||
/**
|
||||
* The properties below are documented in the XMPPRoomMessage protocol.
|
||||
**/
|
||||
|
||||
@property (nonatomic, readonly) XMPPMessage *message;
|
||||
|
||||
@property (nonatomic, readonly) XMPPJID * roomJID;
|
||||
|
||||
@property (nonatomic, readonly) XMPPJID * jid;
|
||||
@property (nonatomic, readonly) NSString * nickname;
|
||||
|
||||
@property (nonatomic, readonly) NSDate * localTimestamp;
|
||||
@property (nonatomic, readonly) NSDate * remoteTimestamp;
|
||||
|
||||
@property (nonatomic, readonly) BOOL isFromMe;
|
||||
|
||||
/**
|
||||
* Compares two messages based on the localTimestamp.
|
||||
*
|
||||
* This method provides the ordering used by XMPPRoomMemoryStorage.
|
||||
* Subclasses may override this method to provide an alternative sorting mechanism.
|
||||
**/
|
||||
- (NSComparisonResult)compare:(XMPPRoomMessageMemoryStorageObject *)another;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,175 @@
|
||||
#import "XMPPRoomMessageMemoryStorageObject.h"
|
||||
#import "XMPP.h"
|
||||
#import "NSXMLElement+XEP_0203.h"
|
||||
|
||||
|
||||
@implementation XMPPRoomMessageMemoryStorageObject
|
||||
{
|
||||
XMPPMessage *message;
|
||||
XMPPJID *jid;
|
||||
NSDate *localTimestamp;
|
||||
NSDate *remoteTimestamp;
|
||||
BOOL isFromMe;
|
||||
}
|
||||
|
||||
- (id)initWithIncomingMessage:(XMPPMessage *)inMessage
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
message = inMessage;
|
||||
jid = [inMessage from];
|
||||
isFromMe = NO;
|
||||
|
||||
remoteTimestamp = [inMessage delayedDeliveryDate];
|
||||
if (remoteTimestamp)
|
||||
localTimestamp = remoteTimestamp;
|
||||
else
|
||||
localTimestamp = [[NSDate alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithOutgoingMessage:(XMPPMessage *)inMessage jid:(XMPPJID *)myRoomJID
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
message = inMessage;
|
||||
jid = myRoomJID;
|
||||
isFromMe = YES;
|
||||
|
||||
localTimestamp = [[NSDate alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Encoding, Decoding
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if ! TARGET_OS_IPHONE
|
||||
- (id)replacementObjectForPortCoder:(NSPortCoder *)encoder
|
||||
{
|
||||
if ([encoder isBycopy])
|
||||
return self;
|
||||
else
|
||||
return [super replacementObjectForPortCoder:encoder];
|
||||
// return [NSDistantObject proxyWithLocal:self connection:[encoder connection]];
|
||||
}
|
||||
#endif
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)coder
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
if ([coder allowsKeyedCoding])
|
||||
{
|
||||
if([coder respondsToSelector:@selector(requiresSecureCoding)] &&
|
||||
[coder requiresSecureCoding])
|
||||
{
|
||||
message = [coder decodeObjectOfClass:[XMPPMessage class] forKey:@"message"];
|
||||
jid = [coder decodeObjectOfClass:[XMPPJID class] forKey:@"jid"];
|
||||
localTimestamp = [coder decodeObjectOfClass:[NSDate class] forKey:@"localTimestamp"];
|
||||
remoteTimestamp = [coder decodeObjectOfClass:[NSDate class] forKey:@"remoteTimestamp"];
|
||||
isFromMe = [coder decodeBoolForKey:@"isFromMe"];
|
||||
}
|
||||
else
|
||||
{
|
||||
message = [coder decodeObjectForKey:@"message"];
|
||||
jid = [coder decodeObjectForKey:@"jid"];
|
||||
localTimestamp = [coder decodeObjectForKey:@"localTimestamp"];
|
||||
remoteTimestamp = [coder decodeObjectForKey:@"remoteTimestamp"];
|
||||
isFromMe = [coder decodeBoolForKey:@"isFromMe"];
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
message = [coder decodeObject];
|
||||
jid = [coder decodeObject];
|
||||
localTimestamp = [coder decodeObject];
|
||||
remoteTimestamp = [coder decodeObject];
|
||||
isFromMe = [[coder decodeObject] boolValue];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder
|
||||
{
|
||||
if ([coder allowsKeyedCoding])
|
||||
{
|
||||
[coder encodeObject:message forKey:@"message"];
|
||||
[coder encodeObject:jid forKey:@"jid"];
|
||||
[coder encodeObject:localTimestamp forKey:@"timestamp"];
|
||||
[coder encodeObject:remoteTimestamp forKey:@"remoteTimestamp"];
|
||||
[coder encodeBool:isFromMe forKey:@"isFromMe"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[coder encodeObject:message];
|
||||
[coder encodeObject:jid];
|
||||
[coder encodeObject:localTimestamp];
|
||||
[coder encodeObject:remoteTimestamp];
|
||||
[coder encodeObject:@(isFromMe)];
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL) supportsSecureCoding
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Copying
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
// We use [self class] to support subclassing
|
||||
|
||||
XMPPRoomMessageMemoryStorageObject *deepCopy = (XMPPRoomMessageMemoryStorageObject *)[[[self class] alloc] init];
|
||||
|
||||
deepCopy->message = [message copy];
|
||||
deepCopy->jid = [jid copy];
|
||||
deepCopy->localTimestamp = [localTimestamp copy];
|
||||
deepCopy->remoteTimestamp = [remoteTimestamp copy];
|
||||
deepCopy->isFromMe = isFromMe;
|
||||
|
||||
return deepCopy;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Properties
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@synthesize message;
|
||||
@synthesize jid;
|
||||
@synthesize localTimestamp;
|
||||
@synthesize remoteTimestamp;
|
||||
@synthesize isFromMe;
|
||||
|
||||
- (XMPPJID *)roomJID
|
||||
{
|
||||
return [jid bareJID];
|
||||
}
|
||||
|
||||
- (NSString *)nickname
|
||||
{
|
||||
return [jid resource];
|
||||
}
|
||||
|
||||
- (NSString *)body
|
||||
{
|
||||
return [[message elementForName:@"body"] stringValue];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Comparisons
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (NSComparisonResult)compare:(XMPPRoomMessageMemoryStorageObject *)another
|
||||
{
|
||||
return [localTimestamp compare:[another localTimestamp]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "XMPPRoom.h"
|
||||
#import "XMPPRoomOccupant.h"
|
||||
|
||||
|
||||
@interface XMPPRoomOccupantMemoryStorageObject : NSObject <XMPPRoomOccupant, NSCopying, NSSecureCoding>
|
||||
|
||||
- (id)initWithPresence:(XMPPPresence *)presence;
|
||||
- (void)updateWithPresence:(XMPPPresence *)presence;
|
||||
|
||||
/**
|
||||
* The properties below are documented in the XMPPRoomOccupant protocol.
|
||||
**/
|
||||
|
||||
@property (readonly) XMPPPresence *presence;
|
||||
|
||||
@property (readonly) XMPPJID * jid;
|
||||
@property (readonly) XMPPJID * roomJID;
|
||||
@property (readonly) NSString * nickname;
|
||||
|
||||
@property (readonly) NSString * role;
|
||||
@property (readonly) NSString * affiliation;
|
||||
@property (readonly) XMPPJID * realJID;
|
||||
|
||||
/**
|
||||
* Compares two occupants based on the nickname.
|
||||
*
|
||||
* This method provides the ordering used by XMPPRoomMemoryStorage.
|
||||
* Subclasses may override this method to provide an alternative sorting mechanism.
|
||||
**/
|
||||
- (NSComparisonResult)compare:(XMPPRoomOccupantMemoryStorageObject *)another;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,196 @@
|
||||
#import "XMPPRoomOccupantMemoryStorageObject.h"
|
||||
|
||||
|
||||
@implementation XMPPRoomOccupantMemoryStorageObject
|
||||
{
|
||||
XMPPPresence *presence;
|
||||
XMPPJID *jid;
|
||||
}
|
||||
|
||||
- (id)initWithPresence:(XMPPPresence *)inPresence
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
presence = inPresence;
|
||||
jid = [inPresence from];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Encoding, Decoding
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if ! TARGET_OS_IPHONE
|
||||
- (id)replacementObjectForPortCoder:(NSPortCoder *)encoder
|
||||
{
|
||||
if ([encoder isBycopy])
|
||||
return self;
|
||||
else
|
||||
return [super replacementObjectForPortCoder:encoder];
|
||||
// return [NSDistantObject proxyWithLocal:self connection:[encoder connection]];
|
||||
}
|
||||
#endif
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)coder
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
if ([coder allowsKeyedCoding])
|
||||
{
|
||||
if([coder respondsToSelector:@selector(requiresSecureCoding)] &&
|
||||
[coder requiresSecureCoding])
|
||||
{
|
||||
presence = [coder decodeObjectOfClass:[XMPPPresence class] forKey:@"presence"];
|
||||
jid = [coder decodeObjectOfClass:[XMPPJID class] forKey:@"jid"];
|
||||
}
|
||||
else
|
||||
{
|
||||
presence = [coder decodeObjectForKey:@"presence"];
|
||||
jid = [coder decodeObjectForKey:@"jid"];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
presence = [coder decodeObject];
|
||||
jid = [coder decodeObject];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder
|
||||
{
|
||||
if ([coder allowsKeyedCoding])
|
||||
{
|
||||
[coder encodeObject:presence forKey:@"presence"];
|
||||
[coder encodeObject:jid forKey:@"jid"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[coder encodeObject:presence];
|
||||
[coder encodeObject:jid];
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL) supportsSecureCoding
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Copying
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
// We use [self class] to support subclassing
|
||||
|
||||
XMPPRoomOccupantMemoryStorageObject *deepCopy = (XMPPRoomOccupantMemoryStorageObject *)[[[self class] alloc] init];
|
||||
|
||||
deepCopy->presence = [presence copy];
|
||||
deepCopy->jid = [jid copy];
|
||||
|
||||
return deepCopy;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Updates
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)updateWithPresence:(XMPPPresence *)inPresence
|
||||
{
|
||||
presence = inPresence;
|
||||
jid = [inPresence from];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Properties
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (XMPPJID *)roomJID
|
||||
{
|
||||
return [jid bareJID];
|
||||
}
|
||||
|
||||
- (XMPPJID *)jid
|
||||
{
|
||||
return jid;
|
||||
}
|
||||
|
||||
- (NSString *)nickname
|
||||
{
|
||||
return [jid resource];
|
||||
}
|
||||
|
||||
- (NSString *)itemAttributeStringValueForName:(NSString *)attrName
|
||||
{
|
||||
NSXMLElement *x = [presence elementForName:@"x" xmlns:@"http://jabber.org/protocol/muc#user"];
|
||||
NSXMLElement *item = [x elementForName:@"item"];
|
||||
if (item)
|
||||
{
|
||||
NSString *result = [item attributeStringValueForName:attrName];
|
||||
if (result)
|
||||
{
|
||||
return [result lowercaseString];
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)role
|
||||
{
|
||||
return [self itemAttributeStringValueForName:@"role"];
|
||||
}
|
||||
|
||||
- (NSString *)affiliation
|
||||
{
|
||||
return [self itemAttributeStringValueForName:@"affiliation"];
|
||||
}
|
||||
|
||||
- (XMPPJID *)realJID
|
||||
{
|
||||
NSString *jidStr = [self itemAttributeStringValueForName:@"jid"];
|
||||
if (jidStr)
|
||||
return [XMPPJID jidWithString:jidStr];
|
||||
else
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (XMPPPresence *)presence
|
||||
{
|
||||
return presence;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Comparisons
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (NSComparisonResult)compare:(XMPPRoomOccupantMemoryStorageObject *)another
|
||||
{
|
||||
return [self.nickname compare:another.nickname];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark NSObject Methods
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (NSUInteger)hash
|
||||
{
|
||||
return [jid hash];
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)anObject
|
||||
{
|
||||
if ([anObject isMemberOfClass:[self class]])
|
||||
{
|
||||
XMPPRoomOccupantMemoryStorageObject *another = (XMPPRoomOccupantMemoryStorageObject *)anObject;
|
||||
|
||||
return [jid isEqualToJID:[another jid]];
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in new issue
Block a user