Skip to content

Commit

Permalink
Merge pull request #750 from esl/andres.XMPPMUCLight
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisballinger committed Jun 20, 2016
2 parents 183db54 + 94edd18 commit ccd62f0
Show file tree
Hide file tree
Showing 16 changed files with 2,922 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Extensions/XEP-0045/XMPPMUC.m
Expand Up @@ -192,7 +192,7 @@ - (BOOL)discoverRoomsForServiceNamed:(NSString *)serviceName
[xmppStream sendElement:iq];
hasRequestedRooms = YES;
}};

if (dispatch_get_specific(moduleQueueTag))
block();
else
Expand Down
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="10171" systemVersion="15E65" minimumToolsVersion="Xcode 7.0">
<entity name="XMPPRoomLightMessageCoreDataStorageObject" representedClassName="XMPPRoomMessageCoreDataStorageObject" 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="XMPPRoomLightMessageCoreDataStorageObject" positionX="-63" positionY="-18" width="128" height="240"/>
</elements>
</model>
@@ -0,0 +1,18 @@
//
// XMPPRoomLightCoreDataStorage.h
// Mangosta
//
// Created by Andres Canal on 6/8/16.
// Copyright © 2016 Inaka. All rights reserved.
//

#import <XMPPFramework/XMPPFramework.h>
#import <XMPPFramework/XMPPCoreDataStorage.h>
#import "XMPPRoomLight.h"

@interface XMPPRoomLightCoreDataStorage : XMPPCoreDataStorage <XMPPRoomLightStorage>

- (void)handleIncomingMessage:(XMPPMessage *)message room:(XMPPRoomLight *)room;
- (void)handleOutgoingMessage:(XMPPMessage *)message room:(XMPPRoomLight *)room;

@end
207 changes: 207 additions & 0 deletions Extensions/XMPPMUCLight/CoreDataStorage/XMPPRoomLightCoreDataStorage.m
@@ -0,0 +1,207 @@
//
// XMPPRoomLightCoreDataStorage.m
// Mangosta
//
// Created by Andres Canal on 6/8/16.
// Copyright © 2016 Inaka. All rights reserved.
//

#import "XMPPRoomLightCoreDataStorage.h"
#import "XMPPCoreDataStorageProtected.h"
#import "NSXMLElement+XEP_0203.h"
#import "XMPPRoomLightMessageCoreDataStorageObject.h"

@implementation XMPPRoomLightCoreDataStorage{
NSString *messageEntityName;
}

- (void)commonInit{

[super commonInit];

messageEntityName = NSStringFromClass([XMPPRoomLightMessageCoreDataStorageObject class]);

}

- (void)handleIncomingMessage:(XMPPMessage *)message room:(XMPPRoomLight *)room{
XMPPStream *xmppStream = room.xmppStream;

XMPPJID *myRoomJID = [XMPPJID jidWithUser:message.from.user
domain:message.from.domain
resource:xmppStream.myJID.full];

XMPPJID *messageJID = [message from];

// Ignore - if message is mine and it was not delayed then ignore
// becuase we handled it in handleOutgoingMessage:room:
if ([myRoomJID isEqualToJID:messageJID] && ![message wasDelayed]){
return;
}

[self scheduleBlock:^{
if (![self existsMessage:message forRoom:room stream:xmppStream]){
[self insertMessage:message outgoing:false forRoom:room stream:xmppStream];
}
}];
}

- (void)handleOutgoingMessage:(XMPPMessage *)message room:(XMPPRoomLight *)room{
XMPPStream *xmppStream = room.xmppStream;

[self scheduleBlock:^{
[self insertMessage:message outgoing:YES forRoom:room stream:xmppStream];
}];
}

- (BOOL)existsMessage:(XMPPMessage *)message forRoom:(XMPPRoomLight *)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];

return ([results count] > 0);
}

- (void)insertMessage:(XMPPMessage *)message
outgoing:(BOOL)isOutgoing
forRoom:(XMPPRoomLight *)room
stream:(XMPPStream *)xmppStream{
// Extract needed information

XMPPJID *myRoomJID = [XMPPJID jidWithUser:room.roomJID.user
domain:room.roomJID.domain
resource:xmppStream.myJID.full];

XMPPJID *roomJID = room.roomJID;
XMPPJID *messageJID = isOutgoing ? 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];

XMPPRoomLightMessageCoreDataStorageObject *roomMessage = (XMPPRoomLightMessageCoreDataStorageObject *)
[[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];
[self didInsertMessage:roomMessage];
}

- (void)didInsertMessage:(XMPPRoomLightMessageCoreDataStorageObject *)message{
// Override me if you're extending the XMPPRoomMessageCoreDataStorageObject class to add additional properties.
// You can update your additional properties here.
//
// At this point the standard properties have already been set.
// So you can, for example, access the XMPPMessage via message.message.
}

- (NSEntityDescription *)messageEntity:(NSManagedObjectContext *)moc{

// This method should be thread-safe.
// So be sure to access the entity name through the property accessor.

return [NSEntityDescription entityForName:[self messageEntityName] inManagedObjectContext:moc];
}

- (NSString *)messageEntityName{

__block NSString *result = nil;

dispatch_block_t block = ^{
result = messageEntityName;
};

if (dispatch_get_specific(storageQueueTag))
block();
else
dispatch_sync(storageQueue, block);

return result;
}

@end
@@ -0,0 +1,46 @@
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

#import "XMPPRoomLight.h"
#import "XMPPFramework/XMPPRoomMessage.h"

@interface XMPPRoomLightMessageCoreDataStorageObject : 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 XMPPRoomCoreDataStorage is shared between multiple xmppStream's,
* this may be needed to distinguish between the streams.
**/
@property (nonatomic, strong) NSString *streamBareJidStr;

@end

0 comments on commit ccd62f0

Please sign in to comment.