This commit is contained in:
giuseppenuc committed 2016-02-24 16:56:39 +01:00
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