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,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>XMPPRoomHybrid.xcdatamodel</string>
</dict>
</plist>
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="878" systemVersion="11C74" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="XMPPRoomMessageHybridCoreDataStorageObject" representedClassName="XMPPRoomMessageHybridCoreDataStorageObject" syncable="YES">
<attribute name="body" optional="YES" attributeType="String" indexed="YES" syncable="YES"/>
<attribute name="fromMe" attributeType="Boolean" defaultValueString="NO" syncable="YES"/>
<attribute name="jid" optional="YES" transient="YES" syncable="YES"/>
<attribute name="jidStr" attributeType="String" indexed="YES" syncable="YES"/>
<attribute name="localTimestamp" attributeType="Date" indexed="YES" syncable="YES"/>
<attribute name="message" optional="YES" transient="YES" syncable="YES"/>
<attribute name="messageStr" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="nickname" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="remoteTimestamp" optional="YES" attributeType="Date" indexed="YES" syncable="YES"/>
<attribute name="roomJID" optional="YES" transient="YES" syncable="YES"/>
<attribute name="roomJIDStr" attributeType="String" indexed="YES" syncable="YES"/>
<attribute name="streamBareJidStr" attributeType="String" indexed="YES" syncable="YES"/>
<attribute name="type" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
</entity>
<elements>
<element name="XMPPRoomMessageHybridCoreDataStorageObject" positionX="160" positionY="192" width="128" height="240"/>
</elements>
</model>
@@ -0,0 +1,197 @@
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "XMPP.h"
#import "XMPPRoom.h"
#import "XMPPRoomMessageHybridCoreDataStorageObject.h"
#import "XMPPRoomOccupantHybridMemoryStorageObject.h"
#import "XMPPCoreDataStorage.h"
/**
* This class is an example implementation of the XMPPRoomStorage protocol.
* It stores messages in a database using core data, and stores occupants in memory (as they are temporary).
*
* You are free to substitute your own storage class.
**/
@interface XMPPRoomHybridStorage : XMPPCoreDataStorage <XMPPRoomStorage>
{
@protected
/* Inherited from XMPPCoreDataStorage
NSString *databaseFileName;
NSUInteger saveThreshold;
dispatch_queue_t storageQueue;
*/
// The occupantsGlobalDict holds all occupants in a heirarchy.
// It is a dictionary of dictionaries of dictionaries.
NSMutableDictionary * occupantsGlobalDict; // Key=xmppStream.myJid, Value=occupantsRoomsDict
// NSMutableDictionary * occupantsRoomsDict; // Key=xmppRoomJid, Value=occupantsRoomDict
// NSMutableDictionary * occupantsRoomDict; // Key=occupantJid, Value=XMPPRoomOccupantHybridMemoryStorageObject
}
/**
* Convenience method to get an instance with the default database name.
*
* IMPORTANT:
* You are NOT required to use the sharedInstance.
*
* If your application makes extensive use of MUC, and you use a sharedInstance of this class,
* then all of your MUC rooms share the same database store. You might get better performance if you create
* multiple instances of this class instead (using different database filenames), as this way you can have
* concurrent writes to multiple databases.
**/
+ (instancetype)sharedInstance;
/* Inherited from XMPPCoreDataStorage
* Please see the XMPPCoreDataStorage header file for extensive documentation.
- (id)initWithDatabaseFilename:(NSString *)databaseFileName;
- (id)initWithInMemoryStore;
@property (readonly) NSString *databaseFileName;
@property (readwrite) NSUInteger saveThreshold;
@property (readonly) NSManagedObjectModel *managedObjectModel;
@property (readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
*/
/**
* You may choose to extend this class, and/or the message class for customized functionality.
* These properties allow for such customization.
*
* You must set your desired entity name, if different from default, before you begin using the storage class.
**/
@property (strong, readwrite) NSString * messageEntityName;
/**
* You can optionally extend the XMPPRoomOccupantMemoryStorageObject class.
* Then just set the class here, and your subclass will automatically get used.
*
* You must set your desired class, if different from default, before you begin using the storage class.
**/
@property (assign, readwrite) Class occupantClass;
/**
* It is likely you don't want the message history to persist forever.
* Doing so would allow the database to grow infinitely large over time.
*
* The maxMessageAge property provides a way to specify how old a message can get
* before it should get deleted from the database.
*
* The deleteInterval specifies how often to sweep for old messages.
* Since deleting is an expensive operation (disk io) it is done on a fixed interval.
*
* You can optionally disable the maxMessageAge by setting it to zero (or a negative value).
* If you disable the maxMessageAge then old messages are not deleted.
*
* You can optionally disable the deleteInterval by setting it to zero (or a negative value).
*
* The default maxAge is 7 days.
* The default deleteInterval is 5 minutes.
**/
@property (assign, readwrite) NSTimeInterval maxMessageAge;
@property (assign, readwrite) NSTimeInterval deleteInterval;
/**
* You may optionally prevent old message deletion for particular rooms.
**/
- (void)pauseOldMessageDeletionForRoom:(XMPPJID *)roomJID;
- (void)resumeOldMessageDeletionForRoom:(XMPPJID *)roomJID;
/**
* Convenience method to get the message entity description.
*
* @see messageEntityName
**/
- (NSEntityDescription *)messageEntity:(NSManagedObjectContext *)moc;
/**
* Returns the timestamp of the most recent message stored in the database for the given room.
* This may be used when requesting the message history from the server,
* to prevent redownloading messages you already have.
*
* @param roomJID - The JID of the room (a bare JID)
*
* @param xmppStream - This class can support multiple concurrent xmppStreams.
* Optionally pass the xmppStream the room applies to.
* If you're using this claass with a single xmppStream, you can pass nil.
*
* @param moc - The managedObjectContext to use when doing the lookups.
* If non-nil, this should match the thread you're currently using.
* If nil, the operation is dispatch_sync'd onto the internal queue,
* and uses the internal managedObjectContext.
*
* The moc may optionally be nil strictly because this method does not return a NSManagedObject.
**/
- (NSDate *)mostRecentMessageTimestampForRoom:(XMPPJID *)roomJID
stream:(XMPPStream *)xmppStream
inContext:(NSManagedObjectContext *)moc;
/**
* Returns the occupant for the given full jid.
*
* @param jid - The full jid of the room occupant (including resource).
* E.g. xmppDevelopers@conf.xmpp.org/robbiehanson
*
* @param xmppStream - This class can support multiple concurrent xmppStreams.
* Optionally pass the xmppStream the room applies to.
* If you're using this claass with a single xmppStream, you can pass nil.
**/
- (XMPPRoomOccupantHybridMemoryStorageObject *)occupantForJID:(XMPPJID *)jid stream:(XMPPStream *)xmppStream;
/**
* Returns all occupants in the given room.
* Each occupant instance will be of kind XMPPRoomOccupantHybridMemoryStorageObject.
*
* @param roomJid - The JID of the room (a bare JID).
* E.g. xmppDevelopers@conf.xmpp.org
*
* @param xmppStream - This class can support multiple concurrent xmppStreams.
* Optionally pass the xmppStream the room applies to.
* If you're using this claass with a single xmppStream, you can pass nil.
**/
- (NSArray *)occupantsForRoom:(XMPPJID *)roomJid stream:(XMPPStream *)xmppStream;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@protocol XMPPRoomHybridStorageDelegate <NSObject>
@optional
//
// XMPPRoomHybridStorage 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.
**/
- (void)xmppRoomHybridStorage:(XMPPRoomHybridStorage *)sender
occupantDidJoin:(XMPPRoomOccupantHybridMemoryStorageObject *)occupant;
/**
* Similar to XMPPRoomDelegate's xmppRoom:occupantDidLeave:withPresence: method.
* This method provides the delegate with the occupant storage instance.
**/
- (void)xmppRoomHybridStorage:(XMPPRoomHybridStorage *)sender
occupantDidLeave:(XMPPRoomOccupantHybridMemoryStorageObject *)occupant;
/**
* Similar to XMPPRoomDelegate's xmppRoom:occupantDidUpdate:withPresence: method.
* This method provides the delegate with the occupant storage instance.
**/
- (void)xmppRoomHybridStorage:(XMPPRoomHybridStorage *)sender
occupantDidUpdate:(XMPPRoomOccupantHybridMemoryStorageObject *)occupant;
@end
@@ -0,0 +1,990 @@
#import "XMPPRoomHybridStorage.h"
#import "XMPPRoomPrivate.h"
#import "XMPPCoreDataStorageProtected.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;
#else
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
#endif
#define AssertPrivateQueue() \
NSAssert(dispatch_get_specific(storageQueueTag), @"Private method: MUST run on storageQueue");
@interface XMPPRoomHybridStorage ()
{
// Protected variables are listed in the header file.
// These are the private variables.
NSString *messageEntityName;
Class occupantClass;
NSTimeInterval maxMessageAge;
NSTimeInterval deleteInterval;
NSMutableSet *pausedMessageDeletion;
dispatch_time_t lastDeleteTime;
dispatch_source_t deleteTimer;
}
- (void)performDelete;
- (void)destroyDeleteTimer;
- (void)updateDeleteTimer;
- (void)createAndStartDeleteTimer;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation XMPPRoomHybridStorage
static XMPPRoomHybridStorage *sharedInstance;
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[XMPPRoomHybridStorage alloc] initWithDatabaseFilename:nil storeOptions:nil];
});
return sharedInstance;
}
- (void)commonInit
{
XMPPLogTrace();
[super commonInit];
// This method is invoked by all public init methods of the superclass
occupantsGlobalDict = [[NSMutableDictionary alloc] init];
messageEntityName = NSStringFromClass([XMPPRoomMessageHybridCoreDataStorageObject class]);
occupantClass = [XMPPRoomOccupantHybridMemoryStorageObject class];
maxMessageAge = (60 * 60 * 24 * 7); // 7 days
deleteInterval = (60 * 5); // 5 days
pausedMessageDeletion = [[NSMutableSet alloc] init];
autoRecreateDatabaseFile = YES;
}
/**
* Documentation from the superclass (XMPPCoreDataStorage):
*
* Override me, if needed, to provide customized behavior.
*
* This method is queried to get the name of the ManagedObjectModel within the app bundle.
* It should return the name of the appropriate file (*.xdatamodel / *.mom / *.momd) sans file extension.
*
* The default implementation returns the name of the subclass, stripping any suffix of "CoreDataStorage".
* E.g., if your subclass was named "XMPPExtensionCoreDataStorage", then this method would return "XMPPExtension".
*
* Note that a file extension should NOT be included.
**/
- (NSString *)managedObjectModelName
{
// Optional hook
//
// The default implementation would return "XMPPPRoomHybridStorage".
// We prefer a slightly shorter version.
return @"XMPPRoomHybrid";
}
- (void)dealloc
{
[self destroyDeleteTimer];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Configuration
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@synthesize messageEntityName;
@synthesize occupantClass;
- (NSTimeInterval)maxMessageAge
{
__block NSTimeInterval result = 0;
dispatch_block_t block = ^{
result = maxMessageAge;
};
if (dispatch_get_specific(storageQueueTag))
block();
else
dispatch_sync(storageQueue, block);
return result;
}
- (void)setMaxMessageAge:(NSTimeInterval)age
{
dispatch_block_t block = ^{ @autoreleasepool {
NSTimeInterval oldMaxMessageAge = maxMessageAge;
NSTimeInterval newMaxMessageAge = age;
maxMessageAge = age;
// There are several cases we need to handle here.
//
// 1. If the maxAge was previously enabled and it just got disabled,
// then we need to stop the deleteTimer. (And we might as well release it.)
//
// 2. If the maxAge was previously disabled and it just got enabled,
// then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.)
//
// 3. If the maxAge was increased,
// then we don't need to do anything.
//
// 4. If the maxAge was decreased,
// then we should do an immediate delete.
BOOL shouldDeleteNow = NO;
if (oldMaxMessageAge > 0.0)
{
if (newMaxMessageAge <= 0.0)
{
// Handles #1
[self destroyDeleteTimer];
}
else if (oldMaxMessageAge > newMaxMessageAge)
{
// Handles #4
shouldDeleteNow = YES;
}
else
{
// Handles #3
// Nothing to do now
}
}
else if (newMaxMessageAge > 0.0)
{
// Handles #2
shouldDeleteNow = YES;
}
if (shouldDeleteNow)
{
[self performDelete];
if (deleteTimer)
[self updateDeleteTimer];
else
[self createAndStartDeleteTimer];
}
}};
if (dispatch_get_specific(storageQueueTag))
block();
else
dispatch_async(storageQueue, block);
}
- (NSTimeInterval)deleteInterval
{
__block NSTimeInterval result = 0;
dispatch_block_t block = ^{
result = deleteInterval;
};
if (dispatch_get_specific(storageQueueTag))
block();
else
dispatch_sync(storageQueue, block);
return result;
}
- (void)setDeleteInterval:(NSTimeInterval)interval
{
dispatch_block_t block = ^{ @autoreleasepool {
deleteInterval = interval;
// There are several cases we need to handle here.
//
// 1. If the deleteInterval was previously enabled and it just got disabled,
// then we need to stop the deleteTimer. (And we might as well release it.)
//
// 2. If the deleteInterval was previously disabled and it just got enabled,
// then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.)
//
// 3. If the deleteInterval increased, then we need to reset the timer so that it fires at the later date.
//
// 4. If the deleteInterval decreased, then we need to reset the timer so that it fires at an earlier date.
// (Plus we might need to do an immediate delete.)
if (deleteInterval > 0.0)
{
if (deleteTimer == NULL)
{
// Handles #2
//
// Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate,
// if a delete is needed the timer will fire immediately.
[self createAndStartDeleteTimer];
}
else
{
// Handles #3
// Handles #4
//
// Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate,
// if a save is needed the timer will fire immediately.
[self updateDeleteTimer];
}
}
else if (deleteTimer)
{
// Handles #1
[self destroyDeleteTimer];
}
}};
if (dispatch_get_specific(storageQueueTag))
block();
else
dispatch_async(storageQueue, block);
}
- (void)pauseOldMessageDeletionForRoom:(XMPPJID *)roomJID
{
dispatch_block_t block = ^{ @autoreleasepool {
[pausedMessageDeletion addObject:[roomJID bareJID]];
}};
if (dispatch_get_specific(storageQueueTag))
block();
else
dispatch_async(storageQueue, block);
}
- (void)resumeOldMessageDeletionForRoom:(XMPPJID *)roomJID
{
dispatch_block_t block = ^{ @autoreleasepool {
[pausedMessageDeletion removeObject:[roomJID bareJID]];
[self performDelete];
}};
if (dispatch_get_specific(storageQueueTag))
block();
else
dispatch_async(storageQueue, block);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Internal API
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)performDelete
{
if (maxMessageAge <= 0.0) return;
NSDate *minLocalTimestamp = [NSDate dateWithTimeIntervalSinceNow:(maxMessageAge * -1.0)];
NSManagedObjectContext *moc = [self managedObjectContext];
NSEntityDescription *messageEntity = [self messageEntity:moc];
NSPredicate *predicate;
if ([pausedMessageDeletion count] > 0)
{
predicate = [NSPredicate predicateWithFormat:@"localTimestamp <= %@ AND roomJIDStr NOT IN %@",
minLocalTimestamp, pausedMessageDeletion];
}
else
{
predicate = [NSPredicate predicateWithFormat:@"localTimestamp <= %@", minLocalTimestamp];
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:messageEntity];
[fetchRequest setPredicate:predicate];
[fetchRequest setFetchBatchSize:saveThreshold];
NSError *error = nil;
NSArray *oldMessages = [moc executeFetchRequest:fetchRequest error:&error];
if (error)
{
XMPPLogWarn(@"%@: %@ - fetch error: %@", THIS_FILE, THIS_METHOD, error);
}
NSUInteger unsavedCount = [self numberOfUnsavedChanges];
for (XMPPRoomMessageHybridCoreDataStorageObject *oldMessage in oldMessages)
{
[moc deleteObject:oldMessage];
if (++unsavedCount >= saveThreshold)
{
[self save];
unsavedCount = 0;
}
}
lastDeleteTime = dispatch_time(DISPATCH_TIME_NOW, 0);
}
- (void)destroyDeleteTimer
{
if (deleteTimer)
{
dispatch_source_cancel(deleteTimer);
#if !OS_OBJECT_USE_OBJC
dispatch_release(deleteTimer);
#endif
deleteTimer = NULL;
}
}
- (void)updateDeleteTimer
{
if ((deleteTimer != NULL) && (deleteInterval > 0.0) && (maxMessageAge > 0.0))
{
uint64_t interval = deleteInterval * NSEC_PER_SEC;
dispatch_time_t startTime;
if (lastDeleteTime > 0)
startTime = dispatch_time(lastDeleteTime, interval);
else
startTime = dispatch_time(DISPATCH_TIME_NOW, interval);
dispatch_source_set_timer(deleteTimer, startTime, interval, 1.0);
}
}
- (void)createAndStartDeleteTimer
{
if ((deleteTimer == NULL) && (deleteInterval > 0.0) && (maxMessageAge > 0.0))
{
deleteTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, storageQueue);
dispatch_source_set_event_handler(deleteTimer, ^{ @autoreleasepool {
[self performDelete];
}});
[self updateDeleteTimer];
if (deleteTimer)
dispatch_resume(deleteTimer);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Protected API
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Optional override hook.
**/
- (BOOL)existsMessage:(XMPPMessage *)message forRoom:(XMPPRoom *)room stream:(XMPPStream *)xmppStream
{
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;
}
// Does this message already exist in the database?
// How can we tell if two XMPPRoomMessages are the same?
//
// 1. Same streamBareJidStr
// 2. Same jid
// 3. Same text
// 4. Approximately the same timestamps
//
// 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.
//
// Note: Predicate order matters. Most unique key should be first, least unique should be last.
NSManagedObjectContext *moc = [self managedObjectContext];
NSEntityDescription *messageEntity = [self messageEntity:moc];
NSString *streamBareJidStr = [[self myJIDForXMPPStream:xmppStream] bare];
XMPPJID *messageJID = [message from];
NSString *messageBody = [[message elementForName:@"body"] stringValue];
NSDate *minLocalTimestamp = [remoteTimestamp dateByAddingTimeInterval:-60];
NSDate *maxLocalTimestamp = [remoteTimestamp dateByAddingTimeInterval: 60];
NSString *predicateFormat = @" body == %@ "
@"AND jidStr == %@ "
@"AND streamBareJidStr == %@ "
@"AND "
@"("
@" (remoteTimestamp == %@) "
@" OR (remoteTimestamp == NIL && localTimestamp BETWEEN {%@, %@})"
@")";
NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateFormat,
messageBody, messageJID, streamBareJidStr,
remoteTimestamp, minLocalTimestamp, maxLocalTimestamp];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:messageEntity];
[fetchRequest setPredicate:predicate];
[fetchRequest setFetchLimit:1];
NSError *error = nil;
NSArray *results = [moc executeFetchRequest:fetchRequest error:&error];
if (error)
{
XMPPLogError(@"%@: %@ - Fetch error: %@", THIS_FILE, THIS_METHOD, error);
}
return ([results count] > 0);
}
/**
* Optional override hook for general extensions.
*
* @see insertMessage:outgoing:forRoom:stream:
**/
- (void)didInsertMessage:(XMPPRoomMessageHybridCoreDataStorageObject *)message
{
// Override me if you're extending the XMPPRoomMessageHybridCoreDataStorageObject class
// to add additional properties, which you can set here.
//
// At this point the standard properties have already been set.
// So you can, for example, access the XMPPMessage via message.message.
}
/**
* Optional override hook for complete customization.
* Override me if you need to do specific custom work when inserting a message in a room.
*
* @see didInsertMessage:
**/
- (void)insertMessage:(XMPPMessage *)message
outgoing:(BOOL)isOutgoing
forRoom:(XMPPRoom *)room
stream:(XMPPStream *)xmppStream
{
// Extract needed information
XMPPJID *roomJID = room.roomJID;
XMPPJID *messageJID = isOutgoing ? room.myRoomJID : [message from];
NSDate *localTimestamp;
NSDate *remoteTimestamp;
if (isOutgoing)
{
localTimestamp = [[NSDate alloc] init];
remoteTimestamp = nil;
}
else
{
remoteTimestamp = [message delayedDeliveryDate];
if (remoteTimestamp) {
localTimestamp = remoteTimestamp;
}
else {
localTimestamp = [[NSDate alloc] init];
}
}
NSString *messageBody = [[message elementForName:@"body"] stringValue];
NSManagedObjectContext *moc = [self managedObjectContext];
NSString *streamBareJidStr = [[self myJIDForXMPPStream:xmppStream] bare];
NSEntityDescription *messageEntity = [self messageEntity:moc];
// Add to database
XMPPRoomMessageHybridCoreDataStorageObject *roomMessage = (XMPPRoomMessageHybridCoreDataStorageObject *)
[[NSManagedObject alloc] initWithEntity:messageEntity insertIntoManagedObjectContext:nil];
roomMessage.message = message;
roomMessage.roomJID = roomJID;
roomMessage.jid = messageJID;
roomMessage.nickname = [messageJID resource];
roomMessage.body = messageBody;
roomMessage.localTimestamp = localTimestamp;
roomMessage.remoteTimestamp = remoteTimestamp;
roomMessage.isFromMe = isOutgoing;
roomMessage.streamBareJidStr = streamBareJidStr;
[moc insertObject:roomMessage]; // Hook if subclassing XMPPRoomMessageHybridCDSO (awakeFromInsert)
[self didInsertMessage:roomMessage]; // Hook if subclassing XMPPRoomHybridStorage
}
/**
* Optional override hook for general extensions.
*
* @see insertOccupantWithPresence:room:stream:
**/
- (void)didInsertOccupant:(XMPPRoomOccupantHybridMemoryStorageObject *)occupant
{
// Override me if you're extending the XMPPRoomOccupantHybridMemoryStorageObject class
// to add additional properties, which you can set here.
//
// At this point the standard properties have already been set.
// So you can, for example, access the XMPPPresence via occupant.presece.
}
/**
* Optional override hook for general extensions.
*
* @see updateOccupant:withPresence:room:stream:
**/
- (void)didUpdateOccupant:(XMPPRoomOccupantHybridMemoryStorageObject *)occupant
{
// Override me if you're extending the XMPPRoomOccupantHybridMemoryStorageObject class,
// and you have additional properties that may need to be updated.
//
// At this point the standard properties have already been updated.
}
/**
* Optional override hook for general extensions.
**/
- (void)willRemoveOccupant:(XMPPRoomOccupantHybridMemoryStorageObject *)occupant
{
// Override me if you have any custom work to do before an occupant leaves (is removed from storage).
}
/**
* Optional override hook for general extensions.
**/
- (void)didRemoveOccupant:(XMPPRoomOccupantHybridMemoryStorageObject *)occupant
{
// Override me if you have any custom work to do after an occupant leaves (is removed from storage).
}
/**
* Optional override hook for complete customization.
* Override me if you need to do custom work when inserting an occupant in a room.
**/
- (XMPPRoomOccupantHybridMemoryStorageObject *)insertOccupantWithPresence:(XMPPPresence *)presence
room:(XMPPRoom *)room
stream:(XMPPStream *)xmppStream
{
XMPPJID *streamFullJid = [self myJIDForXMPPStream:xmppStream];
XMPPJID *roomJid = room.roomJID;
NSMutableDictionary *occupantsRoomsDict = occupantsGlobalDict[streamFullJid];
if (occupantsRoomsDict == nil)
{
occupantsRoomsDict = [[NSMutableDictionary alloc] init];
occupantsGlobalDict[streamFullJid] = occupantsRoomsDict;
}
NSMutableDictionary *occupantsRoomDict = occupantsRoomsDict[roomJid];
if (occupantsRoomDict == nil)
{
occupantsRoomDict = [[NSMutableDictionary alloc] init];
occupantsRoomsDict[roomJid] = occupantsRoomDict;
}
XMPPRoomOccupantHybridMemoryStorageObject *occupant = (XMPPRoomOccupantHybridMemoryStorageObject *)
[[self.occupantClass alloc] initWithPresence:presence streamFullJid:streamFullJid];
occupantsRoomDict[occupant.jid] = occupant;
return occupant;
}
/**
* Optional override hook for complete customization.
* Override me if you need to do custom work when updating an occupant in a room.
**/
- (void)updateOccupant:(XMPPRoomOccupantHybridMemoryStorageObject *)occupant
withPresence:(XMPPPresence *)presence
room:(XMPPRoom *)room
stream:(XMPPStream *)stream
{
[occupant updateWithPresence:presence];
}
/**
* Optional override hook for complete customization.
* Override me if you need to do custom work when removing an occupant from a room.
**/
- (void)removeOccupant:(XMPPRoomOccupantHybridMemoryStorageObject *)occupant
withPresence:(XMPPPresence *)presence
room:(XMPPRoom *)room
stream:(XMPPStream *)stream
{
// Remove from dictionary
XMPPJID *streamFullJid = [self myJIDForXMPPStream:stream];
XMPPJID *roomJid = occupant.roomJID;
NSMutableDictionary *occupantsRoomsDict = occupantsGlobalDict[streamFullJid];
NSMutableDictionary *occupantsRoomDict = occupantsRoomsDict[roomJid];
[occupantsRoomDict removeObjectForKey:occupant.jid]; // Remove occupant
if ([occupantsRoomDict count] == 0)
{
[occupantsRoomsDict removeObjectForKey:roomJid]; // Remove room if now empty
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Public API
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (NSEntityDescription *)messageEntity:(NSManagedObjectContext *)moc
{
// This method should be thread-safe.
// So be sure to access the entity name through the property accessor.
if (moc == nil)
{
XMPPLogWarn(@"%@: %@ - Invalid parameter, moc is nil", THIS_FILE, THIS_METHOD);
return nil;
}
NSString *entityName = self.messageEntityName;
return [NSEntityDescription entityForName:entityName inManagedObjectContext:moc];
}
- (NSDate *)mostRecentMessageTimestampForRoom:(XMPPJID *)roomJID
stream:(XMPPStream *)xmppStream
inContext:(NSManagedObjectContext *)inMoc
{
if (roomJID == nil) return nil;
// It's possible to use our internal managedObjectContext only because we're not returning a NSManagedObject.
__block NSDate *result = nil;
dispatch_block_t block = ^{ @autoreleasepool {
NSManagedObjectContext *moc = inMoc ? : [self managedObjectContext];
NSEntityDescription *entity = [self messageEntity:moc];
NSPredicate *predicate;
if (xmppStream)
{
NSString *streamBareJidStr = [[self myJIDForXMPPStream:xmppStream] bare];
NSString *predicateFormat = @"roomJIDStr == %@ AND streamBareJidStr == %@";
predicate = [NSPredicate predicateWithFormat:predicateFormat, roomJID, streamBareJidStr];
}
else
{
predicate = [NSPredicate predicateWithFormat:@"roomJIDStr == %@", roomJID];
}
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"localTimestamp" ascending:NO];
NSArray *sortDescriptors = @[sortDescriptor];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
[fetchRequest setSortDescriptors:sortDescriptors];
[fetchRequest setFetchLimit:1];
NSError *error = nil;
XMPPRoomMessageHybridCoreDataStorageObject *message = (XMPPRoomMessageHybridCoreDataStorageObject *)
[[moc executeFetchRequest:fetchRequest error:&error] lastObject];
if (error)
{
XMPPLogError(@"%@: %@ - fetchRequest error: %@", THIS_FILE, THIS_METHOD, error);
}
else
{
result = [message.localTimestamp copy];
}
}};
if (inMoc == nil)
dispatch_sync(storageQueue, block);
else
block();
return result;
}
- (XMPPRoomOccupantHybridMemoryStorageObject *)occupantForJID:(XMPPJID *)occupantJid stream:(XMPPStream *)xmppStream
{
if (occupantJid == nil) return nil;
__block XMPPRoomOccupantHybridMemoryStorageObject *occupant = nil;
void (^block)(BOOL) = ^(BOOL shouldCopy){ @autoreleasepool {
XMPPJID *roomJid = [occupantJid bareJID];
if (xmppStream)
{
XMPPJID *streamFullJid = [self myJIDForXMPPStream:xmppStream];
NSDictionary *occupantsRoomsDict = occupantsGlobalDict[streamFullJid];
NSDictionary *occupantsRoomDict = occupantsRoomsDict[roomJid];
occupant = occupantsRoomDict[occupantJid];
}
else
{
for (XMPPJID *streamFullJid in occupantsGlobalDict)
{
NSDictionary *occupantsRoomsDict = occupantsGlobalDict[streamFullJid];
NSDictionary *occupantsRoomDict = occupantsRoomsDict[roomJid];
occupant = occupantsRoomDict[occupantJid];
if (occupant) break;
}
}
if (shouldCopy)
{
occupant = [occupant copy];
}
}};
if (dispatch_get_specific(storageQueueTag))
block(NO);
else
dispatch_sync(storageQueue, ^{ block(YES); });
return occupant;
}
- (NSArray *)occupantsForRoom:(XMPPJID *)roomJid stream:(XMPPStream *)xmppStream
{
roomJid = [roomJid bareJID]; // Just in case a full jid is accidentally passed
__block NSArray *results = nil;
void (^block)(BOOL) = ^(BOOL shouldCopy){ @autoreleasepool {
if (xmppStream)
{
XMPPJID *streamFullJid = [self myJIDForXMPPStream:xmppStream];
NSDictionary *occupantsRoomsDict = occupantsGlobalDict[streamFullJid];
NSDictionary *occupantsRoomDict = occupantsRoomsDict[roomJid];
results = [occupantsRoomDict allValues];
}
else
{
for (XMPPJID *streamFullJid in occupantsGlobalDict)
{
NSDictionary *occupantsRoomsDict = occupantsGlobalDict[streamFullJid];
NSDictionary *occupantsRoomDict = occupantsRoomsDict[roomJid];
if (occupantsRoomDict)
{
results = [occupantsRoomDict allValues];
break;
}
}
}
if (shouldCopy)
{
NSArray *temp = results;
results = [[NSArray alloc] initWithArray:temp copyItems:YES];
}
}};
if (dispatch_get_specific(storageQueueTag))
block(NO);
else
dispatch_sync(storageQueue, ^{ block(YES); });
return results;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark XMPPRoomStorage Protocol
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)handlePresence:(XMPPPresence *)presence room:(XMPPRoom *)room
{
XMPPLogTrace();
dispatch_queue_t roomQueue = room.moduleQueue;
XMPPStream *xmppStream = room.xmppStream;
[self scheduleBlock:^{
XMPPJID *from = [presence from];
if ([[presence type] isEqualToString:@"unavailable"])
{
XMPPRoomOccupantHybridMemoryStorageObject *occupant = [self occupantForJID:from stream:xmppStream];
if (occupant)
{
// Occupant did leave - remove
[self willRemoveOccupant:occupant];
[self removeOccupant:occupant withPresence:presence room:room stream:xmppStream];
[self didRemoveOccupant:occupant];
// Notify delegate(s)
XMPPRoomOccupantHybridMemoryStorageObject *occupantCopy = [occupant copy];
dispatch_async(roomQueue, ^{ @autoreleasepool {
GCDMulticastDelegate <XMPPRoomHybridStorageDelegate> *roomMulticastDelegate =
(GCDMulticastDelegate <XMPPRoomHybridStorageDelegate> *)[room multicastDelegate];
[roomMulticastDelegate xmppRoomHybridStorage:self
occupantDidLeave:occupantCopy];
}});
}
}
else
{
XMPPRoomOccupantHybridMemoryStorageObject *occupant = [self occupantForJID:from stream:xmppStream];
if (occupant == nil)
{
// Occupant did join - add
occupant = [self insertOccupantWithPresence:presence room:room stream:xmppStream];
if (occupant == nil)
{
// Subclasses may choose to ignore occupants for whatever reason.
return;
}
[self didInsertOccupant:occupant];
// Notify delegate(s)
XMPPRoomOccupantHybridMemoryStorageObject *occupantCopy = [occupant copy];
dispatch_async(roomQueue, ^{ @autoreleasepool {
GCDMulticastDelegate <XMPPRoomHybridStorageDelegate> *roomMulticastDelegate =
(GCDMulticastDelegate <XMPPRoomHybridStorageDelegate> *)[room multicastDelegate];
[roomMulticastDelegate xmppRoomHybridStorage:self
occupantDidJoin:occupantCopy];
}});
}
else
{
// Occupant did update - move
[self updateOccupant:occupant withPresence:presence room:room stream:xmppStream];
[self didUpdateOccupant:occupant];
// Notify delegate(s)
XMPPRoomOccupantHybridMemoryStorageObject *occupantCopy = [occupant copy];
dispatch_async(roomQueue, ^{ @autoreleasepool {
GCDMulticastDelegate <XMPPRoomHybridStorageDelegate> *roomMulticastDelegate =
(GCDMulticastDelegate <XMPPRoomHybridStorageDelegate> *)[room multicastDelegate];
[roomMulticastDelegate xmppRoomHybridStorage:self
occupantDidUpdate:occupantCopy];
}});
}
}
}];
}
- (void)handleOutgoingMessage:(XMPPMessage *)message room:(XMPPRoom *)room
{
XMPPLogTrace();
XMPPStream *xmppStream = room.xmppStream;
[self scheduleBlock:^{
[self insertMessage:message outgoing:YES forRoom:room stream:xmppStream];
}];
}
- (void)handleIncomingMessage:(XMPPMessage *)message room:(XMPPRoom *)room
{
XMPPLogTrace();
XMPPJID *myRoomJID = room.myRoomJID;
XMPPJID *messageJID = [message from];
if ([myRoomJID isEqualToJID:messageJID])
{
if (![message wasDelayed])
{
// Ignore - we already stored message in handleOutgoingMessage:room:
return;
}
}
XMPPStream *xmppStream = room.xmppStream;
[self scheduleBlock:^{
if ([self existsMessage:message forRoom:room stream:xmppStream])
{
XMPPLogVerbose(@"%@: %@ - Duplicate message", THIS_FILE, THIS_METHOD);
}
else
{
[self insertMessage:message outgoing:NO forRoom:room stream:xmppStream];
}
}];
}
- (void)handleDidLeaveRoom:(XMPPRoom *)room
{
XMPPLogTrace();
XMPPJID *roomJid = room.roomJID;
XMPPStream *xmppStream = room.xmppStream;
[self scheduleBlock:^{
XMPPJID *streamFullJid = [self myJIDForXMPPStream:xmppStream];
NSMutableDictionary *occupantsRoomsDict = occupantsGlobalDict[streamFullJid];
[occupantsRoomsDict removeObjectForKey:roomJid]; // Remove room (and all associated occupants)
}];
}
@end
@@ -0,0 +1,91 @@
/**
* The XMPPRoomHybridStorage class is designed to be easily extensible.
* The class has several protected methods that act as hooks,
* allowing you to override various methods to customize the functionality how you see fit.
*
* This header file lists the protected methods,
* and you may need to import it in your subclass if you ever need to invoke these methods directly.
*
* E.g. [super insertOccupant...]
**/
@interface XMPPRoomHybridStorage (Protected)
/**
* Returns whether or not the given message already exists in storage.
* If YES, then the message is ignored. Otherwise it is passed to the insert routines.
**/
- (BOOL)existsMessage:(XMPPMessage *)message forRoom:(XMPPRoom *)room stream:(XMPPStream *)xmppStream;
/**
* Override me if you're extending the XMPPRoomMessageHybridCoreDataStorageObject class
* to add additional properties, which you can set here.
*
* At this point the standard properties have already been set.
* So you can, for example, access the XMPPMessage via message.message.
**/
- (void)didInsertMessage:(XMPPRoomMessageHybridCoreDataStorageObject *)message;
/**
* Optional override hook for complete customization.
* Override me if you need to do specific custom work when inserting a message in a room.
**/
- (void)insertMessage:(XMPPMessage *)message
outgoing:(BOOL)isOutgoing
forRoom:(XMPPRoom *)room
stream:(XMPPStream *)xmppStream;
/**
* Override me if you're extending the XMPPRoomOccupantHybridMemoryStorageObject class
* to add additional properties, which you can set here.
*
* At this point the standard properties have already been set.
* So you can, for example, access the XMPPPresence via occupant.presece.
**/
- (void)didInsertOccupant:(XMPPRoomOccupantHybridMemoryStorageObject *)occupant;
/**
* Override me if you're extending the XMPPRoomOccupantHybridMemoryStorageObject class,
* and you have additional properties that may need to be updated.
*
* At this point the standard properties have already been updated.
**/
- (void)didUpdateOccupant:(XMPPRoomOccupantHybridMemoryStorageObject *)occupant;
/**
* Override me if you have any custom work to do before an occupant leaves (is removed from storage).
**/
- (void)willRemoveOccupant:(XMPPRoomOccupantHybridMemoryStorageObject *)occupant;
/**
* Override me if you have any custom work to do after an occupant leaves (is removed from storage).
**/
- (void)didRemoveOccupant:(XMPPRoomOccupantHybridMemoryStorageObject *)occupant;
/**
* Optional override hook for complete customization.
* Override me if you need to do custom work when inserting an occupant in a room.
**/
- (XMPPRoomOccupantHybridMemoryStorageObject *)insertOccupantWithPresence:(XMPPPresence *)presence
room:(XMPPRoom *)room
stream:(XMPPStream *)xmppStream;
/**
* Optional override hook for complete customization.
* Override me if you need to do custom work when updating an occupant in a room.
**/
- (void)updateOccupant:(XMPPRoomOccupantHybridMemoryStorageObject *)occupant
withPresence:(XMPPPresence *)presence
room:(XMPPRoom *)room
stream:(XMPPStream *)stream;
/**
* Optional override hook for complete customization.
* Override me if you need to do custom work when removing an occupant from a room.
**/
- (void)removeOccupant:(XMPPRoomOccupantHybridMemoryStorageObject *)occupant
withPresence:(XMPPPresence *)presence
room:(XMPPRoom *)room
stream:(XMPPStream *)stream;
@end
@@ -0,0 +1,47 @@
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "XMPP.h"
#import "XMPPRoom.h"
@interface XMPPRoomMessageHybridCoreDataStorageObject : NSManagedObject <XMPPRoomMessage>
/**
* The properties below are documented in the XMPPRoomMessage protocol.
**/
@property (nonatomic, retain) XMPPMessage * message; // Transient (proper type, not on disk)
@property (nonatomic, retain) NSString * messageStr; // Shadow (binary data, written to disk)
@property (nonatomic, strong) XMPPJID * roomJID; // Transient (proper type, not on disk)
@property (nonatomic, strong) NSString * roomJIDStr; // Shadow (binary data, written to disk)
@property (nonatomic, retain) XMPPJID * jid; // Transient (proper type, not on disk)
@property (nonatomic, retain) NSString * jidStr; // Shadow (binary data, written to disk)
@property (nonatomic, retain) NSString * nickname;
@property (nonatomic, retain) NSString * body;
@property (nonatomic, retain) NSDate * localTimestamp;
@property (nonatomic, strong) NSDate * remoteTimestamp;
@property (nonatomic, assign) BOOL isFromMe;
@property (nonatomic, strong) NSNumber * fromMe;
/**
* The 'type' property can be used to inject event messages.
* For example: "JohnDoe entered the room".
*
* You can define your own types to suit your needs.
* All normal messages will have a type of zero.
**/
@property (nonatomic, strong) NSNumber * type;
/**
* If a single instance of XMPPRoomHybridStorage is shared between multiple xmppStream's,
* this may be needed to distinguish between the streams.
**/
@property (nonatomic, strong) NSString *streamBareJidStr;
@end
@@ -0,0 +1,206 @@
#import "XMPPRoomMessageHybridCoreDataStorageObject.h"
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
@interface XMPPRoomMessageHybridCoreDataStorageObject ()
@property(nonatomic,strong) XMPPJID * primitiveRoomJID;
@property(nonatomic,strong) NSString * primitiveRoomJIDStr;
@property(nonatomic,strong) XMPPJID * primitiveJid;
@property(nonatomic,strong) NSString * primitiveJidStr;
@property(nonatomic,strong) XMPPMessage * primitiveMessage;
@property(nonatomic,strong) NSString * primitiveMessageStr;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation XMPPRoomMessageHybridCoreDataStorageObject
@dynamic message;
@dynamic messageStr;
@dynamic roomJID, primitiveRoomJID;
@dynamic roomJIDStr, primitiveRoomJIDStr;
@dynamic jid, primitiveJid;
@dynamic jidStr, primitiveJidStr;
@dynamic nickname;
@dynamic body;
@dynamic localTimestamp;
@dynamic remoteTimestamp;
@dynamic isFromMe;
@dynamic fromMe;
@dynamic type;
@dynamic streamBareJidStr;
@dynamic primitiveMessage;
@dynamic primitiveMessageStr;
#pragma mark Transient roomJID
- (XMPPJID *)roomJID
{
// Create and cache on demand
[self willAccessValueForKey:@"roomJID"];
XMPPJID *tmp = self.primitiveRoomJID;
[self didAccessValueForKey:@"roomJID"];
if (tmp == nil)
{
NSString *roomJIDStr = self.roomJIDStr;
if (roomJIDStr)
{
tmp = [XMPPJID jidWithString:roomJIDStr];
self.primitiveRoomJID = tmp;
}
}
return tmp;
}
- (void)setRoomJID:(XMPPJID *)roomJID
{
[self willChangeValueForKey:@"roomJID"];
[self willChangeValueForKey:@"roomJIDStr"];
self.primitiveRoomJID = roomJID;
self.primitiveRoomJIDStr = [roomJID full];
[self didChangeValueForKey:@"roomJID"];
[self didChangeValueForKey:@"roomJIDStr"];
}
- (void)setRoomJIDStr:(NSString *)roomJIDStr
{
[self willChangeValueForKey:@"roomJID"];
[self willChangeValueForKey:@"roomJIDStr"];
self.primitiveRoomJID = [XMPPJID jidWithString:roomJIDStr];
self.primitiveRoomJIDStr = roomJIDStr;
[self didChangeValueForKey:@"roomJID"];
[self didChangeValueForKey:@"roomJIDStr"];
}
#pragma mark Transient jid
- (XMPPJID *)jid
{
// Create and cache on demand
[self willAccessValueForKey:@"jid"];
XMPPJID *tmp = self.primitiveJid;
[self didAccessValueForKey:@"jid"];
if (tmp == nil)
{
NSString *jidStr = self.jidStr;
if (jidStr)
{
tmp = [XMPPJID jidWithString:jidStr];
self.primitiveJid = tmp;
}
}
return tmp;
}
- (void)setJid:(XMPPJID *)jid
{
[self willChangeValueForKey:@"jid"];
[self willChangeValueForKey:@"jidStr"];
self.primitiveJid = jid;
self.primitiveJidStr = [jid full];
[self didChangeValueForKey:@"jid"];
[self didChangeValueForKey:@"jidStr"];
}
- (void)setJidStr:(NSString *)jidStr
{
[self willChangeValueForKey:@"jid"];
[self willChangeValueForKey:@"jidStr"];
self.primitiveJid = [XMPPJID jidWithString:jidStr];
self.primitiveJidStr = jidStr;
[self didChangeValueForKey:@"jid"];
[self didChangeValueForKey:@"jidStr"];
}
#pragma mark Scalar
- (BOOL)isFromMe
{
return [[self fromMe] boolValue];
}
- (void)setIsFromMe:(BOOL)value
{
self.fromMe = @(value);
}
#pragma mark - Message
- (XMPPMessage *)message
{
// Create and cache on demand
[self willAccessValueForKey:@"message"];
XMPPMessage *message = self.primitiveMessage;
[self didAccessValueForKey:@"message"];
if (message == nil)
{
NSString *messageStr = self.messageStr;
if (messageStr)
{
NSXMLElement *element = [[NSXMLElement alloc] initWithXMLString:
messageStr error:nil];
message = [XMPPMessage messageFromElement:element];
self.primitiveMessage = message;
}
}
return message;
}
- (void)setMessage:(XMPPMessage *)message
{
[self willChangeValueForKey:@"message"];
[self willChangeValueForKey:@"messageStr"];
self.primitiveMessage = message;
self.primitiveMessageStr = [message compactXMLString];
[self didChangeValueForKey:@"message"];
[self didChangeValueForKey:@"messageStr"];
}
- (void)setMessageStr:(NSString *)messageStr
{
[self willChangeValueForKey:@"message"];
[self willChangeValueForKey:@"messageStr"];
NSXMLElement *element = [[NSXMLElement alloc] initWithXMLString:messageStr error:nil];
self.primitiveMessage = [XMPPMessage messageFromElement:element];
self.primitiveMessageStr = messageStr;
[self didChangeValueForKey:@"message"];
[self didChangeValueForKey:@"messageStr"];
}
@end
@@ -0,0 +1,41 @@
#import <Foundation/Foundation.h>
#import "XMPPRoom.h"
#import "XMPPRoomOccupant.h"
@interface XMPPRoomOccupantHybridMemoryStorageObject : NSObject <XMPPRoomOccupant, NSCopying, NSSecureCoding>
- (id)initWithPresence:(XMPPPresence *)presence streamFullJid:(XMPPJID *)streamFullJid;
- (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;
@property (readonly) NSDate * createdAt;
/**
* Since XMPPRoomHybridStorage supports multiple xmppStreams,
* this property may be used to differentiate between occupant objects.
**/
@property (readonly) XMPPJID * streamFullJid;
/**
* Sample comparison methods.
**/
- (NSComparisonResult)compareByNickname:(XMPPRoomOccupantHybridMemoryStorageObject *)another;
- (NSComparisonResult)compareByCreatedAt:(XMPPRoomOccupantHybridMemoryStorageObject *)another;
@end
@@ -0,0 +1,237 @@
#import "XMPPRoomOccupantHybridMemoryStorageObject.h"
@implementation XMPPRoomOccupantHybridMemoryStorageObject
{
XMPPPresence *presence;
XMPPJID *jid;
NSDate *createdAt;
XMPPJID *streamFullJid;
}
- (id)initWithPresence:(XMPPPresence *)inPresence streamFullJid:(XMPPJID *)inStreamFullJid
{
NSParameterAssert(inPresence != nil);
NSParameterAssert(inStreamFullJid != nil);
if ((self = [super init]))
{
presence = inPresence;
jid = [presence from];
createdAt = [[NSDate alloc] init];
streamFullJid = inStreamFullJid;
}
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"];
createdAt = [coder decodeObjectOfClass:[NSDate class] forKey:@"createdAt"];
streamFullJid = [coder decodeObjectOfClass:[XMPPJID class] forKey:@"streamFullJid"];
}
else
{
presence = [coder decodeObjectForKey:@"presence"];
jid = [coder decodeObjectForKey:@"jid"];
createdAt = [coder decodeObjectForKey:@"createdAt"];
streamFullJid = [coder decodeObjectForKey:@"streamFullJid"];
}
}
else
{
presence = [coder decodeObject];
jid = [coder decodeObject];
createdAt = [coder decodeObject];
streamFullJid = [coder decodeObject];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
if ([coder allowsKeyedCoding])
{
[coder encodeObject:presence forKey:@"presence"];
[coder encodeObject:jid forKey:@"jid"];
[coder encodeObject:createdAt forKey:@"createdAt"];
[coder encodeObject:streamFullJid forKey:@"streamFullJid"];
}
else
{
[coder encodeObject:presence];
[coder encodeObject:jid];
[coder encodeObject:createdAt];
[coder encodeObject:streamFullJid];
}
}
+ (BOOL) supportsSecureCoding
{
return YES;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Copying
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (id)copyWithZone:(NSZone *)zone
{
// We use [self class] to support subclassing
XMPPRoomOccupantHybridMemoryStorageObject *deepCopy;
deepCopy = (XMPPRoomOccupantHybridMemoryStorageObject *)[[[self class] alloc] init];
deepCopy->presence = [presence copy];
deepCopy->jid = [jid copy];
deepCopy->createdAt = [createdAt copy];
deepCopy->streamFullJid = [streamFullJid copy];
return deepCopy;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Updates
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)updateWithPresence:(XMPPPresence *)inPresence
{
presence = inPresence;
jid = [inPresence from];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Properties
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (XMPPPresence *)presence
{
return presence;
}
- (XMPPJID *)jid
{
return jid;
}
- (XMPPJID *)roomJID
{
return [jid bareJID];
}
- (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;
}
- (NSDate *)createdAt
{
return createdAt;
}
- (XMPPJID *)streamFullJid
{
return streamFullJid;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Compare
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (NSComparisonResult)compareByNickname:(XMPPRoomOccupantHybridMemoryStorageObject *)another
{
return [self.nickname compare:another.nickname];
}
- (NSComparisonResult)compareByCreatedAt:(XMPPRoomOccupantHybridMemoryStorageObject *)another
{
return [self.createdAt compare:another.createdAt];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark NSObject Methods
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (NSUInteger)hash
{
return [jid hash];
}
- (BOOL)isEqual:(id)anObject
{
if ([anObject isMemberOfClass:[self class]])
{
XMPPRoomOccupantHybridMemoryStorageObject *another = (XMPPRoomOccupantHybridMemoryStorageObject *)anObject;
if ([jid isEqualToJID:[another jid]])
{
if ([streamFullJid isEqualToJID:[another streamFullJid]])
{
return YES;
}
}
}
return NO;
}
@end