- Update
This commit is contained in:
1 parent
d281d765ea
commit
5b074a5176
1261 files changed
+199158
-7303
No files matched your search
+8
@@ -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>XMPPMessageArchiving.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1170" systemVersion="11D50b" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
||||
<entity name="XMPPMessageArchiving_Contact_CoreDataObject" representedClassName="XMPPMessageArchiving_Contact_CoreDataObject" syncable="YES">
|
||||
<attribute name="bareJid" optional="YES" transient="YES" syncable="YES"/>
|
||||
<attribute name="bareJidStr" attributeType="String" indexed="YES" syncable="YES"/>
|
||||
<attribute name="mostRecentMessageBody" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="mostRecentMessageOutgoing" attributeType="Boolean" defaultValueString="NO" syncable="YES"/>
|
||||
<attribute name="mostRecentMessageTimestamp" optional="YES" attributeType="Date" indexed="YES" syncable="YES"/>
|
||||
<attribute name="streamBareJidStr" attributeType="String" indexed="YES" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="XMPPMessageArchiving_Message_CoreDataObject" representedClassName="XMPPMessageArchiving_Message_CoreDataObject" syncable="YES">
|
||||
<attribute name="bareJid" optional="YES" transient="YES" syncable="YES"/>
|
||||
<attribute name="bareJidStr" attributeType="String" indexed="YES" syncable="YES"/>
|
||||
<attribute name="body" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="composing" attributeType="Boolean" defaultValueString="NO" indexed="YES" syncable="YES"/>
|
||||
<attribute name="message" optional="YES" transient="YES" syncable="YES"/>
|
||||
<attribute name="messageStr" attributeType="String" syncable="YES"/>
|
||||
<attribute name="outgoing" attributeType="Boolean" defaultValueString="NO" indexed="YES" syncable="YES"/>
|
||||
<attribute name="streamBareJidStr" attributeType="String" indexed="YES" syncable="YES"/>
|
||||
<attribute name="thread" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="timestamp" attributeType="Date" indexed="YES" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="XMPPMessageArchiving_Contact_CoreDataObject" positionX="160" positionY="192" width="128" height="135"/>
|
||||
<element name="XMPPMessageArchiving_Message_CoreDataObject" positionX="160" positionY="192" width="128" height="195"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -0,0 +1,68 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "XMPPCoreDataStorage.h"
|
||||
#import "XMPPMessageArchiving.h"
|
||||
#import "XMPPMessageArchiving_Message_CoreDataObject.h"
|
||||
#import "XMPPMessageArchiving_Contact_CoreDataObject.h"
|
||||
|
||||
|
||||
@interface XMPPMessageArchivingCoreDataStorage : XMPPCoreDataStorage <XMPPMessageArchivingStorage>
|
||||
{
|
||||
/* Inherited protected variables from XMPPCoreDataStorage
|
||||
|
||||
NSString *databaseFileName;
|
||||
NSUInteger saveThreshold;
|
||||
|
||||
dispatch_queue_t storageQueue;
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to get an instance with the default database name.
|
||||
*
|
||||
* IMPORTANT:
|
||||
* You are NOT required to use the sharedInstance.
|
||||
*
|
||||
* If your application uses multiple xmppStreams, and you use a sharedInstance of this class,
|
||||
* then all of your streams 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;
|
||||
|
||||
|
||||
@property (strong) NSString *messageEntityName;
|
||||
@property (strong) NSString *contactEntityName;
|
||||
|
||||
- (NSEntityDescription *)messageEntity:(NSManagedObjectContext *)moc;
|
||||
- (NSEntityDescription *)contactEntity:(NSManagedObjectContext *)moc;
|
||||
|
||||
- (XMPPMessageArchiving_Contact_CoreDataObject *)contactForMessage:(XMPPMessageArchiving_Message_CoreDataObject *)msg;
|
||||
|
||||
- (XMPPMessageArchiving_Contact_CoreDataObject *)contactWithJid:(XMPPJID *)contactJid
|
||||
streamJid:(XMPPJID *)streamJid
|
||||
managedObjectContext:(NSManagedObjectContext *)moc;
|
||||
|
||||
- (XMPPMessageArchiving_Contact_CoreDataObject *)contactWithBareJidStr:(NSString *)contactBareJidStr
|
||||
streamBareJidStr:(NSString *)streamBareJidStr
|
||||
managedObjectContext:(NSManagedObjectContext *)moc;
|
||||
|
||||
/* Inherited from XMPPCoreDataStorage
|
||||
* Please see the XMPPCoreDataStorage header file for extensive documentation.
|
||||
|
||||
- (id)initWithDatabaseFilename:(NSString *)databaseFileName storeOptions:(NSDictionary *)storeOptions;
|
||||
- (id)initWithInMemoryStore;
|
||||
|
||||
@property (readonly) NSString *databaseFileName;
|
||||
|
||||
@property (readwrite) NSUInteger saveThreshold;
|
||||
|
||||
@property (readonly) NSManagedObjectModel *managedObjectModel;
|
||||
@property (readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
|
||||
|
||||
@property (readonly) NSManagedObjectContext *mainThreadManagedObjectContext;
|
||||
|
||||
*/
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,488 @@
|
||||
#import "XMPPMessageArchivingCoreDataStorage.h"
|
||||
#import "XMPPCoreDataStorageProtected.h"
|
||||
#import "XMPPLogging.h"
|
||||
#import "NSXMLElement+XEP_0203.h"
|
||||
#import "XMPPMessage+XEP_0085.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
|
||||
// Log flags: trace
|
||||
#if DEBUG
|
||||
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; // VERBOSE; // | XMPP_LOG_FLAG_TRACE;
|
||||
#else
|
||||
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
|
||||
#endif
|
||||
|
||||
@interface XMPPMessageArchivingCoreDataStorage ()
|
||||
{
|
||||
NSString *messageEntityName;
|
||||
NSString *contactEntityName;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation XMPPMessageArchivingCoreDataStorage
|
||||
|
||||
static XMPPMessageArchivingCoreDataStorage *sharedInstance;
|
||||
|
||||
+ (instancetype)sharedInstance
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
|
||||
sharedInstance = [[XMPPMessageArchivingCoreDataStorage alloc] initWithDatabaseFilename:nil storeOptions:nil];
|
||||
});
|
||||
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Documentation from the superclass (XMPPCoreDataStorage):
|
||||
*
|
||||
* If your subclass needs to do anything for init, it can do so easily by overriding this method.
|
||||
* All public init methods will invoke this method at the end of their implementation.
|
||||
*
|
||||
* Important: If overriden you must invoke [super commonInit] at some point.
|
||||
**/
|
||||
- (void)commonInit
|
||||
{
|
||||
[super commonInit];
|
||||
|
||||
messageEntityName = @"XMPPMessageArchiving_Message_CoreDataObject";
|
||||
contactEntityName = @"XMPPMessageArchiving_Contact_CoreDataObject";
|
||||
}
|
||||
|
||||
/**
|
||||
* Documentation from the superclass (XMPPCoreDataStorage):
|
||||
*
|
||||
* Override me, if needed, to provide customized behavior.
|
||||
* For example, you may want to perform cleanup of any non-persistent data before you start using the database.
|
||||
*
|
||||
* The default implementation does nothing.
|
||||
**/
|
||||
- (void)didCreateManagedObjectContext
|
||||
{
|
||||
// If there are any "composing" messages in the database, delete them (as they are temporary).
|
||||
|
||||
NSManagedObjectContext *moc = [self managedObjectContext];
|
||||
NSEntityDescription *messageEntity = [self messageEntity:moc];
|
||||
|
||||
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"composing == YES"];
|
||||
|
||||
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
|
||||
fetchRequest.entity = messageEntity;
|
||||
fetchRequest.predicate = predicate;
|
||||
fetchRequest.fetchBatchSize = saveThreshold;
|
||||
|
||||
NSError *error = nil;
|
||||
NSArray *messages = [moc executeFetchRequest:fetchRequest error:&error];
|
||||
|
||||
if (messages == nil)
|
||||
{
|
||||
XMPPLogError(@"%@: %@ - Error executing fetchRequest: %@", [self class], THIS_METHOD, error);
|
||||
return;
|
||||
}
|
||||
|
||||
NSUInteger count = 0;
|
||||
|
||||
for (XMPPMessageArchiving_Message_CoreDataObject *message in messages)
|
||||
{
|
||||
[moc deleteObject:message];
|
||||
|
||||
if (++count > saveThreshold)
|
||||
{
|
||||
if (![moc save:&error])
|
||||
{
|
||||
XMPPLogWarn(@"%@: Error saving - %@ %@", [self class], error, [error userInfo]);
|
||||
[moc rollback];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
if (![moc save:&error])
|
||||
{
|
||||
XMPPLogWarn(@"%@: Error saving - %@ %@", [self class], error, [error userInfo]);
|
||||
[moc rollback];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Internal API
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)willInsertMessage:(XMPPMessageArchiving_Message_CoreDataObject *)message
|
||||
{
|
||||
// Override hook
|
||||
}
|
||||
|
||||
- (void)didUpdateMessage:(XMPPMessageArchiving_Message_CoreDataObject *)message
|
||||
{
|
||||
// Override hook
|
||||
}
|
||||
|
||||
- (void)willDeleteMessage:(XMPPMessageArchiving_Message_CoreDataObject *)message
|
||||
{
|
||||
// Override hook
|
||||
}
|
||||
|
||||
- (void)willInsertContact:(XMPPMessageArchiving_Contact_CoreDataObject *)contact
|
||||
{
|
||||
// Override hook
|
||||
}
|
||||
|
||||
- (void)didUpdateContact:(XMPPMessageArchiving_Contact_CoreDataObject *)contact
|
||||
{
|
||||
// Override hook
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Private API
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (XMPPMessageArchiving_Message_CoreDataObject *)composingMessageWithJid:(XMPPJID *)messageJid
|
||||
streamJid:(XMPPJID *)streamJid
|
||||
outgoing:(BOOL)isOutgoing
|
||||
managedObjectContext:(NSManagedObjectContext *)moc
|
||||
{
|
||||
XMPPMessageArchiving_Message_CoreDataObject *result = nil;
|
||||
|
||||
NSEntityDescription *messageEntity = [self messageEntity:moc];
|
||||
|
||||
// Order matters:
|
||||
// 1. composing - most likely not many with it set to YES in database
|
||||
// 2. bareJidStr - splits database by number of conversations
|
||||
// 3. outgoing - splits database in half
|
||||
// 4. streamBareJidStr - might not limit database at all
|
||||
|
||||
NSString *predicateFrmt = @"composing == YES AND bareJidStr == %@ AND outgoing == %@ AND streamBareJidStr == %@";
|
||||
NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateFrmt,
|
||||
[messageJid bare], @(isOutgoing),
|
||||
[streamJid bare]];
|
||||
|
||||
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"timestamp" ascending:NO];
|
||||
|
||||
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
|
||||
fetchRequest.entity = messageEntity;
|
||||
fetchRequest.predicate = predicate;
|
||||
fetchRequest.sortDescriptors = @[sortDescriptor];
|
||||
fetchRequest.fetchLimit = 1;
|
||||
|
||||
NSError *error = nil;
|
||||
NSArray *results = [moc executeFetchRequest:fetchRequest error:&error];
|
||||
|
||||
if (results == nil || error)
|
||||
{
|
||||
XMPPLogError(@"%@: %@ - Error executing fetchRequest: %@", THIS_FILE, THIS_METHOD, fetchRequest);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = (XMPPMessageArchiving_Message_CoreDataObject *)[results lastObject];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Public API
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (XMPPMessageArchiving_Contact_CoreDataObject *)contactForMessage:(XMPPMessageArchiving_Message_CoreDataObject *)msg
|
||||
{
|
||||
// Potential override hook
|
||||
|
||||
return [self contactWithBareJidStr:msg.bareJidStr
|
||||
streamBareJidStr:msg.streamBareJidStr
|
||||
managedObjectContext:msg.managedObjectContext];
|
||||
}
|
||||
|
||||
- (XMPPMessageArchiving_Contact_CoreDataObject *)contactWithJid:(XMPPJID *)contactJid
|
||||
streamJid:(XMPPJID *)streamJid
|
||||
managedObjectContext:(NSManagedObjectContext *)moc
|
||||
{
|
||||
return [self contactWithBareJidStr:[contactJid bare]
|
||||
streamBareJidStr:[streamJid bare]
|
||||
managedObjectContext:moc];
|
||||
}
|
||||
|
||||
- (XMPPMessageArchiving_Contact_CoreDataObject *)contactWithBareJidStr:(NSString *)contactBareJidStr
|
||||
streamBareJidStr:(NSString *)streamBareJidStr
|
||||
managedObjectContext:(NSManagedObjectContext *)moc
|
||||
{
|
||||
NSEntityDescription *entity = [self contactEntity:moc];
|
||||
|
||||
NSPredicate *predicate;
|
||||
if (streamBareJidStr)
|
||||
{
|
||||
predicate = [NSPredicate predicateWithFormat:@"bareJidStr == %@ AND streamBareJidStr == %@",
|
||||
contactBareJidStr, streamBareJidStr];
|
||||
}
|
||||
else
|
||||
{
|
||||
predicate = [NSPredicate predicateWithFormat:@"bareJidStr == %@", contactBareJidStr];
|
||||
}
|
||||
|
||||
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
|
||||
[fetchRequest setEntity:entity];
|
||||
[fetchRequest setFetchLimit:1];
|
||||
[fetchRequest setPredicate:predicate];
|
||||
|
||||
NSError *error = nil;
|
||||
NSArray *results = [moc executeFetchRequest:fetchRequest error:&error];
|
||||
|
||||
if (results == nil)
|
||||
{
|
||||
XMPPLogError(@"%@: %@ - Fetch request error: %@", THIS_FILE, THIS_METHOD, error);
|
||||
return nil;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (XMPPMessageArchiving_Contact_CoreDataObject *)[results lastObject];
|
||||
}
|
||||
}
|
||||
|
||||
- (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;
|
||||
}
|
||||
|
||||
- (void)setMessageEntityName:(NSString *)entityName
|
||||
{
|
||||
dispatch_block_t block = ^{
|
||||
messageEntityName = entityName;
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(storageQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_async(storageQueue, block);
|
||||
}
|
||||
|
||||
- (NSString *)contactEntityName
|
||||
{
|
||||
__block NSString *result = nil;
|
||||
|
||||
dispatch_block_t block = ^{
|
||||
result = contactEntityName;
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(storageQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_sync(storageQueue, block);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setContactEntityName:(NSString *)entityName
|
||||
{
|
||||
dispatch_block_t block = ^{
|
||||
contactEntityName = entityName;
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(storageQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_async(storageQueue, block);
|
||||
}
|
||||
|
||||
- (NSEntityDescription *)messageEntity:(NSManagedObjectContext *)moc
|
||||
{
|
||||
// This is a public method, and may be invoked on any queue.
|
||||
// So be sure to go through the public accessor for the entity name.
|
||||
|
||||
return [NSEntityDescription entityForName:[self messageEntityName] inManagedObjectContext:moc];
|
||||
}
|
||||
|
||||
- (NSEntityDescription *)contactEntity:(NSManagedObjectContext *)moc
|
||||
{
|
||||
// This is a public method, and may be invoked on any queue.
|
||||
// So be sure to go through the public accessor for the entity name.
|
||||
|
||||
return [NSEntityDescription entityForName:[self contactEntityName] inManagedObjectContext:moc];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Storage Protocol
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (BOOL)configureWithParent:(XMPPMessageArchiving *)aParent queue:(dispatch_queue_t)queue
|
||||
{
|
||||
return [super configureWithParent:aParent queue:queue];
|
||||
}
|
||||
|
||||
- (void)archiveMessage:(XMPPMessage *)message outgoing:(BOOL)isOutgoing xmppStream:(XMPPStream *)xmppStream
|
||||
{
|
||||
// Message should either have a body, or be a composing notification
|
||||
|
||||
NSString *messageBody = [[message elementForName:@"body"] stringValue];
|
||||
BOOL isComposing = NO;
|
||||
BOOL shouldDeleteComposingMessage = NO;
|
||||
|
||||
if ([messageBody length] == 0)
|
||||
{
|
||||
// Message doesn't have a body.
|
||||
// Check to see if it has a chat state (composing, paused, etc).
|
||||
|
||||
isComposing = [message hasComposingChatState];
|
||||
if (!isComposing)
|
||||
{
|
||||
if ([message hasChatState])
|
||||
{
|
||||
// Message has non-composing chat state.
|
||||
// So if there is a current composing message in the database,
|
||||
// then we need to delete it.
|
||||
shouldDeleteComposingMessage = YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Message has no body and no chat state.
|
||||
// Nothing to do with it.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[self scheduleBlock:^{
|
||||
|
||||
NSManagedObjectContext *moc = [self managedObjectContext];
|
||||
XMPPJID *myJid = [self myJIDForXMPPStream:xmppStream];
|
||||
|
||||
XMPPJID *messageJid = isOutgoing ? [message to] : [message from];
|
||||
|
||||
// Fetch-n-Update OR Insert new message
|
||||
|
||||
XMPPMessageArchiving_Message_CoreDataObject *archivedMessage =
|
||||
[self composingMessageWithJid:messageJid
|
||||
streamJid:myJid
|
||||
outgoing:isOutgoing
|
||||
managedObjectContext:moc];
|
||||
|
||||
if (shouldDeleteComposingMessage)
|
||||
{
|
||||
if (archivedMessage)
|
||||
{
|
||||
[self willDeleteMessage:archivedMessage]; // Override hook
|
||||
[moc deleteObject:archivedMessage];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Composing message has already been deleted (or never existed)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
XMPPLogVerbose(@"Previous archivedMessage: %@", archivedMessage);
|
||||
|
||||
BOOL didCreateNewArchivedMessage = NO;
|
||||
if (archivedMessage == nil)
|
||||
{
|
||||
archivedMessage = (XMPPMessageArchiving_Message_CoreDataObject *)
|
||||
[[NSManagedObject alloc] initWithEntity:[self messageEntity:moc]
|
||||
insertIntoManagedObjectContext:nil];
|
||||
|
||||
didCreateNewArchivedMessage = YES;
|
||||
}
|
||||
|
||||
archivedMessage.message = message;
|
||||
archivedMessage.body = messageBody;
|
||||
|
||||
archivedMessage.bareJid = [messageJid bareJID];
|
||||
archivedMessage.streamBareJidStr = [myJid bare];
|
||||
|
||||
NSDate *timestamp = [message delayedDeliveryDate];
|
||||
if (timestamp)
|
||||
archivedMessage.timestamp = timestamp;
|
||||
else
|
||||
archivedMessage.timestamp = [[NSDate alloc] init];
|
||||
|
||||
archivedMessage.thread = [[message elementForName:@"thread"] stringValue];
|
||||
archivedMessage.isOutgoing = isOutgoing;
|
||||
archivedMessage.isComposing = isComposing;
|
||||
|
||||
XMPPLogVerbose(@"New archivedMessage: %@", archivedMessage);
|
||||
|
||||
if (didCreateNewArchivedMessage) // [archivedMessage isInserted] doesn't seem to work
|
||||
{
|
||||
XMPPLogVerbose(@"Inserting message...");
|
||||
|
||||
[archivedMessage willInsertObject]; // Override hook
|
||||
[self willInsertMessage:archivedMessage]; // Override hook
|
||||
[moc insertObject:archivedMessage];
|
||||
}
|
||||
else
|
||||
{
|
||||
XMPPLogVerbose(@"Updating message...");
|
||||
|
||||
[archivedMessage didUpdateObject]; // Override hook
|
||||
[self didUpdateMessage:archivedMessage]; // Override hook
|
||||
}
|
||||
|
||||
// Create or update contact (if message with actual content)
|
||||
|
||||
if ([messageBody length] > 0)
|
||||
{
|
||||
BOOL didCreateNewContact = NO;
|
||||
|
||||
XMPPMessageArchiving_Contact_CoreDataObject *contact = [self contactForMessage:archivedMessage];
|
||||
XMPPLogVerbose(@"Previous contact: %@", contact);
|
||||
|
||||
if (contact == nil)
|
||||
{
|
||||
contact = (XMPPMessageArchiving_Contact_CoreDataObject *)
|
||||
[[NSManagedObject alloc] initWithEntity:[self contactEntity:moc]
|
||||
insertIntoManagedObjectContext:nil];
|
||||
|
||||
didCreateNewContact = YES;
|
||||
}
|
||||
|
||||
contact.streamBareJidStr = archivedMessage.streamBareJidStr;
|
||||
contact.bareJid = archivedMessage.bareJid;
|
||||
|
||||
contact.mostRecentMessageTimestamp = archivedMessage.timestamp;
|
||||
contact.mostRecentMessageBody = archivedMessage.body;
|
||||
contact.mostRecentMessageOutgoing = @(isOutgoing);
|
||||
|
||||
XMPPLogVerbose(@"New contact: %@", contact);
|
||||
|
||||
if (didCreateNewContact) // [contact isInserted] doesn't seem to work
|
||||
{
|
||||
XMPPLogVerbose(@"Inserting contact...");
|
||||
|
||||
[contact willInsertObject]; // Override hook
|
||||
[self willInsertContact:contact]; // Override hook
|
||||
[moc insertObject:contact];
|
||||
}
|
||||
else
|
||||
{
|
||||
XMPPLogVerbose(@"Updating contact...");
|
||||
|
||||
[contact didUpdateObject]; // Override hook
|
||||
[self didUpdateContact:contact]; // Override hook
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,36 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreData/CoreData.h>
|
||||
#import "XMPP.h"
|
||||
|
||||
|
||||
@interface XMPPMessageArchiving_Contact_CoreDataObject : NSManagedObject
|
||||
|
||||
@property (nonatomic, strong) XMPPJID * bareJid; // Transient (proper type, not on disk)
|
||||
@property (nonatomic, strong) NSString * bareJidStr; // Shadow (binary data, written to disk)
|
||||
|
||||
@property (nonatomic, strong) NSDate * mostRecentMessageTimestamp;
|
||||
@property (nonatomic, strong) NSString * mostRecentMessageBody;
|
||||
@property (nonatomic, strong) NSNumber * mostRecentMessageOutgoing;
|
||||
|
||||
@property (nonatomic, strong) NSString * streamBareJidStr;
|
||||
|
||||
/**
|
||||
* This method is called immediately before the object is inserted into the managedObjectContext.
|
||||
* At this point, all normal properties have been set.
|
||||
*
|
||||
* If you extend XMPPMessageArchiving_Contact_CoreDataObject,
|
||||
* you can use this method as a hook to set your custom properties.
|
||||
**/
|
||||
- (void)willInsertObject;
|
||||
|
||||
/**
|
||||
* This method is called after any properties on the object have been updated,
|
||||
* due to a message being added to the conversation.
|
||||
* At this point, any changed properties have been updated.
|
||||
*
|
||||
* If you extend XMPPMessageArchiving_Contact_CoreDataObject,
|
||||
* you can use this method as a hook to update your custom properties.
|
||||
**/
|
||||
- (void)didUpdateObject;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,94 @@
|
||||
#import "XMPPMessageArchiving_Contact_CoreDataObject.h"
|
||||
|
||||
|
||||
@interface XMPPMessageArchiving_Contact_CoreDataObject ()
|
||||
|
||||
@property (nonatomic, strong) XMPPJID * primitiveBareJid;
|
||||
@property (nonatomic, strong) NSString * primitiveBareJidStr;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation XMPPMessageArchiving_Contact_CoreDataObject
|
||||
|
||||
@dynamic bareJid, primitiveBareJid;
|
||||
@dynamic bareJidStr, primitiveBareJidStr;
|
||||
@dynamic mostRecentMessageTimestamp;
|
||||
@dynamic mostRecentMessageBody;
|
||||
@dynamic mostRecentMessageOutgoing;
|
||||
@dynamic streamBareJidStr;
|
||||
|
||||
#pragma mark Transient bareJid
|
||||
|
||||
- (XMPPJID *)bareJid
|
||||
{
|
||||
// Create and cache on demand
|
||||
|
||||
[self willAccessValueForKey:@"bareJid"];
|
||||
XMPPJID *tmp = self.primitiveBareJid;
|
||||
[self didAccessValueForKey:@"bareJid"];
|
||||
|
||||
if (tmp == nil)
|
||||
{
|
||||
NSString *bareJidStr = self.bareJidStr;
|
||||
if (bareJidStr)
|
||||
{
|
||||
tmp = [XMPPJID jidWithString:bareJidStr];
|
||||
self.primitiveBareJid = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
- (void)setBareJid:(XMPPJID *)bareJid
|
||||
{
|
||||
if ([self.bareJid isEqualToJID:bareJid options:XMPPJIDCompareBare])
|
||||
{
|
||||
return; // No change
|
||||
}
|
||||
|
||||
[self willChangeValueForKey:@"bareJid"];
|
||||
[self willChangeValueForKey:@"bareJidStr"];
|
||||
|
||||
self.primitiveBareJid = [bareJid bareJID];
|
||||
self.primitiveBareJidStr = [bareJid bare];
|
||||
|
||||
[self didChangeValueForKey:@"bareJid"];
|
||||
[self didChangeValueForKey:@"bareJidStr"];
|
||||
}
|
||||
|
||||
- (void)setBareJidStr:(NSString *)bareJidStr
|
||||
{
|
||||
if ([self.bareJidStr isEqualToString:bareJidStr])
|
||||
{
|
||||
return; // No change
|
||||
}
|
||||
|
||||
[self willChangeValueForKey:@"bareJid"];
|
||||
[self willChangeValueForKey:@"bareJidStr"];
|
||||
|
||||
XMPPJID *bareJid = [[XMPPJID jidWithString:bareJidStr] bareJID];
|
||||
|
||||
self.primitiveBareJid = bareJid;
|
||||
self.primitiveBareJidStr = [bareJid bare];
|
||||
|
||||
[self didChangeValueForKey:@"bareJid"];
|
||||
[self didChangeValueForKey:@"bareJidStr"];
|
||||
}
|
||||
|
||||
#pragma mark Hooks
|
||||
|
||||
- (void)willInsertObject
|
||||
{
|
||||
// If you extend XMPPMessageArchiving_Contact_CoreDataObject,
|
||||
// you can override this method to use as a hook to set your own custom properties.
|
||||
}
|
||||
|
||||
- (void)didUpdateObject
|
||||
{
|
||||
// If you extend XMPPMessageArchiving_Contact_CoreDataObject,
|
||||
// you can override this method to use as a hook to update your own custom properties.
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,52 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreData/CoreData.h>
|
||||
#import "XMPP.h"
|
||||
|
||||
|
||||
@interface XMPPMessageArchiving_Message_CoreDataObject : NSManagedObject
|
||||
|
||||
@property (nonatomic, strong) XMPPMessage * message; // Transient (proper type, not on disk)
|
||||
@property (nonatomic, strong) NSString * messageStr; // Shadow (binary data, written to disk)
|
||||
|
||||
/**
|
||||
* This is the bare jid of the person you're having the conversation with.
|
||||
* For example: robbiehanson@deusty.com
|
||||
*
|
||||
* Regardless of whether the message was incoming or outgoing,
|
||||
* this will represent the "other" participant in the conversation.
|
||||
**/
|
||||
@property (nonatomic, strong) XMPPJID * bareJid; // Transient (proper type, not on disk)
|
||||
@property (nonatomic, strong) NSString * bareJidStr; // Shadow (binary data, written to disk)
|
||||
|
||||
@property (nonatomic, strong) NSString * body;
|
||||
@property (nonatomic, strong) NSString * thread;
|
||||
|
||||
@property (nonatomic, strong) NSNumber * outgoing; // Use isOutgoing
|
||||
@property (nonatomic, assign) BOOL isOutgoing; // Convenience property
|
||||
|
||||
@property (nonatomic, strong) NSNumber * composing; // Use isComposing
|
||||
@property (nonatomic, assign) BOOL isComposing; // Convenience property
|
||||
|
||||
@property (nonatomic, strong) NSDate * timestamp;
|
||||
|
||||
@property (nonatomic, strong) NSString * streamBareJidStr;
|
||||
|
||||
/**
|
||||
* This method is called immediately before the object is inserted into the managedObjectContext.
|
||||
* At this point, all normal properties have been set.
|
||||
*
|
||||
* If you extend XMPPMessageArchiving_Message_CoreDataObject,
|
||||
* you can use this method as a hook to set your custom properties.
|
||||
**/
|
||||
- (void)willInsertObject;
|
||||
|
||||
/**
|
||||
* This method is called immediately after the message has been changed.
|
||||
* At this point, all normal properties have been updated.
|
||||
*
|
||||
* If you extend XMPPMessageArchiving_Message_CoreDataObject,
|
||||
* you can use this method as a hook to set your custom properties.
|
||||
**/
|
||||
- (void)didUpdateObject;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,171 @@
|
||||
#import "XMPPMessageArchiving_Message_CoreDataObject.h"
|
||||
|
||||
|
||||
@interface XMPPMessageArchiving_Message_CoreDataObject ()
|
||||
|
||||
@property(nonatomic,strong) XMPPMessage * primitiveMessage;
|
||||
@property(nonatomic,strong) NSString * primitiveMessageStr;
|
||||
|
||||
@property(nonatomic,strong) XMPPJID * primitiveBareJid;
|
||||
@property(nonatomic,strong) NSString * primitiveBareJidStr;
|
||||
|
||||
@end
|
||||
|
||||
@implementation XMPPMessageArchiving_Message_CoreDataObject
|
||||
|
||||
@dynamic message, primitiveMessage;
|
||||
@dynamic messageStr, primitiveMessageStr;
|
||||
@dynamic bareJid, primitiveBareJid;
|
||||
@dynamic bareJidStr, primitiveBareJidStr;
|
||||
@dynamic body;
|
||||
@dynamic thread;
|
||||
@dynamic outgoing;
|
||||
@dynamic composing;
|
||||
@dynamic timestamp;
|
||||
@dynamic streamBareJidStr;
|
||||
|
||||
#pragma mark Transient 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"];
|
||||
}
|
||||
|
||||
#pragma mark Transient bareJid
|
||||
|
||||
- (XMPPJID *)bareJid
|
||||
{
|
||||
// Create and cache on demand
|
||||
|
||||
[self willAccessValueForKey:@"bareJid"];
|
||||
XMPPJID *tmp = self.primitiveBareJid;
|
||||
[self didAccessValueForKey:@"bareJid"];
|
||||
|
||||
if (tmp == nil)
|
||||
{
|
||||
NSString *bareJidStr = self.bareJidStr;
|
||||
if (bareJidStr)
|
||||
{
|
||||
tmp = [XMPPJID jidWithString:bareJidStr];
|
||||
self.primitiveBareJid = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
- (void)setBareJid:(XMPPJID *)bareJid
|
||||
{
|
||||
if ([self.bareJid isEqualToJID:bareJid options:XMPPJIDCompareBare])
|
||||
{
|
||||
return; // No change
|
||||
}
|
||||
|
||||
[self willChangeValueForKey:@"bareJid"];
|
||||
[self willChangeValueForKey:@"bareJidStr"];
|
||||
|
||||
self.primitiveBareJid = [bareJid bareJID];
|
||||
self.primitiveBareJidStr = [bareJid bare];
|
||||
|
||||
[self didChangeValueForKey:@"bareJid"];
|
||||
[self didChangeValueForKey:@"bareJidStr"];
|
||||
}
|
||||
|
||||
- (void)setBareJidStr:(NSString *)bareJidStr
|
||||
{
|
||||
if ([self.bareJidStr isEqualToString:bareJidStr])
|
||||
{
|
||||
return; // No change
|
||||
}
|
||||
|
||||
[self willChangeValueForKey:@"bareJid"];
|
||||
[self willChangeValueForKey:@"bareJidStr"];
|
||||
|
||||
XMPPJID *bareJid = [[XMPPJID jidWithString:bareJidStr] bareJID];
|
||||
|
||||
self.primitiveBareJid = bareJid;
|
||||
self.primitiveBareJidStr = [bareJid bare];
|
||||
|
||||
[self didChangeValueForKey:@"bareJid"];
|
||||
[self didChangeValueForKey:@"bareJidStr"];
|
||||
}
|
||||
|
||||
#pragma mark Convenience properties
|
||||
|
||||
- (BOOL)isOutgoing
|
||||
{
|
||||
return [self.outgoing boolValue];
|
||||
}
|
||||
|
||||
- (void)setIsOutgoing:(BOOL)flag
|
||||
{
|
||||
self.outgoing = @(flag);
|
||||
}
|
||||
|
||||
- (BOOL)isComposing
|
||||
{
|
||||
return [self.composing boolValue];
|
||||
}
|
||||
|
||||
- (void)setIsComposing:(BOOL)flag
|
||||
{
|
||||
self.composing = @(flag);
|
||||
}
|
||||
|
||||
#pragma mark Hooks
|
||||
|
||||
- (void)willInsertObject
|
||||
{
|
||||
// If you extend XMPPMessageArchiving_Message_CoreDataObject,
|
||||
// you can override this method to use as a hook to set your own custom properties.
|
||||
}
|
||||
|
||||
- (void)didUpdateObject
|
||||
{
|
||||
// If you extend XMPPMessageArchiving_Message_CoreDataObject,
|
||||
// you can override this method to use as a hook to set your own custom properties.
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,117 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "XMPP.h"
|
||||
|
||||
#define _XMPP_MESSAGE_ARCHIVING_H
|
||||
|
||||
@protocol XMPPMessageArchivingStorage;
|
||||
|
||||
/**
|
||||
* This class provides support for storing message history.
|
||||
* The functionality is formalized in XEP-0136.
|
||||
**/
|
||||
@interface XMPPMessageArchiving : XMPPModule
|
||||
{
|
||||
@protected
|
||||
|
||||
__strong id <XMPPMessageArchivingStorage> xmppMessageArchivingStorage;
|
||||
|
||||
@private
|
||||
|
||||
BOOL clientSideMessageArchivingOnly;
|
||||
NSXMLElement *preferences;
|
||||
}
|
||||
|
||||
- (id)initWithMessageArchivingStorage:(id <XMPPMessageArchivingStorage>)storage;
|
||||
- (id)initWithMessageArchivingStorage:(id <XMPPMessageArchivingStorage>)storage dispatchQueue:(dispatch_queue_t)queue;
|
||||
|
||||
@property (readonly, strong) id <XMPPMessageArchivingStorage> xmppMessageArchivingStorage;
|
||||
|
||||
/**
|
||||
* XEP-0136 Message Archiving outlines a complex protocol for:
|
||||
*
|
||||
* - archiving messages on the xmpp server
|
||||
* - allowing the client to sync it's client-side cache with the server side archive
|
||||
* - allowing the client to configure archiving preferences (default, per contact, etc)
|
||||
*
|
||||
* There are times when this complication isn't necessary or possible.
|
||||
* E.g. the server doesn't support the message archiving protocol.
|
||||
*
|
||||
* In this case you can simply set clientSideMessageArchivingOnly to YES,
|
||||
* and this instance won't bother with any of the server protocol stuff.
|
||||
* It will simply arhive outgoing and incoming messages.
|
||||
*
|
||||
* Note: Even when clientSideMessageArchivingOnly is YES,
|
||||
* you can still take advantage of the preference methods to configure various options,
|
||||
* such as how long to store messages, prefs for individual contacts, etc.
|
||||
**/
|
||||
@property (readwrite, assign) BOOL clientSideMessageArchivingOnly;
|
||||
|
||||
/**
|
||||
*
|
||||
**/
|
||||
@property (readwrite, copy) NSXMLElement *preferences;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol XMPPMessageArchivingStorage <NSObject>
|
||||
@required
|
||||
|
||||
//
|
||||
//
|
||||
// -- PUBLIC METHODS --
|
||||
//
|
||||
// There are no public methods required by this protocol.
|
||||
//
|
||||
// Each individual roster storage class will provide a proper way to access/enumerate the
|
||||
// users/resources according to the underlying storage mechanism.
|
||||
//
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
// -- PRIVATE METHODS --
|
||||
//
|
||||
// These methods are designed to be used ONLY by the XMPPMessageArchiving class.
|
||||
//
|
||||
//
|
||||
|
||||
/**
|
||||
* Configures the storage class, passing its parent and parent's dispatch queue.
|
||||
*
|
||||
* This method is called by the init method of the XMPPMessageArchiving class.
|
||||
* This method is designed to inform the storage class of its parent
|
||||
* and of the dispatch queue the parent will be operating on.
|
||||
*
|
||||
* The storage class may choose to operate on the same queue as its parent,
|
||||
* or it may operate on its own internal dispatch queue.
|
||||
*
|
||||
* This method should return YES if it was configured properly.
|
||||
* If a storage class is designed to be used with a single parent at a time, this method may return NO.
|
||||
* The XMPPMessageArchiving class is configured to ignore the passed
|
||||
* storage class in its init method if this method returns NO.
|
||||
**/
|
||||
- (BOOL)configureWithParent:(XMPPMessageArchiving *)aParent queue:(dispatch_queue_t)queue;
|
||||
|
||||
/**
|
||||
*
|
||||
**/
|
||||
- (void)archiveMessage:(XMPPMessage *)message outgoing:(BOOL)isOutgoing xmppStream:(XMPPStream *)stream;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* The storage class may optionally persistently store the client preferences.
|
||||
**/
|
||||
- (void)setPreferences:(NSXMLElement *)prefs forUser:(XMPPJID *)bareUserJid;
|
||||
|
||||
/**
|
||||
* The storage class may optionally persistently store the client preferences.
|
||||
* This method is then used to fetch previously known preferences when the client first connects to the xmpp server.
|
||||
**/
|
||||
- (NSXMLElement *)preferencesForUser:(XMPPJID *)bareUserJid;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,431 @@
|
||||
#import "XMPPMessageArchiving.h"
|
||||
#import "XMPPFramework.h"
|
||||
#import "XMPPLogging.h"
|
||||
#import "NSNumber+XMPP.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
|
||||
// Log flags: trace
|
||||
#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 XMLNS_XMPP_ARCHIVE @"urn:xmpp:archive"
|
||||
|
||||
|
||||
@implementation XMPPMessageArchiving
|
||||
|
||||
- (id)init
|
||||
{
|
||||
// This will cause a crash - it's designed to.
|
||||
// Only the init methods listed in XMPPMessageArchiving.h are supported.
|
||||
|
||||
return [self initWithMessageArchivingStorage:nil dispatchQueue:NULL];
|
||||
}
|
||||
|
||||
- (id)initWithDispatchQueue:(dispatch_queue_t)queue
|
||||
{
|
||||
// This will cause a crash - it's designed to.
|
||||
// Only the init methods listed in XMPPMessageArchiving.h are supported.
|
||||
|
||||
return [self initWithMessageArchivingStorage:nil dispatchQueue:queue];
|
||||
}
|
||||
|
||||
- (id)initWithMessageArchivingStorage:(id <XMPPMessageArchivingStorage>)storage
|
||||
{
|
||||
return [self initWithMessageArchivingStorage:storage dispatchQueue:NULL];
|
||||
}
|
||||
|
||||
- (id)initWithMessageArchivingStorage:(id <XMPPMessageArchivingStorage>)storage dispatchQueue:(dispatch_queue_t)queue
|
||||
{
|
||||
NSParameterAssert(storage != nil);
|
||||
|
||||
if ((self = [super initWithDispatchQueue:queue]))
|
||||
{
|
||||
if ([storage configureWithParent:self queue:moduleQueue])
|
||||
{
|
||||
xmppMessageArchivingStorage = storage;
|
||||
}
|
||||
else
|
||||
{
|
||||
XMPPLogError(@"%@: %@ - Unable to configure storage!", THIS_FILE, THIS_METHOD);
|
||||
}
|
||||
|
||||
NSXMLElement *_default = [NSXMLElement elementWithName:@"default"];
|
||||
[_default addAttributeWithName:@"expire" stringValue:@"604800"];
|
||||
[_default addAttributeWithName:@"save" stringValue:@"body"];
|
||||
|
||||
NSXMLElement *pref = [NSXMLElement elementWithName:@"pref" xmlns:XMLNS_XMPP_ARCHIVE];
|
||||
[pref addChild:_default];
|
||||
|
||||
preferences = pref;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)activate:(XMPPStream *)aXmppStream
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
if ([super activate:aXmppStream])
|
||||
{
|
||||
XMPPLogVerbose(@"%@: Activated", THIS_FILE);
|
||||
|
||||
// Reserved for future potential use
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)deactivate
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
// Reserved for future potential use
|
||||
|
||||
[super deactivate];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Properties
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (id <XMPPMessageArchivingStorage>)xmppMessageArchivingStorage
|
||||
{
|
||||
// Note: The xmppMessageArchivingStorage variable is read-only (set in the init method)
|
||||
|
||||
return xmppMessageArchivingStorage;
|
||||
}
|
||||
|
||||
- (BOOL)clientSideMessageArchivingOnly
|
||||
{
|
||||
__block BOOL result = NO;
|
||||
|
||||
dispatch_block_t block = ^{
|
||||
result = clientSideMessageArchivingOnly;
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_sync(moduleQueue, block);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setClientSideMessageArchivingOnly:(BOOL)flag
|
||||
{
|
||||
dispatch_block_t block = ^{
|
||||
clientSideMessageArchivingOnly = flag;
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_async(moduleQueue, block);
|
||||
}
|
||||
|
||||
- (NSXMLElement *)preferences
|
||||
{
|
||||
__block NSXMLElement *result = nil;
|
||||
|
||||
dispatch_block_t block = ^{
|
||||
|
||||
result = [preferences copy];
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_sync(moduleQueue, block);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setPreferences:(NSXMLElement *)newPreferences
|
||||
{
|
||||
dispatch_block_t block = ^{ @autoreleasepool {
|
||||
|
||||
// Update cached value
|
||||
|
||||
preferences = [newPreferences copy];
|
||||
|
||||
// Update storage
|
||||
|
||||
if ([xmppMessageArchivingStorage respondsToSelector:@selector(setPreferences:forUser:)])
|
||||
{
|
||||
XMPPJID *myBareJid = [[xmppStream myJID] bareJID];
|
||||
|
||||
[xmppMessageArchivingStorage setPreferences:preferences forUser:myBareJid];
|
||||
}
|
||||
|
||||
// Todo:
|
||||
//
|
||||
// - Send new pref to server (if changed)
|
||||
}};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_async(moduleQueue, block);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Utilities
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (BOOL)shouldArchiveMessage:(XMPPMessage *)message outgoing:(BOOL)isOutgoing xmppStream:(XMPPStream *)xmppStream
|
||||
{
|
||||
// XEP-0136 Section 2.9: Preferences precedence rules:
|
||||
//
|
||||
// When determining archiving preferences for a given message, the following rules shall apply:
|
||||
//
|
||||
// 1. 'save' value is taken from the <session> element that matches the conversation, if present,
|
||||
// else from the <item> element that matches the contact (see JID Matching), if present,
|
||||
// else from the default element.
|
||||
//
|
||||
// 2. 'otr' and 'expire' value are taken from the <item> element that matches the contact, if present,
|
||||
// else from the default element.
|
||||
|
||||
NSXMLElement *match = nil;
|
||||
|
||||
NSString *messageThread = [[message elementForName:@"thread"] stringValue];
|
||||
if (messageThread)
|
||||
{
|
||||
// First priority - matching session element
|
||||
|
||||
for (NSXMLElement *session in [preferences elementsForName:@"session"])
|
||||
{
|
||||
NSString *sessionThread = [session attributeStringValueForName:@"thread"];
|
||||
if ([messageThread isEqualToString:sessionThread])
|
||||
{
|
||||
match = session;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (match == nil)
|
||||
{
|
||||
// Second priority - matching item element
|
||||
//
|
||||
//
|
||||
// XEP-0136 Section 10.1: JID Matching
|
||||
//
|
||||
// The following rules apply:
|
||||
//
|
||||
// 1. If the JID is of the form <localpart@domain.tld/resource>, only this particular JID matches.
|
||||
// 2. If the JID is of the form <localpart@domain.tld>, any resource matches.
|
||||
// 3. If the JID is of the form <domain.tld>, any node matches.
|
||||
//
|
||||
// However, having these rules only would make impossible a match like "all collections having JID
|
||||
// exactly equal to bare JID/domain JID". Therefore, when the 'exactmatch' attribute is set to "true" or
|
||||
// "1" on the <list/>, <remove/> or <item/> element, a JID value such as "example.com" matches
|
||||
// that exact JID only rather than <*@example.com>, <*@example.com/*>, or <example.com/*>, and
|
||||
// a JID value such as "localpart@example.com" matches that exact JID only rather than
|
||||
// <localpart@example.com/*>.
|
||||
|
||||
XMPPJID *messageJid;
|
||||
if (isOutgoing)
|
||||
messageJid = [message to];
|
||||
else
|
||||
messageJid = [message from];
|
||||
|
||||
NSXMLElement *match_full = nil;
|
||||
NSXMLElement *match_bare = nil;
|
||||
NSXMLElement *match_domain = nil;
|
||||
|
||||
for (NSXMLElement *item in [preferences elementsForName:@"item"])
|
||||
{
|
||||
XMPPJID *itemJid = [XMPPJID jidWithString:[item attributeStringValueForName:@"jid"]];
|
||||
|
||||
if (itemJid.resource)
|
||||
{
|
||||
BOOL match = [messageJid isEqualToJID:itemJid options:XMPPJIDCompareFull];
|
||||
|
||||
if (match && (match_full == nil))
|
||||
{
|
||||
match_full = item;
|
||||
}
|
||||
}
|
||||
else if (itemJid.user)
|
||||
{
|
||||
BOOL exactmatch = [item attributeBoolValueForName:@"exactmatch" withDefaultValue:NO];
|
||||
BOOL match;
|
||||
|
||||
if (exactmatch)
|
||||
match = [messageJid isEqualToJID:itemJid options:XMPPJIDCompareFull];
|
||||
else
|
||||
match = [messageJid isEqualToJID:itemJid options:XMPPJIDCompareBare];
|
||||
|
||||
if (match && (match_bare == nil))
|
||||
{
|
||||
match_bare = item;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
BOOL exactmatch = [item attributeBoolValueForName:@"exactmatch" withDefaultValue:NO];
|
||||
BOOL match;
|
||||
|
||||
if (exactmatch)
|
||||
match = [messageJid isEqualToJID:itemJid options:XMPPJIDCompareFull];
|
||||
else
|
||||
match = [messageJid isEqualToJID:itemJid options:XMPPJIDCompareDomain];
|
||||
|
||||
if (match && (match_domain == nil))
|
||||
{
|
||||
match_domain = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (match_full)
|
||||
match = match_full;
|
||||
else if (match_bare)
|
||||
match = match_bare;
|
||||
else if (match_domain)
|
||||
match = match_domain;
|
||||
}
|
||||
|
||||
if (match == nil)
|
||||
{
|
||||
// Third priority - default element
|
||||
|
||||
match = [preferences elementForName:@"default"];
|
||||
}
|
||||
|
||||
if (match == nil)
|
||||
{
|
||||
XMPPLogWarn(@"%@: No message archive rule found for message! Discarding...", THIS_FILE);
|
||||
return NO;
|
||||
}
|
||||
|
||||
// The 'save' attribute specifies the user's default setting for Save Mode.
|
||||
// The allowable values are:
|
||||
//
|
||||
// - body : the saving entity SHOULD save only <body/> elements.
|
||||
// - false : the saving entity MUST save nothing.
|
||||
// - message : the saving entity SHOULD save the full XML content of each <message/> element.
|
||||
// - stream : the saving entity SHOULD save every byte that passes over the stream in either direction.
|
||||
//
|
||||
// Note: We currently only support body, and treat values of 'message' or 'stream' the same as 'body'.
|
||||
|
||||
NSString *save = [[match attributeStringValueForName:@"save"] lowercaseString];
|
||||
|
||||
if ([save isEqualToString:@"false"])
|
||||
return NO;
|
||||
else
|
||||
return YES;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark XMPPStream Delegate
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
if (clientSideMessageArchivingOnly) return;
|
||||
|
||||
// Fetch most recent preferences
|
||||
|
||||
if ([xmppMessageArchivingStorage respondsToSelector:@selector(preferencesForUser:)])
|
||||
{
|
||||
XMPPJID *myBareJid = [[xmppStream myJID] bareJID];
|
||||
|
||||
preferences = [xmppMessageArchivingStorage preferencesForUser:myBareJid];
|
||||
}
|
||||
|
||||
// Request archiving preferences from server
|
||||
//
|
||||
// <iq type='get'>
|
||||
// <pref xmlns='urn:xmpp:archive'/>
|
||||
// </iq>
|
||||
|
||||
NSXMLElement *pref = [NSXMLElement elementWithName:@"pref" xmlns:XMLNS_XMPP_ARCHIVE];
|
||||
XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:nil elementID:nil child:pref];
|
||||
|
||||
[sender sendElement:iq];
|
||||
}
|
||||
|
||||
- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq
|
||||
{
|
||||
NSString *type = [iq type];
|
||||
|
||||
if ([type isEqualToString:@"result"])
|
||||
{
|
||||
NSXMLElement *pref = [iq elementForName:@"pref" xmlns:XMLNS_XMPP_ARCHIVE];
|
||||
if (pref)
|
||||
{
|
||||
[self setPreferences:pref];
|
||||
}
|
||||
}
|
||||
else if ([type isEqualToString:@"set"])
|
||||
{
|
||||
// We receive the following type of IQ when we send a chat message within facebook from another device:
|
||||
//
|
||||
// <iq from="chat.facebook.com" to="-121201407@chat.facebook.com/e49b026a_4BA226A73192D type="set">
|
||||
// <own-message xmlns="http://www.facebook.com/xmpp/messages" to="-123@chat.facebook.com" self="false">
|
||||
// <body>Hi Jilr</body>
|
||||
// </own-message>
|
||||
// </iq>
|
||||
|
||||
NSXMLElement *ownMessage = [iq elementForName:@"own-message" xmlns:@"http://www.facebook.com/xmpp/messages"];
|
||||
if (ownMessage)
|
||||
{
|
||||
BOOL isSelf = [ownMessage attributeBoolValueForName:@"self" withDefaultValue:NO];
|
||||
if (!isSelf)
|
||||
{
|
||||
NSString *bodyStr = [[ownMessage elementForName:@"body"] stringValue];
|
||||
if ([bodyStr length] > 0)
|
||||
{
|
||||
NSXMLElement *body = [NSXMLElement elementWithName:@"body" stringValue:bodyStr];
|
||||
|
||||
XMPPJID *to = [XMPPJID jidWithString:[ownMessage attributeStringValueForName:@"to"]];
|
||||
XMPPMessage *message = [XMPPMessage messageWithType:@"chat" to:to];
|
||||
[message addChild:body];
|
||||
|
||||
if ([self shouldArchiveMessage:message outgoing:YES xmppStream:sender])
|
||||
{
|
||||
[xmppMessageArchivingStorage archiveMessage:message outgoing:YES xmppStream:sender];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
if ([self shouldArchiveMessage:message outgoing:YES xmppStream:sender])
|
||||
{
|
||||
[xmppMessageArchivingStorage archiveMessage:message outgoing:YES xmppStream:sender];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
if ([self shouldArchiveMessage:message outgoing:NO xmppStream:sender])
|
||||
{
|
||||
[xmppMessageArchivingStorage archiveMessage:message outgoing:NO xmppStream:sender];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in new issue
Block a user