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>XMPPMessageArchiving.xcdatamodel</string>
</dict>
</plist>
@@ -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