diff --git a/Core/XMPPFramework.h b/Core/XMPPFramework.h index 87f6ef49f8..ec4e80fba9 100644 --- a/Core/XMPPFramework.h +++ b/Core/XMPPFramework.h @@ -34,6 +34,7 @@ #import "XMPPTimer.h" #import "XMPPCoreDataStorage.h" #import "XMPPCoreDataStorageProtected.h" +#import "NSManagedObject+XMPPCoreDataStorage.h" #import "NSXMLElement+XEP_0203.h" #import "XMPPFileTransfer.h" #import "XMPPIncomingFileTransfer.h" @@ -163,7 +164,14 @@ #import "XMPPRoomLightCoreDataStorage.h" #import "XMPPRoomLightCoreDataStorageProtected.h" #import "XMPPRoomLightMessageCoreDataStorageObject.h" - +#import "XMPPMessageCoreDataStorage.h" +#import "XMPPMessageCoreDataStorageObject.h" +#import "XMPPMessageCoreDataStorageObject+Protected.h" +#import "XMPPMessageCoreDataStorageObject+ContextHelpers.h" +#import "XMPPMessageContextCoreDataStorageObject.h" +#import "XMPPMessageContextCoreDataStorageObject+Protected.h" +#import "XMPPMessageContextItemCoreDataStorageObject.h" +#import "XMPPMessageContextItemCoreDataStorageObject+Protected.h" FOUNDATION_EXPORT double XMPPFrameworkVersionNumber; FOUNDATION_EXPORT const unsigned char XMPPFrameworkVersionString[]; diff --git a/Extensions/CoreDataStorage/NSManagedObject+XMPPCoreDataStorage.h b/Extensions/CoreDataStorage/NSManagedObject+XMPPCoreDataStorage.h new file mode 100644 index 0000000000..f76cf79c3d --- /dev/null +++ b/Extensions/CoreDataStorage/NSManagedObject+XMPPCoreDataStorage.h @@ -0,0 +1,33 @@ +#import +#import "XMPPJID.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface NSManagedObject (XMPPCoreDataStorage) + +/// @brief Inserts a managed object with an entity whose name matches the class name. +/// @discussion An assertion will be triggered if no matching entity is found in the model. ++ (instancetype)xmpp_insertNewObjectInManagedObjectContext:(NSManagedObjectContext *)managedObjectContext; + +/// @brief Returns a fetch request for an entity whose name matches the class name. +/// @discussion An assertion will be triggered if no matching entity is found in the model. ++ (NSFetchRequest *)xmpp_fetchRequestInManagedObjectContext:(NSManagedObjectContext *)managedObjectContext; + +/// @brief Returns a predicate for filtering managed objects on JID component attributes. +/// @discussion The provided keypaths are relative to the fetched entity and the filtering logic follows @c [XMPPJID @c isEqualToJID:options:] implementation. ++ (NSPredicate *)xmpp_jidPredicateWithDomainKeyPath:(NSString *)domainKeyPath + resourceKeyPath:(NSString *)resourceKeyPath + userKeyPath:(NSString *)userKeyPath + value:(XMPPJID *)value + compareOptions:(XMPPJIDCompareOptions)compareOptions; + +@end + +@interface NSManagedObjectContext (XMPPCoreDataStorage) + +/// Executes the provided fetch request raising an assertion upon failure. +- (NSArray *)xmpp_executeForcedSuccessFetchRequest:(NSFetchRequest *)fetchRequest; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Extensions/CoreDataStorage/NSManagedObject+XMPPCoreDataStorage.m b/Extensions/CoreDataStorage/NSManagedObject+XMPPCoreDataStorage.m new file mode 100644 index 0000000000..697917b9d5 --- /dev/null +++ b/Extensions/CoreDataStorage/NSManagedObject+XMPPCoreDataStorage.m @@ -0,0 +1,65 @@ +#import "NSManagedObject+XMPPCoreDataStorage.h" + +@implementation NSManagedObject (XMPPCoreDataStorage) + ++ (instancetype)xmpp_insertNewObjectInManagedObjectContext:(NSManagedObjectContext *)managedObjectContext +{ + return [[self alloc] initWithEntity:[self xmpp_entityInManagedObjectContext:managedObjectContext] + insertIntoManagedObjectContext:managedObjectContext]; +} + ++ (NSFetchRequest *)xmpp_fetchRequestInManagedObjectContext:(NSManagedObjectContext *)managedObjectContext +{ + NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; + fetchRequest.entity = [self xmpp_entityInManagedObjectContext:managedObjectContext]; + return fetchRequest; +} + ++ (NSPredicate *)xmpp_jidPredicateWithDomainKeyPath:(NSString *)domainKeyPath resourceKeyPath:(NSString *)resourceKeyPath userKeyPath:(NSString *)userKeyPath value:(XMPPJID *)value compareOptions:(XMPPJIDCompareOptions)compareOptions +{ + NSMutableArray *subpredicates = [[NSMutableArray alloc] init]; + + if (compareOptions & XMPPJIDCompareDomain) { + [subpredicates addObject:[NSPredicate predicateWithFormat:@"%K = %@", domainKeyPath, value.domain]]; + } + + if (compareOptions & XMPPJIDCompareResource) { + [subpredicates addObject:[NSPredicate predicateWithFormat:@"%K = %@", resourceKeyPath, value.resource]]; + } + + if (compareOptions & XMPPJIDCompareUser) { + [subpredicates addObject:[NSPredicate predicateWithFormat:@"%K = %@", userKeyPath, value.user]]; + } + + return [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]; +} + ++ (NSEntityDescription *)xmpp_entityInManagedObjectContext:(NSManagedObjectContext *)managedObjectContext +{ + NSUInteger selfEntityIndex = [managedObjectContext.persistentStoreCoordinator.managedObjectModel.entities indexOfObjectPassingTest:^BOOL(NSEntityDescription * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + BOOL matchesSelf = [obj.managedObjectClassName isEqualToString:NSStringFromClass(self)]; + if (matchesSelf) { + *stop = YES; + } + return matchesSelf; + }]; + NSAssert(selfEntityIndex != NSNotFound, @"Entity for %@ not found", self); + + return managedObjectContext.persistentStoreCoordinator.managedObjectModel.entities[selfEntityIndex]; +} + +@end + +@implementation NSManagedObjectContext (XMPPCoreDataStorage) + +- (NSArray *)xmpp_executeForcedSuccessFetchRequest:(NSFetchRequest *)fetchRequest +{ + NSError *error; + NSArray *fetchResult = [self executeFetchRequest:fetchRequest error:&error]; + if (!fetchResult) { + NSAssert(NO, @"Fetch request %@ failed with error %@", fetchRequest, error); + } + return fetchResult; +} + +@end diff --git a/Extensions/CoreDataStorage/XMPPCoreDataStorage.m b/Extensions/CoreDataStorage/XMPPCoreDataStorage.m index 39218e30f0..813331c956 100644 --- a/Extensions/CoreDataStorage/XMPPCoreDataStorage.m +++ b/Extensions/CoreDataStorage/XMPPCoreDataStorage.m @@ -560,7 +560,7 @@ - (NSPersistentStoreCoordinator *)persistentStoreCoordinator { return; } - + XMPPLogVerbose(@"%@: Creating persistentStoreCoordinator", [self class]); persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom]; diff --git a/Extensions/MessageStorage/XMPPMessage.xcdatamodeld/XMPPMessage.xcdatamodel/elements b/Extensions/MessageStorage/XMPPMessage.xcdatamodeld/XMPPMessage.xcdatamodel/elements new file mode 100644 index 0000000000..5d2171caf1 Binary files /dev/null and b/Extensions/MessageStorage/XMPPMessage.xcdatamodeld/XMPPMessage.xcdatamodel/elements differ diff --git a/Extensions/MessageStorage/XMPPMessage.xcdatamodeld/XMPPMessage.xcdatamodel/layout b/Extensions/MessageStorage/XMPPMessage.xcdatamodeld/XMPPMessage.xcdatamodel/layout new file mode 100644 index 0000000000..9a4906ad29 Binary files /dev/null and b/Extensions/MessageStorage/XMPPMessage.xcdatamodeld/XMPPMessage.xcdatamodel/layout differ diff --git a/Extensions/MessageStorage/XMPPMessageContextCoreDataStorageObject+Protected.h b/Extensions/MessageStorage/XMPPMessageContextCoreDataStorageObject+Protected.h new file mode 100644 index 0000000000..7e5be17584 --- /dev/null +++ b/Extensions/MessageStorage/XMPPMessageContextCoreDataStorageObject+Protected.h @@ -0,0 +1,50 @@ +#import "XMPPMessageContextCoreDataStorageObject.h" + +NS_ASSUME_NONNULL_BEGIN + +@class XMPPMessageCoreDataStorageObject, XMPPMessageContextJIDItemCoreDataStorageObject, XMPPMessageContextMarkerItemCoreDataStorageObject, XMPPMessageContextStringItemCoreDataStorageObject, XMPPMessageContextTimestampItemCoreDataStorageObject; + +@interface XMPPMessageContextCoreDataStorageObject (Protected) + +/// The message the context object is assigned to. +@property (nonatomic, strong, nullable) XMPPMessageCoreDataStorageObject *message; + +/// The JID values aggregated by the context object. +@property (nonatomic, copy, nullable) NSSet *jidItems; + +/// The markers aggregated by the context object. +@property (nonatomic, copy, nullable) NSSet *markerItems; + +/// The string values aggregated by the context object. +@property (nonatomic, copy, nullable) NSSet *stringItems; + +/// The timestamp values aggregated by the context object. +@property (nonatomic, copy, nullable) NSSet *timestampItems; + +@end + +@interface XMPPMessageContextCoreDataStorageObject (CoreDataGeneratedRelationshipAccesssors) + +- (void)addJidItemsObject:(XMPPMessageContextJIDItemCoreDataStorageObject *)value; +- (void)removeJidItemsObject:(XMPPMessageContextJIDItemCoreDataStorageObject *)value; +- (void)addJidItems:(NSSet *)value; +- (void)removeJidItems:(NSSet *)value; + +- (void)addMarkerItemsObject:(XMPPMessageContextMarkerItemCoreDataStorageObject *)value; +- (void)removeMarkerItemsObject:(XMPPMessageContextMarkerItemCoreDataStorageObject *)value; +- (void)addMarkerItems:(NSSet *)value; +- (void)removeMarkerItems:(NSSet *)value; + +- (void)addStringItemsObject:(XMPPMessageContextStringItemCoreDataStorageObject *)value; +- (void)removeStringItemsObject:(XMPPMessageContextStringItemCoreDataStorageObject *)value; +- (void)addStringItems:(NSSet *)value; +- (void)removeStringItems:(NSSet *)value; + +- (void)addTimestampItemsObject:(XMPPMessageContextTimestampItemCoreDataStorageObject *)value; +- (void)removeTimestampItemsObject:(XMPPMessageContextTimestampItemCoreDataStorageObject *)value; +- (void)addTimestampItems:(NSSet *)value; +- (void)removeTimestampItems:(NSSet *)value; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Extensions/MessageStorage/XMPPMessageContextCoreDataStorageObject.h b/Extensions/MessageStorage/XMPPMessageContextCoreDataStorageObject.h new file mode 100644 index 0000000000..1ead01a7fa --- /dev/null +++ b/Extensions/MessageStorage/XMPPMessageContextCoreDataStorageObject.h @@ -0,0 +1,15 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + An auxiliary context storage object aggregating module-provided values assigned to a stored message. + + @see XMPPMessageCoreDataStorageObject + @see XMPPMessageContextItemCoreDataStorageObject + */ +@interface XMPPMessageContextCoreDataStorageObject : NSManagedObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/Extensions/MessageStorage/XMPPMessageContextCoreDataStorageObject.m b/Extensions/MessageStorage/XMPPMessageContextCoreDataStorageObject.m new file mode 100644 index 0000000000..8c14ef9faa --- /dev/null +++ b/Extensions/MessageStorage/XMPPMessageContextCoreDataStorageObject.m @@ -0,0 +1,18 @@ +#import "XMPPMessageContextCoreDataStorageObject.h" +#import "XMPPMessageContextCoreDataStorageObject+Protected.h" + +@interface XMPPMessageContextCoreDataStorageObject () + +@property (nonatomic, strong, nullable) XMPPMessageCoreDataStorageObject *message; +@property (nonatomic, copy, nullable) NSSet *jidItems; +@property (nonatomic, copy, nullable) NSSet *markerItems; +@property (nonatomic, copy, nullable) NSSet *stringItems; +@property (nonatomic, copy, nullable) NSSet *timestampItems; + +@end + +@implementation XMPPMessageContextCoreDataStorageObject + +@dynamic message, jidItems, markerItems, stringItems, timestampItems; + +@end diff --git a/Extensions/MessageStorage/XMPPMessageContextItemCoreDataStorageObject+Protected.h b/Extensions/MessageStorage/XMPPMessageContextItemCoreDataStorageObject+Protected.h new file mode 100644 index 0000000000..0d887b24fe --- /dev/null +++ b/Extensions/MessageStorage/XMPPMessageContextItemCoreDataStorageObject+Protected.h @@ -0,0 +1,91 @@ +#import "XMPPMessageContextItemCoreDataStorageObject.h" +#import "XMPPJID.h" + +NS_ASSUME_NONNULL_BEGIN + +@class XMPPMessageContextCoreDataStorageObject; + +typedef NS_ENUM(int16_t, XMPPMessageDirection); + +/// A tag assigned to a JID auxiliary value. +typedef NSString * XMPPMessageContextJIDItemTag NS_EXTENSIBLE_STRING_ENUM; + +/// An tag assigned to an auxiliary marker. +typedef NSString * XMPPMessageContextMarkerItemTag NS_EXTENSIBLE_STRING_ENUM; + +/// A tag assigned to a string auxiliary value. +typedef NSString * XMPPMessageContextStringItemTag NS_EXTENSIBLE_STRING_ENUM; + +/// A tag assigned to a timestamp auxiliary value. +typedef NSString * XMPPMessageContextTimestampItemTag NS_EXTENSIBLE_STRING_ENUM; + +@interface XMPPMessageContextItemCoreDataStorageObject (Protected) + +/// The context element aggregating the value. +@property (nonatomic, strong, nullable) XMPPMessageContextCoreDataStorageObject *contextElement; + +@end + +/// A storage object representing a module-provided JID value assigned to a stored message. +@interface XMPPMessageContextJIDItemCoreDataStorageObject : XMPPMessageContextItemCoreDataStorageObject + +/// The tag assigned to the value. +@property (nonatomic, copy, nullable) XMPPMessageContextJIDItemTag tag; + +/// The stored JID value. +@property (nonatomic, strong, nullable) XMPPJID *value; + +/// Returns a predicate to fetch values with the specified tag. ++ (NSPredicate *)tagPredicateWithValue:(XMPPMessageContextJIDItemTag)value; + +/// Returns a predicate to fetch items with the specified value. ++ (NSPredicate *)jidPredicateWithValue:(XMPPJID *)value compareOptions:(XMPPJIDCompareOptions)compareOptions; + +@end + +/// A storage object representing a module-provided marker assigned to a stored message. +@interface XMPPMessageContextMarkerItemCoreDataStorageObject : XMPPMessageContextItemCoreDataStorageObject + +/// The tag assigned to the marker. +@property (nonatomic, copy, nullable) XMPPMessageContextMarkerItemTag tag; + +/// Returns a predicate to fetch markers with the specified tag. ++ (NSPredicate *)tagPredicateWithValue:(XMPPMessageContextMarkerItemTag)value; + +@end + +/// A storage object representing a module-provided string value assigned to a stored message. +@interface XMPPMessageContextStringItemCoreDataStorageObject : XMPPMessageContextItemCoreDataStorageObject + +/// The tag assigned to the value. +@property (nonatomic, copy, nullable) XMPPMessageContextStringItemTag tag; + +/// The stored string value. +@property (nonatomic, copy, nullable) NSString *value; + +/// Returns a predicate to fetch values with the specified tag. ++ (NSPredicate *)tagPredicateWithValue:(XMPPMessageContextStringItemTag)tag; + +/// Returns a predicate to fetch items with the specified value. ++ (NSPredicate *)stringPredicateWithValue:(NSString *)value; + +@end + +/// A storage object representing a module-provided timestamp value assigned to a stored message. +@interface XMPPMessageContextTimestampItemCoreDataStorageObject : XMPPMessageContextItemCoreDataStorageObject + +/// The tag assigned to the value. +@property (nonatomic, copy, nullable) XMPPMessageContextTimestampItemTag tag; + +/// The stored timestamp value. +@property (nonatomic, strong, nullable) NSDate *value; + +/// Returns a predicate to fetch values with the specified tag. ++ (NSPredicate *)tagPredicateWithValue:(XMPPMessageContextTimestampItemTag)value; + +/// Returns a predicate to fetch items with values in the specified range. ++ (NSPredicate *)timestampRangePredicateWithStartValue:(nullable NSDate *)startValue endValue:(nullable NSDate *)endValue; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Extensions/MessageStorage/XMPPMessageContextItemCoreDataStorageObject.h b/Extensions/MessageStorage/XMPPMessageContextItemCoreDataStorageObject.h new file mode 100644 index 0000000000..a8bdd3567d --- /dev/null +++ b/Extensions/MessageStorage/XMPPMessageContextItemCoreDataStorageObject.h @@ -0,0 +1,151 @@ +#import +#import "XMPPJID.h" + +NS_ASSUME_NONNULL_BEGIN + +@class XMPPMessageCoreDataStorageObject; + +typedef NS_ENUM(int16_t, XMPPMessageDirection); +typedef NS_ENUM(int16_t, XMPPMessageType); + +typedef NS_ENUM(NSInteger, XMPPMessageContentCompareOperator) { + /// Content is equal the search string. + XMPPMessageContentCompareOperatorEquals, + /// Content begins with the search string. + XMPPMessageContentCompareOperatorBeginsWith, + /// Content contains the search string. + XMPPMessageContentCompareOperatorContains, + /// Content ends with the search string. + XMPPMessageContentCompareOperatorEndsWith, + /// Content is equal to the search string and the search string can contain wildcard characters. + XMPPMessageContentCompareOperatorLike, + /// Content matches the the search string interpreted as a regular expression. + XMPPMessageContentCompareOperatorMatches +}; + +typedef NS_OPTIONS(NSInteger, XMPPMessageContentCompareOptions) { + /// Content comparison is case-insensitive. + XMPPMessageContentCompareCaseInsensitive = 1 << 0, + /// Content comparison is diacritic-insensitive. + XMPPMessageContentCompareDiacriticInsensitive = 1 << 1 +}; + +/** + A storage object representing a module-provided value assigned to a stored message. + + @see XMPPMessageCoreDataStorageObject + @see XMPPMessageContextCoreDataStorageObject + */ +@interface XMPPMessageContextItemCoreDataStorageObject : NSManagedObject + +@end + +@interface XMPPMessageContextItemCoreDataStorageObject (XMPPMessageCoreDataStorageFetch) + +/** + Returns a fetch request for timestamp context values with associated messages. + + A common application use case involves fetching temporally ordered messages. In terms of the message storage Core Data model, + this translates to fetching timestamp context values with specific predicates and then looking up the message objects they are attached to. + + The modules that assign custom timestamp context values will also provide appropriate predicates to be used with this method. + It is application's responsibility to avoid fetches with duplicate messages when composing predicates coming from multiple modules. + */ ++ (NSFetchRequest *)requestByTimestampsWithPredicate:(NSPredicate *)predicate + inAscendingOrder:(BOOL)isInAscendingOrder + fromManagedObjectContext:(NSManagedObjectContext *)managedObjectContext; + +/** + Returns a predicate to be provided to @c requestByTimestampsWithPredicate:inAscendingOrder:fromManagedObjectContext: + that limits the fetch results to only include the single most relevant stream context timestamp per message. + + @see requestByTimestampsWithPredicate:inAscendingOrder:fromManagedObjectContext: + */ ++ (NSPredicate *)streamTimestampKindPredicate; + +/** + Returns a predicate to be provided to @c requestByTimestampsWithPredicate:inAscendingOrder:fromManagedObjectContext: + that limits the fetch results to only include timestamp values from the given range. + + In order to request an open range, provide a nil value for the respective boundary. + + @see requestByTimestampsWithPredicate:inAscendingOrder:fromManagedObjectContext: + */ ++ (NSPredicate *)timestampRangePredicateWithStartValue:(nullable NSDate *)startValue endValue:(nullable NSDate *)endValue; + +/** + Returns a predicate to be provided to @c requestByTimestampsWithPredicate:inAscendingOrder:fromManagedObjectContext: + that limits the fetch results to only include timestamp values for messages with the given @c fromJID value. + + @see requestByTimestampsWithPredicate:inAscendingOrder:fromManagedObjectContext: + */ ++ (NSPredicate *)messageFromJIDPredicateWithValue:(XMPPJID *)value compareOptions:(XMPPJIDCompareOptions)compareOptions; + +/** + Returns a predicate to be provided to @c requestByTimestampsWithPredicate:inAscendingOrder:fromManagedObjectContext: + that limits the fetch results to only include timestamp values for messages with the given @c toJID value. + + @see requestByTimestampsWithPredicate:inAscendingOrder:fromManagedObjectContext: + */ ++ (NSPredicate *)messageToJIDPredicateWithValue:(XMPPJID *)value compareOptions:(XMPPJIDCompareOptions)compareOptions; + +/** + Returns a predicate to be provided to @c requestByTimestampsWithPredicate:inAscendingOrder:fromManagedObjectContext: + that limits the fetch results to only include timestamp values for messages exchanged with an entity with the given JID value. + + The relevant messages in this case are the outgoing ones with a matching @c toJID value and incoming ones with a matching @c fromJID value. + + @see requestByTimestampsWithPredicate:inAscendingOrder:fromManagedObjectContext: + */ ++ (NSPredicate *)messageRemotePartyJIDPredicateWithValue:(XMPPJID *)value compareOptions:(XMPPJIDCompareOptions)compareOptions; + +/** + Returns a predicate to be provided to @c requestByTimestampsWithPredicate:inAscendingOrder:fromManagedObjectContext: + that limits the fetch results to only include timestamp values for messages with specific body content. + + @see requestByTimestampsWithPredicate:inAscendingOrder:fromManagedObjectContext: + */ ++ (NSPredicate *)messageBodyPredicateWithValue:(NSString *)value + compareOperator:(XMPPMessageContentCompareOperator)compareOperator + options:(XMPPMessageContentCompareOptions)options; + +/** + Returns a predicate to be provided to @c requestByTimestampsWithPredicate:inAscendingOrder:fromManagedObjectContext: + that limits the fetch results to only include timestamp values for messages with specific subject content. + + @see requestByTimestampsWithPredicate:inAscendingOrder:fromManagedObjectContext: + */ ++ (NSPredicate *)messageSubjectPredicateWithValue:(NSString *)value + compareOperator:(XMPPMessageContentCompareOperator)compareOperator + options:(XMPPMessageContentCompareOptions)options; + +/** + Returns a predicate to be provided to @c requestByTimestampsWithPredicate:inAscendingOrder:fromManagedObjectContext: + that limits the fetch results to only include timestamp values for messages from the given thread. + + @see requestByTimestampsWithPredicate:inAscendingOrder:fromManagedObjectContext: + */ ++ (NSPredicate *)messageThreadPredicateWithValue:(NSString *)value; + +/** + Returns a predicate to be provided to @c requestByTimestampsWithPredicate:inAscendingOrder:fromManagedObjectContext: + that limits the fetch results to only include timestamp values for messages with the specified direction. + + @see requestByTimestampsWithPredicate:inAscendingOrder:fromManagedObjectContext: + */ ++ (NSPredicate *)messageDirectionPredicateWithValue:(XMPPMessageDirection)value; + +/** + Returns a predicate to be provided to @c requestByTimestampsWithPredicate:inAscendingOrder:fromManagedObjectContext: + that limits the fetch results to only include timestamp values for messages of the specified type. + + @see requestByTimestampsWithPredicate:inAscendingOrder:fromManagedObjectContext: + */ ++ (NSPredicate *)messageTypePredicateWithValue:(XMPPMessageType)value; + +/// Returns the message the context item is associated with. +- (XMPPMessageCoreDataStorageObject *)message; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Extensions/MessageStorage/XMPPMessageContextItemCoreDataStorageObject.m b/Extensions/MessageStorage/XMPPMessageContextItemCoreDataStorageObject.m new file mode 100644 index 0000000000..8d92e14b37 --- /dev/null +++ b/Extensions/MessageStorage/XMPPMessageContextItemCoreDataStorageObject.m @@ -0,0 +1,194 @@ +#import "XMPPMessageContextItemCoreDataStorageObject.h" +#import "XMPPMessageContextItemCoreDataStorageObject+Protected.h" +#import "XMPPJID.h" +#import "NSManagedObject+XMPPCoreDataStorage.h" + +@interface XMPPMessageContextItemCoreDataStorageObject () + +@property (nonatomic, strong, nullable) XMPPMessageContextCoreDataStorageObject *contextElement; + +@end + +@implementation XMPPMessageContextItemCoreDataStorageObject + +@dynamic contextElement; + ++ (NSPredicate *)tagPredicateWithValue:(NSString *)value +{ + return [NSPredicate predicateWithFormat:@"%K = %@", NSStringFromSelector(@selector(tag)), value]; +} + +@end + +@interface XMPPMessageContextJIDItemCoreDataStorageObject () + +@property (nonatomic, copy, nullable) NSString *valueDomain; +@property (nonatomic, copy, nullable) NSString *valueResource; +@property (nonatomic, copy, nullable) NSString *valueUser; + +@end + +@interface XMPPMessageContextJIDItemCoreDataStorageObject (CoreDataGeneratedPrimitiveAccessors) + +- (XMPPJID *)primitiveValue; +- (void)setPrimitiveValue:(XMPPJID *)value; +- (void)setPrimitiveValueDomain:(NSString *)value; +- (void)setPrimitiveValueResource:(NSString *)value; +- (void)setPrimitiveValueUser:(NSString *)value; + +@end + +@implementation XMPPMessageContextJIDItemCoreDataStorageObject + +@dynamic tag, valueDomain, valueResource, valueUser; + +#pragma mark - value transient property + +- (XMPPJID *)value +{ + [self willAccessValueForKey:NSStringFromSelector(@selector(value))]; + XMPPJID *value = [self primitiveValue]; + [self didAccessValueForKey:NSStringFromSelector(@selector(value))]; + + if (value) { + return value; + } + + XMPPJID *newValue = [XMPPJID jidWithUser:self.valueUser domain:self.valueDomain resource:self.valueResource]; + [self setPrimitiveValue:newValue]; + + return newValue; +} + +- (void)setValue:(XMPPJID *)value +{ + if ([self.value isEqualToJID:value]) { + return; + } + + [self willChangeValueForKey:NSStringFromSelector(@selector(value))]; + [self willChangeValueForKey:NSStringFromSelector(@selector(valueDomain))]; + [self willChangeValueForKey:NSStringFromSelector(@selector(valueResource))]; + [self willChangeValueForKey:NSStringFromSelector(@selector(valueUser))]; + [self setPrimitiveValue:value]; + [self setPrimitiveValueDomain:value.domain]; + [self setPrimitiveValueResource:value.resource]; + [self setPrimitiveValueUser:value.user]; + [self didChangeValueForKey:NSStringFromSelector(@selector(valueDomain))]; + [self didChangeValueForKey:NSStringFromSelector(@selector(valueResource))]; + [self didChangeValueForKey:NSStringFromSelector(@selector(valueUser))]; + [self didChangeValueForKey:NSStringFromSelector(@selector(value))]; +} + +- (void)setValueDomain:(NSString *)valueDomain +{ + if ([self.valueDomain isEqualToString:valueDomain]) { + return; + } + + [self willChangeValueForKey:NSStringFromSelector(@selector(valueDomain))]; + [self willChangeValueForKey:NSStringFromSelector(@selector(value))]; + [self setPrimitiveValueDomain:valueDomain]; + [self setPrimitiveValue:nil]; + [self didChangeValueForKey:NSStringFromSelector(@selector(value))]; + [self didChangeValueForKey:NSStringFromSelector(@selector(valueDomain))]; +} + +- (void)setValueResource:(NSString *)valueResource +{ + if ([self.valueResource isEqualToString:valueResource]) { + return; + } + + [self willChangeValueForKey:NSStringFromSelector(@selector(valueResource))]; + [self willChangeValueForKey:NSStringFromSelector(@selector(value))]; + [self setPrimitiveValueResource:valueResource]; + [self setPrimitiveValue:nil]; + [self didChangeValueForKey:NSStringFromSelector(@selector(value))]; + [self didChangeValueForKey:NSStringFromSelector(@selector(valueResource))]; +} + +- (void)setValueUser:(NSString *)valueUser +{ + if ([self.valueUser isEqualToString:valueUser]) { + return; + } + + [self willChangeValueForKey:NSStringFromSelector(@selector(valueUser))]; + [self willChangeValueForKey:NSStringFromSelector(@selector(value))]; + [self setPrimitiveValueUser:valueUser]; + [self setPrimitiveValue:nil]; + [self didChangeValueForKey:NSStringFromSelector(@selector(value))]; + [self didChangeValueForKey:NSStringFromSelector(@selector(valueUser))]; +} + +#pragma mark - Public + ++ (NSPredicate *)tagPredicateWithValue:(XMPPMessageContextJIDItemTag)value +{ + return [super tagPredicateWithValue:value]; +} + ++ (NSPredicate *)jidPredicateWithValue:(XMPPJID *)value compareOptions:(XMPPJIDCompareOptions)compareOptions +{ + return [self xmpp_jidPredicateWithDomainKeyPath:NSStringFromSelector(@selector(valueDomain)) + resourceKeyPath:NSStringFromSelector(@selector(valueResource)) + userKeyPath:NSStringFromSelector(@selector(valueUser)) + value:value + compareOptions:compareOptions]; +} + +@end + +@implementation XMPPMessageContextMarkerItemCoreDataStorageObject + +@dynamic tag; + ++ (NSPredicate *)tagPredicateWithValue:(XMPPMessageContextMarkerItemTag)value +{ + return [super tagPredicateWithValue:value]; +} + +@end + +@implementation XMPPMessageContextStringItemCoreDataStorageObject + +@dynamic tag, value; + ++ (NSPredicate *)tagPredicateWithValue:(XMPPMessageContextStringItemTag)value +{ + return [super tagPredicateWithValue:value]; +} + ++ (NSPredicate *)stringPredicateWithValue:(NSString *)value +{ + return [NSPredicate predicateWithFormat:@"%K = %@", NSStringFromSelector(@selector(value)), value]; +} + +@end + +@implementation XMPPMessageContextTimestampItemCoreDataStorageObject + +@dynamic tag, value; + ++ (NSPredicate *)tagPredicateWithValue:(XMPPMessageContextTimestampItemTag)value +{ + return [super tagPredicateWithValue:value]; +} + ++ (NSPredicate *)timestampRangePredicateWithStartValue:(NSDate *)startValue endValue:(NSDate *)endValue +{ + NSMutableArray *subpredicates = [[NSMutableArray alloc] init]; + + if (startValue) { + [subpredicates addObject:[NSPredicate predicateWithFormat:@"%K >= %@", NSStringFromSelector(@selector(value)), startValue]]; + } + + if (endValue) { + [subpredicates addObject:[NSPredicate predicateWithFormat:@"%K <= %@", NSStringFromSelector(@selector(value)), endValue]]; + } + + return subpredicates.count == 1 ? subpredicates.firstObject : [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]; +} + +@end diff --git a/Extensions/MessageStorage/XMPPMessageCoreDataStorage.h b/Extensions/MessageStorage/XMPPMessageCoreDataStorage.h new file mode 100644 index 0000000000..4f090507c2 --- /dev/null +++ b/Extensions/MessageStorage/XMPPMessageCoreDataStorage.h @@ -0,0 +1,15 @@ +#import "XMPPCoreDataStorage.h" + +/** + A client message storage implementation that supports per-module extensibility while maintaining a fixed underlying Core Data model. + + The design is based on assigning auxiliary context objects to each stored XMPP message. Those context objects aggregate arbitrary sets of tagged primitive values. + By defining their own context aggregations and value tags, modules can extend storage capabilities and expose them via a simple API using categories on the core classes. + + The application-facing API consists of the main interface and any categories provided by module authors. The protected interface provides module helper methods. + + @see XMPPMessageCoreDataStorageObject + */ +@interface XMPPMessageCoreDataStorage : XMPPCoreDataStorage + +@end diff --git a/Extensions/MessageStorage/XMPPMessageCoreDataStorage.m b/Extensions/MessageStorage/XMPPMessageCoreDataStorage.m new file mode 100644 index 0000000000..9d75cd791c --- /dev/null +++ b/Extensions/MessageStorage/XMPPMessageCoreDataStorage.m @@ -0,0 +1,5 @@ +#import "XMPPMessageCoreDataStorage.h" + +@implementation XMPPMessageCoreDataStorage + +@end diff --git a/Extensions/MessageStorage/XMPPMessageCoreDataStorageObject+ContextHelpers.h b/Extensions/MessageStorage/XMPPMessageCoreDataStorageObject+ContextHelpers.h new file mode 100644 index 0000000000..ed1ec5b69e --- /dev/null +++ b/Extensions/MessageStorage/XMPPMessageCoreDataStorageObject+ContextHelpers.h @@ -0,0 +1,78 @@ +#import "XMPPMessageCoreDataStorageObject.h" +#import "XMPPMessageContextCoreDataStorageObject+Protected.h" +#import "XMPPMessageContextItemCoreDataStorageObject+Protected.h" + +NS_ASSUME_NONNULL_BEGIN + +/// An API to be used by modules to manipulate auxiliary context objects assigned to a stored message. +@interface XMPPMessageCoreDataStorageObject (ContextHelpers) + +/// Inserts a new context element associated with the message. +- (XMPPMessageContextCoreDataStorageObject *)appendContextElement; + +/** + @brief Enumerates the message's context elements until the lookup block returns a non-nil value and returns that value. + @discussion This method expects the lookup block to only return a non-nil value for a single element and will trigger an assertion otherwise. + */ +- (nullable id)lookupInContextWithBlock:(id __nullable (^)(XMPPMessageContextCoreDataStorageObject *contextElement))lookupBlock; + +@end + +/// An API to be used by modules to manipulate auxiliary context object values assigned to a stored message. +@interface XMPPMessageContextCoreDataStorageObject (ContextHelpers) + +/// Inserts a new JID value associated with the context element. +- (XMPPMessageContextJIDItemCoreDataStorageObject *)appendJIDItemWithTag:(XMPPMessageContextJIDItemTag)tag value:(XMPPJID *)value; + +/// Inserts a new marker associated with the context element. +- (XMPPMessageContextMarkerItemCoreDataStorageObject *)appendMarkerItemWithTag:(XMPPMessageContextMarkerItemTag)tag; + +/// Inserts a new string value associated with the context element. +- (XMPPMessageContextStringItemCoreDataStorageObject *)appendStringItemWithTag:(XMPPMessageContextStringItemTag)tag value:(NSString *)value; + +/// Inserts a new timestamp value associated with the context element. +- (XMPPMessageContextTimestampItemCoreDataStorageObject *)appendTimestampItemWithTag:(XMPPMessageContextTimestampItemTag)tag value:(NSDate *)value; + +/// Removes all JID values with the given tag associated with the context element. +- (void)removeJIDItemsWithTag:(XMPPMessageContextJIDItemTag)tag; + +/// Removes all markers with the given tag associated with the context element. +- (void)removeMarkerItemsWithTag:(XMPPMessageContextMarkerItemTag)tag; + +/// Removes all string values with the given tag associated with the context element. +- (void)removeStringItemsWithTag:(XMPPMessageContextStringItemTag)tag; + +/// Removes all timestamp values with the given tag associated with the context element. +- (void)removeTimestampItemsWithTag:(XMPPMessageContextTimestampItemTag)tag; + +/// Returns all JID values with the given tag associated with the context element. +- (NSSet *)jidItemValuesForTag:(XMPPMessageContextJIDItemTag)tag; + +/// @brief Returns the unique JID value with the given tag associated with the context element. +/// @discussion Will trigger an assertion if there is more than one matching value. +- (nullable XMPPJID *)jidItemValueForTag:(XMPPMessageContextJIDItemTag)tag; + +/// Returns the number of markers with the given tag associated with the context element. +- (NSInteger)markerItemCountForTag:(XMPPMessageContextMarkerItemTag)tag; + +/// @brief Tests whether there is a marker with the given tag associated with the context element. +/// @discussion Will trigger an assertion if there is more than one matching marker. +- (BOOL)hasMarkerItemForTag:(XMPPMessageContextMarkerItemTag)tag; + +/// Returns all string values with the given tag associated with the context element. +- (NSSet *)stringItemValuesForTag:(XMPPMessageContextStringItemTag)tag; + +/// @brief Returns the unique string value with the given tag associated with the context element. +/// @discussion Will trigger an assertion if there is more than one matching value. +- (nullable NSString *)stringItemValueForTag:(XMPPMessageContextStringItemTag)tag; + +/// Returns all timestamp values with the given tag associated with the context element. +- (NSSet *)timestampItemValuesForTag:(XMPPMessageContextTimestampItemTag)tag; + +/// @brief Returns the unique timestamp value with the given tag associated with the context element. +/// @discussion Will trigger an assertion if there is more than one matching value. +- (nullable NSDate *)timestampItemValueForTag:(XMPPMessageContextTimestampItemTag)tag; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Extensions/MessageStorage/XMPPMessageCoreDataStorageObject+ContextHelpers.m b/Extensions/MessageStorage/XMPPMessageCoreDataStorageObject+ContextHelpers.m new file mode 100644 index 0000000000..c2c325ec54 --- /dev/null +++ b/Extensions/MessageStorage/XMPPMessageCoreDataStorageObject+ContextHelpers.m @@ -0,0 +1,221 @@ +#import "XMPPMessageCoreDataStorageObject+ContextHelpers.h" +#import "XMPPMessageCoreDataStorageObject+Protected.h" +#import "XMPPMessageContextCoreDataStorageObject+Protected.h" +#import "NSManagedObject+XMPPCoreDataStorage.h" + +@implementation XMPPMessageCoreDataStorageObject (ContextHelpers) + +- (XMPPMessageContextCoreDataStorageObject *)appendContextElement +{ + NSAssert(self.managedObjectContext, @"Attempted to append a context element to a message not associated with any managed object context"); + + XMPPMessageContextCoreDataStorageObject *insertedElement = [XMPPMessageContextCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.managedObjectContext]; + insertedElement.message = self; + return insertedElement; +} + +- (id)lookupInContextWithBlock:(id (^)(XMPPMessageContextCoreDataStorageObject * _Nonnull))lookupBlock +{ + id lookupResult; + for (XMPPMessageContextCoreDataStorageObject *contextElement in self.contextElements) { + id elementResult = lookupBlock(contextElement); + if (!elementResult) { + continue; + } + NSAssert(!lookupResult, @"A unique lookup result is expected"); + lookupResult = elementResult; +#ifdef NS_BLOCK_ASSERTIONS + break; +#endif + } + return lookupResult; +} + +@end + +@implementation XMPPMessageContextCoreDataStorageObject (ContextHelpers) + +- (XMPPMessageContextJIDItemCoreDataStorageObject *)appendJIDItemWithTag:(XMPPMessageContextJIDItemTag)tag value:(XMPPJID *)value +{ + NSAssert(self.managedObjectContext, @"Attempted to append an item to a context element not associated with any managed object context"); + + XMPPMessageContextJIDItemCoreDataStorageObject *insertedItem = [XMPPMessageContextJIDItemCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.managedObjectContext]; + insertedItem.tag = tag; + insertedItem.value = value; + insertedItem.contextElement = self; + return insertedItem; +} + +- (XMPPMessageContextMarkerItemCoreDataStorageObject *)appendMarkerItemWithTag:(XMPPMessageContextMarkerItemTag)tag +{ + NSAssert(self.managedObjectContext, @"Attempted to append an item to a context element not associated with any managed object context"); + + XMPPMessageContextMarkerItemCoreDataStorageObject *insertedItem = [XMPPMessageContextMarkerItemCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.managedObjectContext]; + insertedItem.tag = tag; + insertedItem.contextElement = self; + return insertedItem; +} + +- (XMPPMessageContextStringItemCoreDataStorageObject *)appendStringItemWithTag:(XMPPMessageContextStringItemTag)tag value:(NSString *)value +{ + NSAssert(self.managedObjectContext, @"Attempted to append an item to a context element not associated with any managed object context"); + + XMPPMessageContextStringItemCoreDataStorageObject *insertedItem = [XMPPMessageContextStringItemCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.managedObjectContext]; + insertedItem.tag = tag; + insertedItem.value = value; + insertedItem.contextElement = self; + return insertedItem; +} + +- (XMPPMessageContextTimestampItemCoreDataStorageObject *)appendTimestampItemWithTag:(XMPPMessageContextTimestampItemTag)tag value:(NSDate *)value +{ + NSAssert(self.managedObjectContext, @"Attempted to append an item to a context element not associated with any managed object context"); + + XMPPMessageContextTimestampItemCoreDataStorageObject *insertedItem = [XMPPMessageContextTimestampItemCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.managedObjectContext]; + insertedItem.tag = tag; + insertedItem.value = value; + insertedItem.contextElement = self; + return insertedItem; +} + +- (void)removeJIDItemsWithTag:(XMPPMessageContextJIDItemTag)tag +{ + NSAssert(self.managedObjectContext, @"Attempted to remove an item from a context element not associated with any managed object context"); + + for (XMPPMessageContextJIDItemCoreDataStorageObject *jidItem in [self jidItemsForTag:tag expectingSingleElement:NO]) { + [self removeJidItemsObject:jidItem]; + [self.managedObjectContext deleteObject:jidItem]; + } +} + +- (void)removeMarkerItemsWithTag:(XMPPMessageContextMarkerItemTag)tag +{ + NSAssert(self.managedObjectContext, @"Attempted to remove an item from a context element not associated with any managed object context"); + + for (XMPPMessageContextMarkerItemCoreDataStorageObject *markerItem in [self markerItemsForTag:tag expectingSingleElement:NO]) { + [self removeMarkerItemsObject:markerItem]; + [self.managedObjectContext deleteObject:markerItem]; + } +} + +- (void)removeStringItemsWithTag:(XMPPMessageContextStringItemTag)tag +{ + NSAssert(self.managedObjectContext, @"Attempted to remove an item from a context element not associated with any managed object context"); + + for (XMPPMessageContextStringItemCoreDataStorageObject *stringItem in [self stringItemsForTag:tag expectingSingleElement:NO]) { + [self removeStringItemsObject:stringItem]; + [self.managedObjectContext deleteObject:stringItem]; + } +} + +- (void)removeTimestampItemsWithTag:(XMPPMessageContextTimestampItemTag)tag +{ + NSAssert(self.managedObjectContext, @"Attempted to remove an item from a context element not associated with any managed object context"); + + for (XMPPMessageContextTimestampItemCoreDataStorageObject *timestampItem in [self timestampItemsForTag:tag expectingSingleElement:NO]) { + [self removeTimestampItemsObject:timestampItem]; + [self.managedObjectContext deleteObject:timestampItem]; + } +} + +- (NSSet *)jidItemValuesForTag:(XMPPMessageContextJIDItemTag)tag +{ + return [[self jidItemsForTag:tag expectingSingleElement:NO] valueForKey:NSStringFromSelector(@selector(value))]; +} + +- (XMPPJID *)jidItemValueForTag:(XMPPMessageContextJIDItemTag)tag +{ + return [[self jidItemsForTag:tag expectingSingleElement:YES] anyObject].value; +} + +- (NSInteger)markerItemCountForTag:(XMPPMessageContextMarkerItemTag)tag +{ + return [self markerItemsForTag:tag expectingSingleElement:NO].count; +} + +- (BOOL)hasMarkerItemForTag:(XMPPMessageContextMarkerItemTag)tag +{ + return [[self markerItemsForTag:tag expectingSingleElement:YES] anyObject] != nil; +} + +- (NSSet *)stringItemValuesForTag:(XMPPMessageContextStringItemTag)tag +{ + return [[self stringItemsForTag:tag expectingSingleElement:NO] valueForKey:NSStringFromSelector(@selector(value))]; +} + +- (NSString *)stringItemValueForTag:(XMPPMessageContextStringItemTag)tag +{ + return [[self stringItemsForTag:tag expectingSingleElement:YES] anyObject].value; +} + +- (NSSet *)timestampItemValuesForTag:(XMPPMessageContextTimestampItemTag)tag +{ + return [[self timestampItemsForTag:tag expectingSingleElement:NO] valueForKey:NSStringFromSelector(@selector(value))]; +} + +- (NSDate *)timestampItemValueForTag:(XMPPMessageContextTimestampItemTag)tag +{ + return [[self timestampItemsForTag:tag expectingSingleElement:YES] anyObject].value; +} + +- (NSSet *)jidItemsForTag:(XMPPMessageContextJIDItemTag)tag expectingSingleElement:(BOOL)isSingleElementExpected +{ + NSSet *filteredSet = [self.jidItems objectsPassingTest:^BOOL(XMPPMessageContextJIDItemCoreDataStorageObject * _Nonnull obj, BOOL * _Nonnull stop) { + BOOL matchesTag = [obj.tag isEqualToString:tag]; +#ifdef NS_BLOCK_ASSERTIONS + if (matchesTag && isSingleElementExpected) { + *stop = YES; + } +#endif + return matchesTag; + }]; + NSAssert(!(isSingleElementExpected && filteredSet.count > 1) , @"Only one item expected"); + return filteredSet; +} + +- (NSSet *)markerItemsForTag:(XMPPMessageContextMarkerItemTag)tag expectingSingleElement:(BOOL)isSingleElementExpected +{ + NSSet *filteredSet = [self.markerItems objectsPassingTest:^BOOL(XMPPMessageContextMarkerItemCoreDataStorageObject * _Nonnull obj, BOOL * _Nonnull stop) { + BOOL matchesTag = [obj.tag isEqualToString:tag]; +#ifdef NS_BLOCK_ASSERTIONS + if (matchesTag && isSingleElementExpected) { + *stop = YES; + } +#endif + return matchesTag; + }]; + NSAssert(!(isSingleElementExpected && filteredSet.count > 1) , @"Only one item expected"); + return filteredSet; +} + +- (NSSet *)stringItemsForTag:(XMPPMessageContextStringItemTag)tag expectingSingleElement:(BOOL)isSingleElementExpected +{ + NSSet *filteredSet = [self.stringItems objectsPassingTest:^BOOL(XMPPMessageContextStringItemCoreDataStorageObject * _Nonnull obj, BOOL * _Nonnull stop) { + BOOL matchesTag = [obj.tag isEqualToString:tag]; +#ifdef NS_BLOCK_ASSERTIONS + if (matchesTag && isSingleElementExpected) { + *stop = YES; + } +#endif + return matchesTag; + }]; + NSAssert(!(isSingleElementExpected && filteredSet.count > 1) , @"Only one item expected"); + return filteredSet; +} + +- (NSSet *)timestampItemsForTag:(XMPPMessageContextTimestampItemTag)tag expectingSingleElement:(BOOL)isSingleElementExpected +{ + NSSet *filteredSet = [self.timestampItems objectsPassingTest:^BOOL(XMPPMessageContextTimestampItemCoreDataStorageObject * _Nonnull obj, BOOL * _Nonnull stop) { + BOOL matchesTag = [obj.tag isEqualToString:tag]; +#ifdef NS_BLOCK_ASSERTIONS + if (matchesTag && isSingleElementExpected) { + *stop = YES; + } +#endif + return matchesTag; + }]; + NSAssert(!(isSingleElementExpected && filteredSet.count > 1) , @"Only one item expected"); + return filteredSet; +} + +@end diff --git a/Extensions/MessageStorage/XMPPMessageCoreDataStorageObject+Protected.h b/Extensions/MessageStorage/XMPPMessageCoreDataStorageObject+Protected.h new file mode 100644 index 0000000000..3e404e1eb5 --- /dev/null +++ b/Extensions/MessageStorage/XMPPMessageCoreDataStorageObject+Protected.h @@ -0,0 +1,78 @@ +#import "XMPPMessageCoreDataStorageObject.h" + +NS_ASSUME_NONNULL_BEGIN + +@class XMPPMessageContextCoreDataStorageObject; + +/// An API to be used by modules to manipulate core message objects. +@interface XMPPMessageCoreDataStorageObject (Protected) + +/// The persistent attribute storing the domain component of @c fromJID property. +@property (nonatomic, copy, nullable) NSString *fromDomain; + +/// The persistent attribute storing the resource component of @c fromJID property. +@property (nonatomic, copy, nullable) NSString *fromResource; + +/// The persistent attribute storing the user component of @c fromJID property. +@property (nonatomic, copy, nullable) NSString *fromUser; + +/// The persistent attribute storing the domain component of @c toJID property. +@property (nonatomic, copy, nullable) NSString *toDomain; + +/// The persistent attribute storing the resource component of @c toJID property. +@property (nonatomic, copy, nullable) NSString *toResource; + +/// The persistent attribute storing the user component of @c toJID property. +@property (nonatomic, copy, nullable) NSString *toUser; + +/// The auxiliary context objects assigned to the message. +@property (nonatomic, copy, nullable) NSSet *contextElements; + +/// @brief Returns the message object from the given context that has a stream element event with the given ID recorded. +/// @discussion As the stream element event IDs are expected to be unique, this method will trigger an assertion if more than one matching object is found. ++ (nullable XMPPMessageCoreDataStorageObject *)findWithStreamEventID:(NSString *)streamEventID + inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext; + +/// @brief Records stream element event properties for the incoming message. +/// @discussion This method will trigger an assertion unless invoked on an incoming message. It will also trigger an assertion when invoked more than once. +- (void)registerIncomingMessageStreamEventID:(NSString *)streamEventID + streamJID:(XMPPJID *)streamJID + streamEventTimestamp:(NSDate *)streamEventTimestamp; + +/// @brief Records the core RFC 3921/6121 properties of an incoming message from the given XML representation. +/// @discussion This method will trigger an assertion unless invoked on an incoming message. Subsequent invocations will overwrite previous values. +- (void)registerIncomingMessageCore:(XMPPMessage *)message; + +/** + Records stream element event properties for the sent message that has a pending outgoing event registration. + + This method will trigger an assertion unless invoked on an outgoing message. + It will also trigger an assertion if not matched with a prior @c registerOutgoingMessageStreamEventID: invocation. + */ +- (void)registerOutgoingMessageStreamJID:(XMPPJID *)streamJID streamEventTimestamp:(NSDate *)streamEventTimestamp; + +/** + Retires the current stream element event timestamp or marks the initial timestamp for retirement if no timestamp is currently registered. + + A single message object can be associated with multiple timestamps, e.g. there can be several transmission attempts for an outgoing message + or a message can have both stream timestamp and delayed delivery timestamp assigned. + + At the same time, a common application use case involves fetching temporally ordered messages. In terms of the message storage Core Data model, + this translates to fetching timestamp context values with specific tags and then looking up the message objects they are attached to. + + For this approach to work, there needs to be at most one timestamp per message that meets the fetch criteria; retiring stream timestamps allows to exclude duplicates. + */ +- (void)retireStreamTimestamp; + +@end + +@interface XMPPMessageCoreDataStorageObject (CoreDataGeneratedRelationshipAccesssors) + +- (void)addContextElementsObject:(XMPPMessageContextCoreDataStorageObject *)value; +- (void)removeContextElementsObject:(XMPPMessageContextCoreDataStorageObject *)value; +- (void)addContextElements:(NSSet *)value; +- (void)removeContextElements:(NSSet *)value; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Extensions/MessageStorage/XMPPMessageCoreDataStorageObject.h b/Extensions/MessageStorage/XMPPMessageCoreDataStorageObject.h new file mode 100644 index 0000000000..87f5c8a606 --- /dev/null +++ b/Extensions/MessageStorage/XMPPMessageCoreDataStorageObject.h @@ -0,0 +1,86 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@class XMPPJID, XMPPMessage; + +typedef NS_ENUM(int16_t, XMPPMessageDirection) { + /// A value indicating that the message's origin is not defined. + XMPPMessageDirectionUnspecified, + /// A value indicating that the message has been received from the stream. + XMPPMessageDirectionIncoming, + /// A value indicating that the message is originating from the device. + XMPPMessageDirectionOutgoing +}; + +typedef NS_ENUM(int16_t, XMPPMessageType) { + /// A value indicating normal message type as per RFC 3921/6121 + XMPPMessageTypeNormal, + /// A value indicating chat message type as per RFC 3921/6121 + XMPPMessageTypeChat, + /// A value indicating error message type as per RFC 3921/6121 + XMPPMessageTypeError, + /// A value indicating groupchat message type as per RFC 3921/6121 + XMPPMessageTypeGroupchat, + /// A value indicating headline message type as per RFC 3921/6121 + XMPPMessageTypeHeadline +}; + +/** + An object storing the core XMPP message properties defined in RFC 3921/6121. + + @see XMPPMessageCoreDataStorage + @see XMPPMessageContextCoreDataStorageObject + @see XMPPMessageContextItemCoreDataStorageObject + */ +@interface XMPPMessageCoreDataStorageObject : NSManagedObject + +/// The value of "from" attribute (transient). +@property (nonatomic, strong, nullable) XMPPJID *fromJID; + +/// The value of "to" attribute (transient). +@property (nonatomic, strong, nullable) XMPPJID *toJID; + +/// The contents of "body" child element. +@property (nonatomic, copy, nullable) NSString *body; + +/// The value of "id" attribute. +@property (nonatomic, copy, nullable) NSString *stanzaID; + +/// The contents of "subject" child element. +@property (nonatomic, copy, nullable) NSString *subject; + +/// The contents of "thread" child element. +@property (nonatomic, copy, nullable) NSString *thread; + +/// The transmission direction from client's point of view. +@property (nonatomic, assign) XMPPMessageDirection direction; + +/// The value of "type" attribute. +@property (nonatomic, assign) XMPPMessageType type; + +/// @brief Returns the XML representation of the message including only the core RFC 3921/6121 properties. +/// @discussion Applications employing store-then-send approach to messaging can use this method to obtain the seed of an outgoing message stanza they later decorate with extension-derived values. +- (XMPPMessage *)coreMessage; + +/** + Records a unique outgoing XMPP stream element event ID for the message. + + After recording the ID, the application should use the @c sendElement:registeringEventWithID:andGetReceipt: method to send the message, providing the recorded value. + This way, modules will be able to track the message in their stream callbacks and update the storage accordingly. + + This method will trigger an assertion unless invoked on an outgoing message. It will also trigger an assertion if called more than once per actual transmission attempt. + */ +- (void)registerOutgoingMessageStreamEventID:(NSString *)outgoingMessageStreamEventID; + +/// @brief Returns the local stream JID for the most recent stream element event associated with the message. +/// @discussion Incoming messages always have a single stream element event associated with them. Outgoing messages can have 0 or more, one per each transmission attempt. +- (nullable XMPPJID *)streamJID; + +/// @brief Returns the timestamp for the most recent stream element event associated with the message. +/// @discussion Incoming messages always have a single stream element event associated with them. Outgoing messages can have 0 or more, one per each transmission attempt. +- (nullable NSDate *)streamTimestamp; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Extensions/MessageStorage/XMPPMessageCoreDataStorageObject.m b/Extensions/MessageStorage/XMPPMessageCoreDataStorageObject.m new file mode 100644 index 0000000000..6e24e9d4d6 --- /dev/null +++ b/Extensions/MessageStorage/XMPPMessageCoreDataStorageObject.m @@ -0,0 +1,527 @@ +#import "XMPPMessageCoreDataStorageObject.h" +#import "XMPPMessageCoreDataStorageObject+Protected.h" +#import "XMPPMessageCoreDataStorageObject+ContextHelpers.h" +#import "XMPPMessageContextItemCoreDataStorageObject.h" +#import "NSManagedObject+XMPPCoreDataStorage.h" +#import "XMPPJID.h" +#import "XMPPMessage.h" + +static XMPPMessageContextJIDItemTag const XMPPMessageContextStreamJIDTag = @"XMPPMessageContextStreamJID"; +static XMPPMessageContextMarkerItemTag const XMPPMessageContextPendingStreamContextAssignmentTag = @"XMPPMessageContextPendingStreamContextAssignment"; +static XMPPMessageContextMarkerItemTag const XMPPMessageContextLatestStreamTimestampRetirementTag = @"XMPPMessageContextLatestStreamTimestampRetirement"; +static XMPPMessageContextStringItemTag const XMPPMessageContextStreamEventIDTag = @"XMPPMessageContextStreamEventID"; +static XMPPMessageContextTimestampItemTag const XMPPMessageContextActiveStreamTimestampTag = @"XMPPMessageContextActiveStreamTimestamp"; +static XMPPMessageContextTimestampItemTag const XMPPMessageContextRetiredStreamTimestampTag = @"XMPPMessageContextRetiredStreamTimestamp"; + +@interface XMPPMessageCoreDataStorageObject () + +@property (nonatomic, copy, nullable) NSString *fromDomain; +@property (nonatomic, copy, nullable) NSString *fromResource; +@property (nonatomic, copy, nullable) NSString *fromUser; +@property (nonatomic, copy, nullable) NSString *toDomain; +@property (nonatomic, copy, nullable) NSString *toResource; +@property (nonatomic, copy, nullable) NSString *toUser; + +@property (nonatomic, copy, nullable) NSSet *contextElements; + +@end + +@interface XMPPMessageCoreDataStorageObject (CoreDataGeneratedPrimitiveAccessors) + +- (XMPPJID *)primitiveFromJID; +- (void)setPrimitiveFromJID:(XMPPJID *)value; +- (void)setPrimitiveFromDomain:(NSString *)value; +- (void)setPrimitiveFromResource:(NSString *)value; +- (void)setPrimitiveFromUser:(NSString *)value; + +- (XMPPJID *)primitiveToJID; +- (void)setPrimitiveToJID:(XMPPJID *)value; +- (void)setPrimitiveToDomain:(NSString *)value; +- (void)setPrimitiveToResource:(NSString *)value; +- (void)setPrimitiveToUser:(NSString *)value; + +@end + +@implementation XMPPMessageCoreDataStorageObject + +@dynamic fromDomain, fromResource, fromUser, toDomain, toResource, toUser, body, stanzaID, subject, thread, direction, type, contextElements; + +#pragma mark - fromJID transient property + +- (XMPPJID *)fromJID +{ + [self willAccessValueForKey:NSStringFromSelector(@selector(fromJID))]; + XMPPJID *fromJID = [self primitiveFromJID]; + [self didAccessValueForKey:NSStringFromSelector(@selector(fromJID))]; + + if (fromJID) { + return fromJID; + } + + XMPPJID *newFromJID = [XMPPJID jidWithUser:self.fromUser domain:self.fromDomain resource:self.fromResource]; + [self setPrimitiveFromJID:newFromJID]; + + return newFromJID; +} + +- (void)setFromJID:(XMPPJID *)fromJID +{ + if ([self.fromJID isEqualToJID:fromJID options:XMPPJIDCompareFull]) { + return; + } + + [self willChangeValueForKey:NSStringFromSelector(@selector(fromJID))]; + [self willChangeValueForKey:NSStringFromSelector(@selector(fromDomain))]; + [self willChangeValueForKey:NSStringFromSelector(@selector(fromResource))]; + [self willChangeValueForKey:NSStringFromSelector(@selector(fromUser))]; + [self setPrimitiveFromJID:fromJID]; + [self setPrimitiveFromDomain:fromJID.domain]; + [self setPrimitiveFromResource:fromJID.resource]; + [self setPrimitiveFromUser:fromJID.user]; + [self didChangeValueForKey:NSStringFromSelector(@selector(fromDomain))]; + [self didChangeValueForKey:NSStringFromSelector(@selector(fromResource))]; + [self didChangeValueForKey:NSStringFromSelector(@selector(fromUser))]; + [self didChangeValueForKey:NSStringFromSelector(@selector(fromJID))]; +} + +- (void)setFromDomain:(NSString *)fromDomain +{ + if ([self.fromDomain isEqualToString:fromDomain]) { + return; + } + + [self willChangeValueForKey:NSStringFromSelector(@selector(fromDomain))]; + [self willChangeValueForKey:NSStringFromSelector(@selector(fromJID))]; + [self setPrimitiveFromDomain:fromDomain]; + [self setPrimitiveFromJID:nil]; + [self didChangeValueForKey:NSStringFromSelector(@selector(fromJID))]; + [self didChangeValueForKey:NSStringFromSelector(@selector(fromDomain))]; +} + +- (void)setFromResource:(NSString *)fromResource +{ + if ([self.fromResource isEqualToString:fromResource]) { + return; + } + + [self willChangeValueForKey:NSStringFromSelector(@selector(fromResource))]; + [self willChangeValueForKey:NSStringFromSelector(@selector(fromJID))]; + [self setPrimitiveFromResource:fromResource]; + [self setPrimitiveFromJID:nil]; + [self didChangeValueForKey:NSStringFromSelector(@selector(fromJID))]; + [self didChangeValueForKey:NSStringFromSelector(@selector(fromResource))]; +} + +- (void)setFromUser:(NSString *)fromUser +{ + if ([self.fromUser isEqualToString:fromUser]) { + return; + } + + [self willChangeValueForKey:NSStringFromSelector(@selector(fromUser))]; + [self willChangeValueForKey:NSStringFromSelector(@selector(fromJID))]; + [self setPrimitiveFromUser:fromUser]; + [self setPrimitiveFromJID:nil]; + [self didChangeValueForKey:NSStringFromSelector(@selector(fromJID))]; + [self didChangeValueForKey:NSStringFromSelector(@selector(fromUser))]; +} + +#pragma mark - toJID transient property + +- (XMPPJID *)toJID +{ + [self willAccessValueForKey:NSStringFromSelector(@selector(toJID))]; + XMPPJID *toJID = [self primitiveToJID]; + [self didAccessValueForKey:NSStringFromSelector(@selector(toJID))]; + + if (toJID) { + return toJID; + } + + XMPPJID *newToJID = [XMPPJID jidWithUser:self.toUser domain:self.toDomain resource:self.toResource]; + [self setPrimitiveToJID:newToJID]; + + return newToJID; +} + +- (void)setToJID:(XMPPJID *)toJID +{ + if ([self.toJID isEqualToJID:toJID options:XMPPJIDCompareFull]) { + return; + } + + [self willChangeValueForKey:NSStringFromSelector(@selector(toJID))]; + [self willChangeValueForKey:NSStringFromSelector(@selector(toDomain))]; + [self willChangeValueForKey:NSStringFromSelector(@selector(toResource))]; + [self willChangeValueForKey:NSStringFromSelector(@selector(toUser))]; + [self setPrimitiveToJID:toJID]; + [self setPrimitiveToDomain:toJID.domain]; + [self setPrimitiveToResource:toJID.resource]; + [self setPrimitiveToUser:toJID.user]; + [self didChangeValueForKey:NSStringFromSelector(@selector(toDomain))]; + [self didChangeValueForKey:NSStringFromSelector(@selector(toResource))]; + [self didChangeValueForKey:NSStringFromSelector(@selector(toUser))]; + [self didChangeValueForKey:NSStringFromSelector(@selector(toJID))]; +} + +- (void)setToDomain:(NSString *)toDomain +{ + if ([self.toDomain isEqualToString:toDomain]) { + return; + } + + [self willChangeValueForKey:NSStringFromSelector(@selector(toDomain))]; + [self willChangeValueForKey:NSStringFromSelector(@selector(toJID))]; + [self setPrimitiveToDomain:toDomain]; + [self setPrimitiveToJID:nil]; + [self didChangeValueForKey:NSStringFromSelector(@selector(toJID))]; + [self didChangeValueForKey:NSStringFromSelector(@selector(toDomain))]; +} + +- (void)setToResource:(NSString *)toResource +{ + if ([self.toResource isEqualToString:toResource]) { + return; + } + + [self willChangeValueForKey:NSStringFromSelector(@selector(toResource))]; + [self willChangeValueForKey:NSStringFromSelector(@selector(toJID))]; + [self setPrimitiveToResource:toResource]; + [self setPrimitiveToJID:nil]; + [self didChangeValueForKey:NSStringFromSelector(@selector(toJID))]; + [self didChangeValueForKey:NSStringFromSelector(@selector(toResource))]; +} + +- (void)setToUser:(NSString *)toUser +{ + if ([self.toUser isEqualToString:toUser]) { + return; + } + + [self willChangeValueForKey:NSStringFromSelector(@selector(toUser))]; + [self willChangeValueForKey:NSStringFromSelector(@selector(toJID))]; + [self setPrimitiveToUser:toUser]; + [self setPrimitiveToJID:nil]; + [self didChangeValueForKey:NSStringFromSelector(@selector(toJID))]; + [self didChangeValueForKey:NSStringFromSelector(@selector(toUser))]; +} + +#pragma mark - Public + ++ (XMPPMessageCoreDataStorageObject *)findWithStreamEventID:(NSString *)streamEventID inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext +{ + NSFetchRequest *fetchRequest = [XMPPMessageContextStringItemCoreDataStorageObject xmpp_fetchRequestInManagedObjectContext:managedObjectContext]; + NSArray *predicates = @[[XMPPMessageContextStringItemCoreDataStorageObject stringPredicateWithValue:streamEventID], + [XMPPMessageContextStringItemCoreDataStorageObject tagPredicateWithValue:XMPPMessageContextStreamEventIDTag]]; + fetchRequest.predicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicates]; + + NSArray *fetchResult = [managedObjectContext xmpp_executeForcedSuccessFetchRequest:fetchRequest]; + NSAssert(fetchResult.count <= 1, @"Expected a single context item for any given stream event ID"); + + return fetchResult.firstObject.contextElement.message; +} + +- (XMPPMessage *)coreMessage +{ + NSString *typeString; + switch (self.type) { + case XMPPMessageTypeChat: + typeString = @"chat"; + break; + + case XMPPMessageTypeError: + typeString = @"error"; + break; + + case XMPPMessageTypeGroupchat: + typeString = @"groupchat"; + break; + + case XMPPMessageTypeHeadline: + typeString = @"headline"; + break; + + case XMPPMessageTypeNormal: + typeString = @"normal"; + break; + } + + XMPPMessage *message = [[XMPPMessage alloc] initWithType:typeString to:self.toJID elementID:self.stanzaID]; + + if (self.body) { + [message addBody:self.body]; + } + if (self.subject) { + [message addSubject:self.subject]; + } + if (self.thread) { + [message addThread:self.thread]; + } + + return message; +} + +- (void)registerIncomingMessageCore:(XMPPMessage *)message +{ + NSAssert(self.direction == XMPPMessageDirectionIncoming, @"Only applicable to incoming message objects"); + + self.fromJID = [message from]; + self.toJID = [message to]; + self.body = [message body]; + self.stanzaID = [message elementID]; + self.subject = [message subject]; + self.thread = [message thread]; + + if ([[message type] isEqualToString:@"chat"]) { + self.type = XMPPMessageTypeChat; + } else if ([[message type] isEqualToString:@"error"]) { + self.type = XMPPMessageTypeError; + } else if ([[message type] isEqualToString:@"groupchat"]) { + self.type = XMPPMessageTypeGroupchat; + } else if ([[message type] isEqualToString:@"headline"]) { + self.type = XMPPMessageTypeHeadline; + } else { + self.type = XMPPMessageTypeNormal; + } +} + +- (void)registerIncomingMessageStreamEventID:(NSString *)streamEventID streamJID:(XMPPJID *)streamJID streamEventTimestamp:(NSDate *)streamEventTimestamp +{ + NSAssert(self.direction == XMPPMessageDirectionIncoming, @"Only applicable to incoming message objects"); + NSAssert(![self lookupCurrentStreamContext], @"Another stream context element already exists"); + + XMPPMessageContextCoreDataStorageObject *streamContext = [self appendContextElement]; + [streamContext appendStringItemWithTag:XMPPMessageContextStreamEventIDTag value:streamEventID]; + [streamContext appendJIDItemWithTag:XMPPMessageContextStreamJIDTag value:streamJID]; + [streamContext appendTimestampItemWithTag:XMPPMessageContextActiveStreamTimestampTag value:streamEventTimestamp]; +} + +- (void)registerOutgoingMessageStreamEventID:(NSString *)outgoingMessageStreamEventID +{ + NSAssert(self.direction == XMPPMessageDirectionOutgoing, @"Only applicable to outgoing message objects"); + NSAssert(![self lookupPendingStreamContext], @"Pending stream context element already exists"); + + XMPPMessageContextCoreDataStorageObject *streamContext = [self appendContextElement]; + [streamContext appendStringItemWithTag:XMPPMessageContextStreamEventIDTag value:outgoingMessageStreamEventID]; + [streamContext appendMarkerItemWithTag:XMPPMessageContextPendingStreamContextAssignmentTag]; +} + +- (void)registerOutgoingMessageStreamJID:(XMPPJID *)streamJID streamEventTimestamp:(NSDate *)streamEventTimestamp +{ + NSAssert(self.direction == XMPPMessageDirectionOutgoing, @"Only applicable to outgoing message objects"); + + XMPPMessageContextCoreDataStorageObject *streamContext = [self lookupPendingStreamContext]; + NSAssert(streamContext, @"No pending stream context element found"); + + XMPPMessageContextTimestampItemTag timestampTag; + if ([self lookupActiveStreamContext]) { + [self retireStreamTimestamp]; + timestampTag = XMPPMessageContextActiveStreamTimestampTag; + } else if (![self lookupLatestRetiredStreamContext]) { + timestampTag = XMPPMessageContextActiveStreamTimestampTag; + } else { + timestampTag = XMPPMessageContextRetiredStreamTimestampTag; + } + + [streamContext removeMarkerItemsWithTag:XMPPMessageContextPendingStreamContextAssignmentTag]; + [streamContext appendJIDItemWithTag:XMPPMessageContextStreamJIDTag value:streamJID]; + [streamContext appendTimestampItemWithTag:timestampTag value:streamEventTimestamp]; +} + +- (XMPPJID *)streamJID +{ + return [[self lookupCurrentStreamContext] jidItemValueForTag:XMPPMessageContextStreamJIDTag]; +} + +- (NSDate *)streamTimestamp +{ + XMPPMessageContextCoreDataStorageObject *latestStreamContext = [self lookupCurrentStreamContext]; + return [latestStreamContext timestampItemValueForTag:XMPPMessageContextActiveStreamTimestampTag] ?: [latestStreamContext timestampItemValueForTag:XMPPMessageContextRetiredStreamTimestampTag]; +} + +- (void)retireStreamTimestamp +{ + XMPPMessageContextCoreDataStorageObject *previousRetiredStreamContext = [self lookupLatestRetiredStreamContext]; + XMPPMessageContextCoreDataStorageObject *activeStreamContext = [self lookupActiveStreamContext]; + + if (activeStreamContext) { + [previousRetiredStreamContext removeMarkerItemsWithTag:XMPPMessageContextLatestStreamTimestampRetirementTag]; + + NSDate *retiredStreamTimestamp = [activeStreamContext timestampItemValueForTag:XMPPMessageContextActiveStreamTimestampTag]; + [activeStreamContext removeTimestampItemsWithTag:XMPPMessageContextActiveStreamTimestampTag]; + [activeStreamContext appendTimestampItemWithTag:XMPPMessageContextRetiredStreamTimestampTag value:retiredStreamTimestamp]; + [activeStreamContext appendMarkerItemWithTag:XMPPMessageContextLatestStreamTimestampRetirementTag]; + } else if (!previousRetiredStreamContext) { + XMPPMessageContextCoreDataStorageObject *initialPendingStreamContext = [self lookupPendingStreamContext]; + [initialPendingStreamContext appendMarkerItemWithTag:XMPPMessageContextLatestStreamTimestampRetirementTag]; + } else { + NSAssert(NO, @"No stream context element found for retiring"); + } +} + +#pragma mark - Overridden + +- (void)awakeFromSnapshotEvents:(NSSnapshotEventType)flags +{ + [super awakeFromSnapshotEvents:flags]; + + [self setPrimitiveFromJID:nil]; + [self setPrimitiveToJID:nil]; +} + +#pragma mark - Private + +- (XMPPMessageContextCoreDataStorageObject *)lookupPendingStreamContext +{ + return [self lookupInContextWithBlock:^id _Nullable(XMPPMessageContextCoreDataStorageObject * _Nonnull contextElement) { + return [contextElement hasMarkerItemForTag:XMPPMessageContextPendingStreamContextAssignmentTag] ? contextElement : nil; + }]; +} + +- (XMPPMessageContextCoreDataStorageObject *)lookupCurrentStreamContext +{ + return [self lookupActiveStreamContext] ?: [self lookupLatestRetiredStreamContext]; +} + +- (XMPPMessageContextCoreDataStorageObject *)lookupActiveStreamContext +{ + return [self lookupInContextWithBlock:^id _Nullable(XMPPMessageContextCoreDataStorageObject * _Nonnull contextElement) { + return [contextElement timestampItemValueForTag:XMPPMessageContextActiveStreamTimestampTag] ? contextElement : nil; + }]; +} + +- (XMPPMessageContextCoreDataStorageObject *)lookupLatestRetiredStreamContext +{ + return [self lookupInContextWithBlock:^id _Nullable(XMPPMessageContextCoreDataStorageObject * _Nonnull contextElement) { + return [contextElement hasMarkerItemForTag:XMPPMessageContextLatestStreamTimestampRetirementTag] ? contextElement : nil; + }]; +} + +@end + +@implementation XMPPMessageContextItemCoreDataStorageObject (XMPPMessageCoreDataStorageFetch) + ++ (NSFetchRequest *)requestByTimestampsWithPredicate:(NSPredicate *)predicate inAscendingOrder:(BOOL)isInAscendingOrder fromManagedObjectContext:(NSManagedObjectContext *)managedObjectContext +{ + NSFetchRequest *fetchRequest = [XMPPMessageContextTimestampItemCoreDataStorageObject xmpp_fetchRequestInManagedObjectContext:managedObjectContext]; + fetchRequest.predicate = predicate; + fetchRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:NSStringFromSelector(@selector(value)) ascending:isInAscendingOrder]]; + return fetchRequest; +} + ++ (NSPredicate *)streamTimestampKindPredicate +{ + return [XMPPMessageContextTimestampItemCoreDataStorageObject tagPredicateWithValue:XMPPMessageContextActiveStreamTimestampTag]; +} + ++ (NSPredicate *)timestampRangePredicateWithStartValue:(nullable NSDate *)startValue endValue:(nullable NSDate *)endValue +{ + return [XMPPMessageContextTimestampItemCoreDataStorageObject timestampRangePredicateWithStartValue:startValue endValue:endValue]; +} + ++ (NSPredicate *)messageFromJIDPredicateWithValue:(XMPPJID *)value compareOptions:(XMPPJIDCompareOptions)compareOptions +{ + return [self xmpp_jidPredicateWithDomainKeyPath:[[self messageKeyPath] stringByAppendingFormat:@".%@", NSStringFromSelector(@selector(fromDomain))] + resourceKeyPath:[[self messageKeyPath] stringByAppendingFormat:@".%@", NSStringFromSelector(@selector(fromResource))] + userKeyPath:[[self messageKeyPath] stringByAppendingFormat:@".%@", NSStringFromSelector(@selector(fromUser))] + value:value + compareOptions:compareOptions]; +} + ++ (NSPredicate *)messageToJIDPredicateWithValue:(XMPPJID *)value compareOptions:(XMPPJIDCompareOptions)compareOptions +{ + return [self xmpp_jidPredicateWithDomainKeyPath:[[self messageKeyPath] stringByAppendingFormat:@".%@", NSStringFromSelector(@selector(toDomain))] + resourceKeyPath:[[self messageKeyPath] stringByAppendingFormat:@".%@", NSStringFromSelector(@selector(toResource))] + userKeyPath:[[self messageKeyPath] stringByAppendingFormat:@".%@", NSStringFromSelector(@selector(toUser))] + value:value + compareOptions:compareOptions]; +} + ++ (NSPredicate *)messageRemotePartyJIDPredicateWithValue:(XMPPJID *)value compareOptions:(XMPPJIDCompareOptions)compareOptions +{ + NSArray *outgoingMessagePredicates = @[[self messageToJIDPredicateWithValue:value compareOptions:compareOptions], + [XMPPMessageContextItemCoreDataStorageObject messageDirectionPredicateWithValue:XMPPMessageDirectionOutgoing]]; + NSArray *incomingMessagePredicates = @[[self messageFromJIDPredicateWithValue:value compareOptions:compareOptions], + [XMPPMessageContextItemCoreDataStorageObject messageDirectionPredicateWithValue:XMPPMessageDirectionIncoming]]; + + return [NSCompoundPredicate orPredicateWithSubpredicates:@[[NSCompoundPredicate andPredicateWithSubpredicates:outgoingMessagePredicates], + [NSCompoundPredicate andPredicateWithSubpredicates:incomingMessagePredicates]]]; +} + ++ (NSPredicate *)messageBodyPredicateWithValue:(NSString *)value compareOperator:(XMPPMessageContentCompareOperator)compareOperator options:(XMPPMessageContentCompareOptions)options +{ + return [self messageContentPredicateWithKey:NSStringFromSelector(@selector(body)) value:value compareOperator:compareOperator options:options]; +} + ++ (NSPredicate *)messageSubjectPredicateWithValue:(NSString *)value compareOperator:(XMPPMessageContentCompareOperator)compareOperator options:(XMPPMessageContentCompareOptions)options +{ + return [self messageContentPredicateWithKey:NSStringFromSelector(@selector(subject)) value:value compareOperator:compareOperator options:options]; +} + ++ (NSPredicate *)messageThreadPredicateWithValue:(NSString *)value +{ + return [NSPredicate predicateWithFormat:@"%K.%K = %@", [self messageKeyPath], NSStringFromSelector(@selector(thread)), value]; +} + ++ (NSPredicate *)messageDirectionPredicateWithValue:(XMPPMessageDirection)value +{ + return [NSPredicate predicateWithFormat:@"%K.%K = %d", [self messageKeyPath], NSStringFromSelector(@selector(direction)), value]; +} + ++ (NSPredicate *)messageTypePredicateWithValue:(XMPPMessageType)value +{ + return [NSPredicate predicateWithFormat:@"%K.%K = %d", [self messageKeyPath], NSStringFromSelector(@selector(type)), value]; +} + ++ (NSPredicate *)messageContentPredicateWithKey:(NSString *)contentKey value:(NSString *)value compareOperator:(XMPPMessageContentCompareOperator)compareOperator options:(XMPPMessageContentCompareOptions)options +{ + NSMutableString *predicateFormat = [[NSMutableString alloc] initWithFormat:@"%@.%@ ", [self messageKeyPath], contentKey]; + + switch (compareOperator) { + case XMPPMessageContentCompareOperatorEquals: + [predicateFormat appendString:@"= "]; + break; + case XMPPMessageContentCompareOperatorBeginsWith: + [predicateFormat appendString:@"BEGINSWITH "]; + break; + case XMPPMessageContentCompareOperatorContains: + [predicateFormat appendString:@"CONTAINS "]; + break; + case XMPPMessageContentCompareOperatorEndsWith: + [predicateFormat appendString:@"ENDSWITH "]; + break; + case XMPPMessageContentCompareOperatorLike: + [predicateFormat appendString:@"LIKE "]; + break; + case XMPPMessageContentCompareOperatorMatches: + [predicateFormat appendString:@"MATCHES "]; + break; + } + + NSMutableString *optionString = [[NSMutableString alloc] init]; + if (options & XMPPMessageContentCompareCaseInsensitive) { + [optionString appendString:@"c"]; + } + if (options & XMPPMessageContentCompareDiacriticInsensitive) { + [optionString appendString:@"d"]; + } + if (optionString.length > 0) { + [predicateFormat appendFormat:@"[%@] ", optionString]; + } + + [predicateFormat appendString:@"%@"]; + + return [NSPredicate predicateWithFormat:predicateFormat, value]; +} + ++ (NSString *)messageKeyPath +{ + return [NSString stringWithFormat:@"%@.%@", NSStringFromSelector(@selector(contextElement)), NSStringFromSelector(@selector(message))]; +} + +- (XMPPMessageCoreDataStorageObject *)message +{ + return self.contextElement.message; +} + +@end diff --git a/XMPPFramework.xcodeproj/project.pbxproj b/XMPPFramework.xcodeproj/project.pbxproj index a48131ff9f..c61da53e1a 100644 --- a/XMPPFramework.xcodeproj/project.pbxproj +++ b/XMPPFramework.xcodeproj/project.pbxproj @@ -938,6 +938,42 @@ D9E6A05A1F92DEE100D8BFCB /* XMPPStanzaIdModule.m in Sources */ = {isa = PBXBuildFile; fileRef = D9E6A0561F92DEE100D8BFCB /* XMPPStanzaIdModule.m */; }; D9E6A05B1F92DEE100D8BFCB /* XMPPStanzaIdModule.m in Sources */ = {isa = PBXBuildFile; fileRef = D9E6A0561F92DEE100D8BFCB /* XMPPStanzaIdModule.m */; }; D9E6A05C1F92DEE100D8BFCB /* XMPPStanzaIdModule.m in Sources */ = {isa = PBXBuildFile; fileRef = D9E6A0561F92DEE100D8BFCB /* XMPPStanzaIdModule.m */; }; + DD1784121F3C9FA800D662A6 /* XMPPMessage.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DD1784061F3C9FA800D662A6 /* XMPPMessage.xcdatamodeld */; }; + DD1784131F3C9FA800D662A6 /* XMPPMessage.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DD1784061F3C9FA800D662A6 /* XMPPMessage.xcdatamodeld */; }; + DD1784141F3C9FA800D662A6 /* XMPPMessage.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DD1784061F3C9FA800D662A6 /* XMPPMessage.xcdatamodeld */; }; + DD1784151F3C9FA800D662A6 /* XMPPMessageCoreDataStorageObject.h in Headers */ = {isa = PBXBuildFile; fileRef = DD1784081F3C9FA800D662A6 /* XMPPMessageCoreDataStorageObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD1784161F3C9FA800D662A6 /* XMPPMessageCoreDataStorageObject.h in Headers */ = {isa = PBXBuildFile; fileRef = DD1784081F3C9FA800D662A6 /* XMPPMessageCoreDataStorageObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD1784171F3C9FA800D662A6 /* XMPPMessageCoreDataStorageObject.h in Headers */ = {isa = PBXBuildFile; fileRef = DD1784081F3C9FA800D662A6 /* XMPPMessageCoreDataStorageObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD1784181F3C9FA800D662A6 /* XMPPMessageCoreDataStorageObject.m in Sources */ = {isa = PBXBuildFile; fileRef = DD1784091F3C9FA800D662A6 /* XMPPMessageCoreDataStorageObject.m */; }; + DD1784191F3C9FA800D662A6 /* XMPPMessageCoreDataStorageObject.m in Sources */ = {isa = PBXBuildFile; fileRef = DD1784091F3C9FA800D662A6 /* XMPPMessageCoreDataStorageObject.m */; }; + DD17841A1F3C9FA800D662A6 /* XMPPMessageCoreDataStorageObject.m in Sources */ = {isa = PBXBuildFile; fileRef = DD1784091F3C9FA800D662A6 /* XMPPMessageCoreDataStorageObject.m */; }; + DD17841B1F3C9FA800D662A6 /* XMPPMessageContextCoreDataStorageObject.h in Headers */ = {isa = PBXBuildFile; fileRef = DD17840A1F3C9FA800D662A6 /* XMPPMessageContextCoreDataStorageObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD17841C1F3C9FA800D662A6 /* XMPPMessageContextCoreDataStorageObject.h in Headers */ = {isa = PBXBuildFile; fileRef = DD17840A1F3C9FA800D662A6 /* XMPPMessageContextCoreDataStorageObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD17841D1F3C9FA800D662A6 /* XMPPMessageContextCoreDataStorageObject.h in Headers */ = {isa = PBXBuildFile; fileRef = DD17840A1F3C9FA800D662A6 /* XMPPMessageContextCoreDataStorageObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD17841E1F3C9FA800D662A6 /* XMPPMessageContextCoreDataStorageObject.m in Sources */ = {isa = PBXBuildFile; fileRef = DD17840B1F3C9FA800D662A6 /* XMPPMessageContextCoreDataStorageObject.m */; }; + DD17841F1F3C9FA800D662A6 /* XMPPMessageContextCoreDataStorageObject.m in Sources */ = {isa = PBXBuildFile; fileRef = DD17840B1F3C9FA800D662A6 /* XMPPMessageContextCoreDataStorageObject.m */; }; + DD1784201F3C9FA800D662A6 /* XMPPMessageContextCoreDataStorageObject.m in Sources */ = {isa = PBXBuildFile; fileRef = DD17840B1F3C9FA800D662A6 /* XMPPMessageContextCoreDataStorageObject.m */; }; + DD1784211F3C9FA800D662A6 /* XMPPMessageCoreDataStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = DD17840C1F3C9FA800D662A6 /* XMPPMessageCoreDataStorage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD1784221F3C9FA800D662A6 /* XMPPMessageCoreDataStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = DD17840C1F3C9FA800D662A6 /* XMPPMessageCoreDataStorage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD1784231F3C9FA800D662A6 /* XMPPMessageCoreDataStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = DD17840C1F3C9FA800D662A6 /* XMPPMessageCoreDataStorage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD1784241F3C9FA800D662A6 /* XMPPMessageCoreDataStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = DD17840D1F3C9FA800D662A6 /* XMPPMessageCoreDataStorage.m */; }; + DD1784251F3C9FA800D662A6 /* XMPPMessageCoreDataStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = DD17840D1F3C9FA800D662A6 /* XMPPMessageCoreDataStorage.m */; }; + DD1784261F3C9FA800D662A6 /* XMPPMessageCoreDataStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = DD17840D1F3C9FA800D662A6 /* XMPPMessageCoreDataStorage.m */; }; + DD19E4011F8CA02000CED8EF /* XMPPMessageContextItemCoreDataStorageObject+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = DD19E4001F8C9FCB00CED8EF /* XMPPMessageContextItemCoreDataStorageObject+Protected.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD19E4021F8CA02000CED8EF /* XMPPMessageContextItemCoreDataStorageObject+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = DD19E4001F8C9FCB00CED8EF /* XMPPMessageContextItemCoreDataStorageObject+Protected.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD19E4031F8CA02100CED8EF /* XMPPMessageContextItemCoreDataStorageObject+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = DD19E4001F8C9FCB00CED8EF /* XMPPMessageContextItemCoreDataStorageObject+Protected.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD19E4041F8CA06D00CED8EF /* XMPPMessageContextCoreDataStorageObject+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = DD19E3FF1F8C9F0F00CED8EF /* XMPPMessageContextCoreDataStorageObject+Protected.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD19E4051F8CA06E00CED8EF /* XMPPMessageContextCoreDataStorageObject+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = DD19E3FF1F8C9F0F00CED8EF /* XMPPMessageContextCoreDataStorageObject+Protected.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD19E4061F8CA06F00CED8EF /* XMPPMessageContextCoreDataStorageObject+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = DD19E3FF1F8C9F0F00CED8EF /* XMPPMessageContextCoreDataStorageObject+Protected.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD19E40D1F8CA3E200CED8EF /* XMPPMessageCoreDataStorageObject+ContextHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = DD19E40B1F8CA3E200CED8EF /* XMPPMessageCoreDataStorageObject+ContextHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD19E40E1F8CA3E200CED8EF /* XMPPMessageCoreDataStorageObject+ContextHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = DD19E40B1F8CA3E200CED8EF /* XMPPMessageCoreDataStorageObject+ContextHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD19E40F1F8CA3E200CED8EF /* XMPPMessageCoreDataStorageObject+ContextHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = DD19E40B1F8CA3E200CED8EF /* XMPPMessageCoreDataStorageObject+ContextHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD19E4101F8CA3E200CED8EF /* XMPPMessageCoreDataStorageObject+ContextHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = DD19E40C1F8CA3E200CED8EF /* XMPPMessageCoreDataStorageObject+ContextHelpers.m */; }; + DD19E4111F8CA3E200CED8EF /* XMPPMessageCoreDataStorageObject+ContextHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = DD19E40C1F8CA3E200CED8EF /* XMPPMessageCoreDataStorageObject+ContextHelpers.m */; }; + DD19E4121F8CA3E200CED8EF /* XMPPMessageCoreDataStorageObject+ContextHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = DD19E40C1F8CA3E200CED8EF /* XMPPMessageCoreDataStorageObject+ContextHelpers.m */; }; + DD1E12301F5EE6100012A506 /* XMPPMessageCoreDataStorageObject+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = DD1E122F1F5EE4DA0012A506 /* XMPPMessageCoreDataStorageObject+Protected.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD1E12311F5EE6110012A506 /* XMPPMessageCoreDataStorageObject+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = DD1E122F1F5EE4DA0012A506 /* XMPPMessageCoreDataStorageObject+Protected.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD1E12321F5EE6120012A506 /* XMPPMessageCoreDataStorageObject+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = DD1E122F1F5EE4DA0012A506 /* XMPPMessageCoreDataStorageObject+Protected.h */; settings = {ATTRIBUTES = (Public, ); }; }; DD1E73331ED885FD009B529B /* XMPPRoomLightCoreDataStorage+XEP_0313.h in Headers */ = {isa = PBXBuildFile; fileRef = DD1E73311ED885FD009B529B /* XMPPRoomLightCoreDataStorage+XEP_0313.h */; settings = {ATTRIBUTES = (Public, ); }; }; DD1E73341ED885FD009B529B /* XMPPRoomLightCoreDataStorage+XEP_0313.h in Headers */ = {isa = PBXBuildFile; fileRef = DD1E73311ED885FD009B529B /* XMPPRoomLightCoreDataStorage+XEP_0313.h */; settings = {ATTRIBUTES = (Public, ); }; }; DD1E73351ED885FD009B529B /* XMPPRoomLightCoreDataStorage+XEP_0313.h in Headers */ = {isa = PBXBuildFile; fileRef = DD1E73311ED885FD009B529B /* XMPPRoomLightCoreDataStorage+XEP_0313.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -947,6 +983,18 @@ DD1E733A1ED88622009B529B /* XMPPRoomLightCoreDataStorageProtected.h in Headers */ = {isa = PBXBuildFile; fileRef = DD1E73391ED88622009B529B /* XMPPRoomLightCoreDataStorageProtected.h */; settings = {ATTRIBUTES = (Public, ); }; }; DD1E733B1ED88622009B529B /* XMPPRoomLightCoreDataStorageProtected.h in Headers */ = {isa = PBXBuildFile; fileRef = DD1E73391ED88622009B529B /* XMPPRoomLightCoreDataStorageProtected.h */; settings = {ATTRIBUTES = (Public, ); }; }; DD1E733C1ED88622009B529B /* XMPPRoomLightCoreDataStorageProtected.h in Headers */ = {isa = PBXBuildFile; fileRef = DD1E73391ED88622009B529B /* XMPPRoomLightCoreDataStorageProtected.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD1E80671F56AC4D003C21A0 /* XMPPMessageContextItemCoreDataStorageObject.h in Headers */ = {isa = PBXBuildFile; fileRef = DD1E80651F56AC4D003C21A0 /* XMPPMessageContextItemCoreDataStorageObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD1E80681F56AC4D003C21A0 /* XMPPMessageContextItemCoreDataStorageObject.h in Headers */ = {isa = PBXBuildFile; fileRef = DD1E80651F56AC4D003C21A0 /* XMPPMessageContextItemCoreDataStorageObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD1E80691F56AC4D003C21A0 /* XMPPMessageContextItemCoreDataStorageObject.h in Headers */ = {isa = PBXBuildFile; fileRef = DD1E80651F56AC4D003C21A0 /* XMPPMessageContextItemCoreDataStorageObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD1E806A1F56AC4D003C21A0 /* XMPPMessageContextItemCoreDataStorageObject.m in Sources */ = {isa = PBXBuildFile; fileRef = DD1E80661F56AC4D003C21A0 /* XMPPMessageContextItemCoreDataStorageObject.m */; }; + DD1E806B1F56AC4D003C21A0 /* XMPPMessageContextItemCoreDataStorageObject.m in Sources */ = {isa = PBXBuildFile; fileRef = DD1E80661F56AC4D003C21A0 /* XMPPMessageContextItemCoreDataStorageObject.m */; }; + DD1E806C1F56AC4D003C21A0 /* XMPPMessageContextItemCoreDataStorageObject.m in Sources */ = {isa = PBXBuildFile; fileRef = DD1E80661F56AC4D003C21A0 /* XMPPMessageContextItemCoreDataStorageObject.m */; }; + DDFFF40A1F3DE10700B99353 /* NSManagedObject+XMPPCoreDataStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = DDFFF4081F3DE10700B99353 /* NSManagedObject+XMPPCoreDataStorage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DDFFF40B1F3DE10700B99353 /* NSManagedObject+XMPPCoreDataStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = DDFFF4081F3DE10700B99353 /* NSManagedObject+XMPPCoreDataStorage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DDFFF40C1F3DE10700B99353 /* NSManagedObject+XMPPCoreDataStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = DDFFF4081F3DE10700B99353 /* NSManagedObject+XMPPCoreDataStorage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DDFFF40D1F3DE10700B99353 /* NSManagedObject+XMPPCoreDataStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = DDFFF4091F3DE10700B99353 /* NSManagedObject+XMPPCoreDataStorage.m */; }; + DDFFF40E1F3DE10700B99353 /* NSManagedObject+XMPPCoreDataStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = DDFFF4091F3DE10700B99353 /* NSManagedObject+XMPPCoreDataStorage.m */; }; + DDFFF40F1F3DE10700B99353 /* NSManagedObject+XMPPCoreDataStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = DDFFF4091F3DE10700B99353 /* NSManagedObject+XMPPCoreDataStorage.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1543,9 +1591,25 @@ D9DCD6BF1E625B4D0010D1C7 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/System/Library/Frameworks/AppKit.framework; sourceTree = DEVELOPER_DIR; }; D9E6A0551F92DEE100D8BFCB /* XMPPStanzaIdModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XMPPStanzaIdModule.h; sourceTree = ""; }; D9E6A0561F92DEE100D8BFCB /* XMPPStanzaIdModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XMPPStanzaIdModule.m; sourceTree = ""; }; + DD1784071F3C9FA800D662A6 /* XMPPMessage.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = XMPPMessage.xcdatamodel; sourceTree = ""; }; + DD1784081F3C9FA800D662A6 /* XMPPMessageCoreDataStorageObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMPPMessageCoreDataStorageObject.h; sourceTree = ""; }; + DD1784091F3C9FA800D662A6 /* XMPPMessageCoreDataStorageObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XMPPMessageCoreDataStorageObject.m; sourceTree = ""; }; + DD17840A1F3C9FA800D662A6 /* XMPPMessageContextCoreDataStorageObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMPPMessageContextCoreDataStorageObject.h; sourceTree = ""; }; + DD17840B1F3C9FA800D662A6 /* XMPPMessageContextCoreDataStorageObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XMPPMessageContextCoreDataStorageObject.m; sourceTree = ""; }; + DD17840C1F3C9FA800D662A6 /* XMPPMessageCoreDataStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMPPMessageCoreDataStorage.h; sourceTree = ""; }; + DD17840D1F3C9FA800D662A6 /* XMPPMessageCoreDataStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XMPPMessageCoreDataStorage.m; sourceTree = ""; }; + DD19E3FF1F8C9F0F00CED8EF /* XMPPMessageContextCoreDataStorageObject+Protected.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XMPPMessageContextCoreDataStorageObject+Protected.h"; sourceTree = ""; }; + DD19E4001F8C9FCB00CED8EF /* XMPPMessageContextItemCoreDataStorageObject+Protected.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XMPPMessageContextItemCoreDataStorageObject+Protected.h"; sourceTree = ""; }; + DD19E40B1F8CA3E200CED8EF /* XMPPMessageCoreDataStorageObject+ContextHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XMPPMessageCoreDataStorageObject+ContextHelpers.h"; sourceTree = ""; }; + DD19E40C1F8CA3E200CED8EF /* XMPPMessageCoreDataStorageObject+ContextHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "XMPPMessageCoreDataStorageObject+ContextHelpers.m"; sourceTree = ""; }; + DD1E122F1F5EE4DA0012A506 /* XMPPMessageCoreDataStorageObject+Protected.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XMPPMessageCoreDataStorageObject+Protected.h"; sourceTree = ""; }; DD1E73311ED885FD009B529B /* XMPPRoomLightCoreDataStorage+XEP_0313.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XMPPRoomLightCoreDataStorage+XEP_0313.h"; sourceTree = ""; }; DD1E73321ED885FD009B529B /* XMPPRoomLightCoreDataStorage+XEP_0313.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "XMPPRoomLightCoreDataStorage+XEP_0313.m"; sourceTree = ""; }; DD1E73391ED88622009B529B /* XMPPRoomLightCoreDataStorageProtected.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMPPRoomLightCoreDataStorageProtected.h; sourceTree = ""; }; + DD1E80651F56AC4D003C21A0 /* XMPPMessageContextItemCoreDataStorageObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMPPMessageContextItemCoreDataStorageObject.h; sourceTree = ""; }; + DD1E80661F56AC4D003C21A0 /* XMPPMessageContextItemCoreDataStorageObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XMPPMessageContextItemCoreDataStorageObject.m; sourceTree = ""; }; + DDFFF4081F3DE10700B99353 /* NSManagedObject+XMPPCoreDataStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSManagedObject+XMPPCoreDataStorage.h"; sourceTree = ""; }; + DDFFF4091F3DE10700B99353 /* NSManagedObject+XMPPCoreDataStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSManagedObject+XMPPCoreDataStorage.m"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1804,6 +1868,7 @@ D9DCD1431E6250930010D1C7 /* Reconnect */, D9DCD1461E6250930010D1C7 /* Roster */, D9DCD15E1E6250930010D1C7 /* SystemInputActivityMonitor */, + DD1784051F3C9FA800D662A6 /* MessageStorage */, D9DCD1611E6250930010D1C7 /* XEP-0009 */, D9DCD1681E6250930010D1C7 /* XEP-0012 */, D9DCD16D1E6250930010D1C7 /* XEP-0016 */, @@ -1863,6 +1928,8 @@ D9DCD1221E6250920010D1C7 /* XMPPCoreDataStorage.h */, D9DCD1231E6250920010D1C7 /* XMPPCoreDataStorage.m */, D9DCD1241E6250920010D1C7 /* XMPPCoreDataStorageProtected.h */, + DDFFF4081F3DE10700B99353 /* NSManagedObject+XMPPCoreDataStorage.h */, + DDFFF4091F3DE10700B99353 /* NSManagedObject+XMPPCoreDataStorage.m */, ); path = CoreDataStorage; sourceTree = ""; @@ -2581,6 +2648,27 @@ name = Frameworks; sourceTree = ""; }; + DD1784051F3C9FA800D662A6 /* MessageStorage */ = { + isa = PBXGroup; + children = ( + DD1784061F3C9FA800D662A6 /* XMPPMessage.xcdatamodeld */, + DD17840C1F3C9FA800D662A6 /* XMPPMessageCoreDataStorage.h */, + DD17840D1F3C9FA800D662A6 /* XMPPMessageCoreDataStorage.m */, + DD1784081F3C9FA800D662A6 /* XMPPMessageCoreDataStorageObject.h */, + DD1E122F1F5EE4DA0012A506 /* XMPPMessageCoreDataStorageObject+Protected.h */, + DD1784091F3C9FA800D662A6 /* XMPPMessageCoreDataStorageObject.m */, + DD19E40B1F8CA3E200CED8EF /* XMPPMessageCoreDataStorageObject+ContextHelpers.h */, + DD19E40C1F8CA3E200CED8EF /* XMPPMessageCoreDataStorageObject+ContextHelpers.m */, + DD17840A1F3C9FA800D662A6 /* XMPPMessageContextCoreDataStorageObject.h */, + DD19E3FF1F8C9F0F00CED8EF /* XMPPMessageContextCoreDataStorageObject+Protected.h */, + DD17840B1F3C9FA800D662A6 /* XMPPMessageContextCoreDataStorageObject.m */, + DD1E80651F56AC4D003C21A0 /* XMPPMessageContextItemCoreDataStorageObject.h */, + DD19E4001F8C9FCB00CED8EF /* XMPPMessageContextItemCoreDataStorageObject+Protected.h */, + DD1E80661F56AC4D003C21A0 /* XMPPMessageContextItemCoreDataStorageObject.m */, + ); + path = MessageStorage; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -2632,6 +2720,8 @@ D9DCD2681E6250930010D1C7 /* XMPPProcessOne.h in Headers */, D9DCD2E51E6250930010D1C7 /* XMPPCapsCoreDataStorageObject.h in Headers */, D9DCD27D1E6250930010D1C7 /* XMPPRoster.h in Headers */, + DDFFF40A1F3DE10700B99353 /* NSManagedObject+XMPPCoreDataStorage.h in Headers */, + DD1E80671F56AC4D003C21A0 /* XMPPMessageContextItemCoreDataStorageObject.h in Headers */, D9DCD25E1E6250930010D1C7 /* OMEMOModule.h in Headers */, D9DCD2581E6250930010D1C7 /* NSXMLElement+OMEMO.h in Headers */, D9DCD31C1E6250930010D1C7 /* NSXMLElement+XEP_0297.h in Headers */, @@ -2640,11 +2730,13 @@ D9DCD24B1E6250930010D1C7 /* XMPPBandwidthMonitor.h in Headers */, D9DCD2901E6250930010D1C7 /* XMPPRoomCoreDataStorage.h in Headers */, D9DCD3081E6250930010D1C7 /* XMPPAutoPing.h in Headers */, + DD1784151F3C9FA800D662A6 /* XMPPMessageCoreDataStorageObject.h in Headers */, D9DCD2F61E6250930010D1C7 /* XMPPvCardAvatarModule.h in Headers */, D9DCD28B1E6250930010D1C7 /* XMPPLastActivity.h in Headers */, D9DCD2601E6250930010D1C7 /* OMEMOPreKey.h in Headers */, D9DCD25A1E6250930010D1C7 /* OMEMOBundle.h in Headers */, D9DCD2A61E6250930010D1C7 /* XMPPMUC.h in Headers */, + DD19E4041F8CA06D00CED8EF /* XMPPMessageContextCoreDataStorageObject+Protected.h in Headers */, D9DCD27F1E6250930010D1C7 /* XMPPRosterPrivate.h in Headers */, D9DCD2661E6250930010D1C7 /* XMPPMessage+OMEMO.h in Headers */, D9DCD2851E6250930010D1C7 /* XMPPIQ+JabberRPCResonse.h in Headers */, @@ -2691,12 +2783,15 @@ D9DCD3021E6250930010D1C7 /* XMPPStreamManagementMemoryStorage.h in Headers */, D9DCD2CC1E6250930010D1C7 /* XMPPPubSub.h in Headers */, D9DCD25C1E6250930010D1C7 /* OMEMOKeyData.h in Headers */, + DD1784211F3C9FA800D662A6 /* XMPPMessageCoreDataStorage.h in Headers */, D9DCD2941E6250930010D1C7 /* XMPPRoomOccupantCoreDataStorageObject.h in Headers */, D9DCD29C1E6250930010D1C7 /* XMPPRoomOccupantHybridMemoryStorageObject.h in Headers */, + DD1E12301F5EE6100012A506 /* XMPPMessageCoreDataStorageObject+Protected.h in Headers */, D9DCD26E1E6250930010D1C7 /* XMPPResourceCoreDataStorageObject.h in Headers */, D9DCD2831E6250930010D1C7 /* XMPPIQ+JabberRPC.h in Headers */, D9DCD3201E6250930010D1C7 /* XMPPMessageArchiveManagement.h in Headers */, D9DCD2D61E6250930010D1C7 /* NSDate+XMPPDateTimeProfiles.h in Headers */, + DD19E40D1F8CA3E200CED8EF /* XMPPMessageCoreDataStorageObject+ContextHelpers.h in Headers */, D9DCD2BA1E6250930010D1C7 /* XMPPvCardTempAdrTypes.h in Headers */, D9DCD2C81E6250930010D1C7 /* XMPPResultSet.h in Headers */, D9DCD2B41E6250930010D1C7 /* XMPPvCardTempCoreDataStorageObject.h in Headers */, @@ -2730,6 +2825,8 @@ 0D44BB211E5370ED000930E0 /* XMPPStream.h in Headers */, 0D44BB1F1E5370ED000930E0 /* XMPPPresence.h in Headers */, 0D44BB191E5370ED000930E0 /* XMPPMessage.h in Headers */, + DD17841B1F3C9FA800D662A6 /* XMPPMessageContextCoreDataStorageObject.h in Headers */, + DD19E4011F8CA02000CED8EF /* XMPPMessageContextItemCoreDataStorageObject+Protected.h in Headers */, 0D44BB161E5370ED000930E0 /* XMPPJID.h in Headers */, 0D44BB4C1E537105000930E0 /* XMPPDeprecatedPlainAuthentication.h in Headers */, 0D44BB141E5370ED000930E0 /* XMPPIQ.h in Headers */, @@ -2797,6 +2894,8 @@ D9DCD4BE1E6256D90010D1C7 /* XMPPProcessOne.h in Headers */, D9DCD4BF1E6256D90010D1C7 /* XMPPCapsCoreDataStorageObject.h in Headers */, D9DCD4C01E6256D90010D1C7 /* XMPPRoster.h in Headers */, + DDFFF40B1F3DE10700B99353 /* NSManagedObject+XMPPCoreDataStorage.h in Headers */, + DD1E80681F56AC4D003C21A0 /* XMPPMessageContextItemCoreDataStorageObject.h in Headers */, D9DCD4C11E6256D90010D1C7 /* OMEMOModule.h in Headers */, D9DCD4C21E6256D90010D1C7 /* NSXMLElement+OMEMO.h in Headers */, D9DCD4C31E6256D90010D1C7 /* NSXMLElement+XEP_0297.h in Headers */, @@ -2805,11 +2904,13 @@ D9DCD4C61E6256D90010D1C7 /* XMPPBandwidthMonitor.h in Headers */, D9DCD4C71E6256D90010D1C7 /* XMPPRoomCoreDataStorage.h in Headers */, D9DCD4C81E6256D90010D1C7 /* XMPPAutoPing.h in Headers */, + DD1784161F3C9FA800D662A6 /* XMPPMessageCoreDataStorageObject.h in Headers */, D9DCD4C91E6256D90010D1C7 /* XMPPvCardAvatarModule.h in Headers */, D9DCD4CA1E6256D90010D1C7 /* XMPPLastActivity.h in Headers */, D9DCD4CB1E6256D90010D1C7 /* OMEMOPreKey.h in Headers */, D9DCD4CC1E6256D90010D1C7 /* OMEMOBundle.h in Headers */, D9DCD4CD1E6256D90010D1C7 /* XMPPMUC.h in Headers */, + DD19E4051F8CA06E00CED8EF /* XMPPMessageContextCoreDataStorageObject+Protected.h in Headers */, D9DCD4CE1E6256D90010D1C7 /* XMPPRosterPrivate.h in Headers */, D9DCD4CF1E6256D90010D1C7 /* XMPPMessage+OMEMO.h in Headers */, D9DCD4D01E6256D90010D1C7 /* XMPPIQ+JabberRPCResonse.h in Headers */, @@ -2856,12 +2957,15 @@ D9DCD4F61E6256D90010D1C7 /* XMPPStreamManagementMemoryStorage.h in Headers */, D9DCD4F71E6256D90010D1C7 /* XMPPPubSub.h in Headers */, D9DCD4F81E6256D90010D1C7 /* OMEMOKeyData.h in Headers */, + DD1784221F3C9FA800D662A6 /* XMPPMessageCoreDataStorage.h in Headers */, D9DCD4F91E6256D90010D1C7 /* XMPPRoomOccupantCoreDataStorageObject.h in Headers */, D9DCD4FA1E6256D90010D1C7 /* XMPPRoomOccupantHybridMemoryStorageObject.h in Headers */, + DD1E12311F5EE6110012A506 /* XMPPMessageCoreDataStorageObject+Protected.h in Headers */, D9DCD4FB1E6256D90010D1C7 /* XMPPResourceCoreDataStorageObject.h in Headers */, D9DCD4FC1E6256D90010D1C7 /* XMPPIQ+JabberRPC.h in Headers */, D9DCD4FD1E6256D90010D1C7 /* XMPPMessageArchiveManagement.h in Headers */, D9DCD4FE1E6256D90010D1C7 /* NSDate+XMPPDateTimeProfiles.h in Headers */, + DD19E40E1F8CA3E200CED8EF /* XMPPMessageCoreDataStorageObject+ContextHelpers.h in Headers */, D9DCD4FF1E6256D90010D1C7 /* XMPPvCardTempAdrTypes.h in Headers */, D9DCD5001E6256D90010D1C7 /* XMPPResultSet.h in Headers */, D9DCD5011E6256D90010D1C7 /* XMPPvCardTempCoreDataStorageObject.h in Headers */, @@ -2895,6 +2999,8 @@ D9DCD51C1E6256D90010D1C7 /* XMPPStream.h in Headers */, D9DCD51D1E6256D90010D1C7 /* XMPPPresence.h in Headers */, D9DCD51E1E6256D90010D1C7 /* XMPPMessage.h in Headers */, + DD17841C1F3C9FA800D662A6 /* XMPPMessageContextCoreDataStorageObject.h in Headers */, + DD19E4021F8CA02000CED8EF /* XMPPMessageContextItemCoreDataStorageObject+Protected.h in Headers */, D9DCD51F1E6256D90010D1C7 /* XMPPJID.h in Headers */, D9DCD5201E6256D90010D1C7 /* XMPPDeprecatedPlainAuthentication.h in Headers */, D9DCD5211E6256D90010D1C7 /* XMPPIQ.h in Headers */, @@ -2962,6 +3068,8 @@ D9DCD6211E6258CF0010D1C7 /* XMPPProcessOne.h in Headers */, D9DCD6221E6258CF0010D1C7 /* XMPPCapsCoreDataStorageObject.h in Headers */, D9DCD6231E6258CF0010D1C7 /* XMPPRoster.h in Headers */, + DDFFF40C1F3DE10700B99353 /* NSManagedObject+XMPPCoreDataStorage.h in Headers */, + DD1E80691F56AC4D003C21A0 /* XMPPMessageContextItemCoreDataStorageObject.h in Headers */, D9DCD6241E6258CF0010D1C7 /* OMEMOModule.h in Headers */, D9DCD6251E6258CF0010D1C7 /* NSXMLElement+OMEMO.h in Headers */, D9DCD6261E6258CF0010D1C7 /* NSXMLElement+XEP_0297.h in Headers */, @@ -2970,11 +3078,13 @@ D9DCD6291E6258CF0010D1C7 /* XMPPBandwidthMonitor.h in Headers */, D9DCD62A1E6258CF0010D1C7 /* XMPPRoomCoreDataStorage.h in Headers */, D9DCD62B1E6258CF0010D1C7 /* XMPPAutoPing.h in Headers */, + DD1784171F3C9FA800D662A6 /* XMPPMessageCoreDataStorageObject.h in Headers */, D9DCD62C1E6258CF0010D1C7 /* XMPPvCardAvatarModule.h in Headers */, D9DCD62D1E6258CF0010D1C7 /* XMPPLastActivity.h in Headers */, D9DCD62E1E6258CF0010D1C7 /* OMEMOPreKey.h in Headers */, D9DCD62F1E6258CF0010D1C7 /* OMEMOBundle.h in Headers */, D9DCD6301E6258CF0010D1C7 /* XMPPMUC.h in Headers */, + DD19E4061F8CA06F00CED8EF /* XMPPMessageContextCoreDataStorageObject+Protected.h in Headers */, D9DCD6311E6258CF0010D1C7 /* XMPPRosterPrivate.h in Headers */, D9DCD6321E6258CF0010D1C7 /* XMPPMessage+OMEMO.h in Headers */, D9DCD6331E6258CF0010D1C7 /* XMPPIQ+JabberRPCResonse.h in Headers */, @@ -3021,12 +3131,15 @@ D9DCD6591E6258CF0010D1C7 /* XMPPStreamManagementMemoryStorage.h in Headers */, D9DCD65A1E6258CF0010D1C7 /* XMPPPubSub.h in Headers */, D9DCD65B1E6258CF0010D1C7 /* OMEMOKeyData.h in Headers */, + DD1784231F3C9FA800D662A6 /* XMPPMessageCoreDataStorage.h in Headers */, D9DCD65C1E6258CF0010D1C7 /* XMPPRoomOccupantCoreDataStorageObject.h in Headers */, D9DCD65D1E6258CF0010D1C7 /* XMPPRoomOccupantHybridMemoryStorageObject.h in Headers */, + DD1E12321F5EE6120012A506 /* XMPPMessageCoreDataStorageObject+Protected.h in Headers */, D9DCD65E1E6258CF0010D1C7 /* XMPPResourceCoreDataStorageObject.h in Headers */, D9DCD65F1E6258CF0010D1C7 /* XMPPIQ+JabberRPC.h in Headers */, D9DCD6601E6258CF0010D1C7 /* XMPPMessageArchiveManagement.h in Headers */, D9DCD6611E6258CF0010D1C7 /* NSDate+XMPPDateTimeProfiles.h in Headers */, + DD19E40F1F8CA3E200CED8EF /* XMPPMessageCoreDataStorageObject+ContextHelpers.h in Headers */, D9DCD6621E6258CF0010D1C7 /* XMPPvCardTempAdrTypes.h in Headers */, D9DCD6631E6258CF0010D1C7 /* XMPPResultSet.h in Headers */, D9DCD6641E6258CF0010D1C7 /* XMPPvCardTempCoreDataStorageObject.h in Headers */, @@ -3060,6 +3173,8 @@ D9DCD67F1E6258CF0010D1C7 /* XMPPStream.h in Headers */, D9DCD6801E6258CF0010D1C7 /* XMPPPresence.h in Headers */, D9DCD6811E6258CF0010D1C7 /* XMPPMessage.h in Headers */, + DD17841D1F3C9FA800D662A6 /* XMPPMessageContextCoreDataStorageObject.h in Headers */, + DD19E4031F8CA02100CED8EF /* XMPPMessageContextItemCoreDataStorageObject+Protected.h in Headers */, D9DCD6821E6258CF0010D1C7 /* XMPPJID.h in Headers */, D9DCD6831E6258CF0010D1C7 /* XMPPDeprecatedPlainAuthentication.h in Headers */, D9DCD6841E6258CF0010D1C7 /* XMPPIQ.h in Headers */, @@ -3437,6 +3552,7 @@ D9DCD3131E6250930010D1C7 /* XEP_0223.m in Sources */, D9DCD2651E6250930010D1C7 /* XMPPIQ+OMEMO.m in Sources */, D9DCD3151E6250930010D1C7 /* XMPPAttentionModule.m in Sources */, + DD1E806A1F56AC4D003C21A0 /* XMPPMessageContextItemCoreDataStorageObject.m in Sources */, D9DCD2551E6250930010D1C7 /* XMPPOutgoingFileTransfer.m in Sources */, D9DCD2951E6250930010D1C7 /* XMPPRoomOccupantCoreDataStorageObject.m in Sources */, D9DCD2931E6250930010D1C7 /* XMPPRoomMessageCoreDataStorageObject.m in Sources */, @@ -3477,6 +3593,7 @@ D9DCD24C1E6250930010D1C7 /* XMPPBandwidthMonitor.m in Sources */, D9DCD3111E6250930010D1C7 /* NSXMLElement+XEP_0203.m in Sources */, 0D44BB551E537105000930E0 /* XMPPXOAuth2Google.m in Sources */, + DD1784241F3C9FA800D662A6 /* XMPPMessageCoreDataStorage.m in Sources */, D9DCD2EB1E6250930010D1C7 /* XMPPMessageArchiving.xcdatamodeld in Sources */, D9DCD2A51E6250930010D1C7 /* XMPPMessage+XEP0045.m in Sources */, D9DCD2DF1E6250930010D1C7 /* XMPPTransports.m in Sources */, @@ -3536,6 +3653,7 @@ D9DCD2A91E6250930010D1C7 /* XMPPRoom.m in Sources */, D9DCD25B1E6250930010D1C7 /* OMEMOBundle.m in Sources */, D9DCD2E61E6250930010D1C7 /* XMPPCapsCoreDataStorageObject.m in Sources */, + DD1784181F3C9FA800D662A6 /* XMPPMessageCoreDataStorageObject.m in Sources */, D9DCD2D51E6250930010D1C7 /* XMPPRegistration.m in Sources */, D9DCD27E1E6250930010D1C7 /* XMPPRoster.m in Sources */, D9DCD3051E6250930010D1C7 /* XMPPStreamManagementStanzas.m in Sources */, @@ -3559,12 +3677,16 @@ D9DCD2DD1E6250930010D1C7 /* XMPPSoftwareVersion.m in Sources */, D9DCD28A1E6250930010D1C7 /* XMPPIQ+LastActivity.m in Sources */, D9DCD2B31E6250930010D1C7 /* XMPPvCardCoreDataStorageObject.m in Sources */, + DDFFF40D1F3DE10700B99353 /* NSManagedObject+XMPPCoreDataStorage.m in Sources */, D9DCD2B51E6250930010D1C7 /* XMPPvCardTempCoreDataStorageObject.m in Sources */, D9DCD3231E6250930010D1C7 /* XMPPMessage+XEP_0333.m in Sources */, D9DCD2A11E6250930010D1C7 /* XMPPRoomMessageMemoryStorageObject.m in Sources */, D9DCD2721E6250930010D1C7 /* XMPPRosterCoreDataStorage.m in Sources */, 0D44BB681E537110000930E0 /* DDList.m in Sources */, + DD17841E1F3C9FA800D662A6 /* XMPPMessageContextCoreDataStorageObject.m in Sources */, 0D44BB701E537110000930E0 /* XMPPSRVResolver.m in Sources */, + DD19E4101F8CA3E200CED8EF /* XMPPMessageCoreDataStorageObject+ContextHelpers.m in Sources */, + DD1784121F3C9FA800D662A6 /* XMPPMessage.xcdatamodeld in Sources */, D9DCD3191E6250930010D1C7 /* XMPPMessage+XEP_0280.m in Sources */, 0D44BB6E1E537110000930E0 /* XMPPIDTracker.m in Sources */, D9DCD2DB1E6250930010D1C7 /* XMPPMessage+XEP_0085.m in Sources */, @@ -3593,6 +3715,7 @@ D9DCD40D1E6256D90010D1C7 /* XEP_0223.m in Sources */, D9DCD40E1E6256D90010D1C7 /* XMPPIQ+OMEMO.m in Sources */, D9DCD40F1E6256D90010D1C7 /* XMPPAttentionModule.m in Sources */, + DD1E806B1F56AC4D003C21A0 /* XMPPMessageContextItemCoreDataStorageObject.m in Sources */, D9DCD4101E6256D90010D1C7 /* XMPPOutgoingFileTransfer.m in Sources */, D9DCD4111E6256D90010D1C7 /* XMPPRoomOccupantCoreDataStorageObject.m in Sources */, D9DCD4121E6256D90010D1C7 /* XMPPRoomMessageCoreDataStorageObject.m in Sources */, @@ -3633,6 +3756,7 @@ D9DCD4321E6256D90010D1C7 /* XMPPBandwidthMonitor.m in Sources */, D9DCD4331E6256D90010D1C7 /* NSXMLElement+XEP_0203.m in Sources */, D9DCD4341E6256D90010D1C7 /* XMPPXOAuth2Google.m in Sources */, + DD1784251F3C9FA800D662A6 /* XMPPMessageCoreDataStorage.m in Sources */, D9DCD4351E6256D90010D1C7 /* XMPPMessageArchiving.xcdatamodeld in Sources */, D9DCD4361E6256D90010D1C7 /* XMPPMessage+XEP0045.m in Sources */, D9DCD4371E6256D90010D1C7 /* XMPPTransports.m in Sources */, @@ -3692,6 +3816,7 @@ D9DCD46C1E6256D90010D1C7 /* XMPPRoom.m in Sources */, D9DCD46D1E6256D90010D1C7 /* OMEMOBundle.m in Sources */, D9DCD46E1E6256D90010D1C7 /* XMPPCapsCoreDataStorageObject.m in Sources */, + DD1784191F3C9FA800D662A6 /* XMPPMessageCoreDataStorageObject.m in Sources */, D9DCD46F1E6256D90010D1C7 /* XMPPRegistration.m in Sources */, D9DCD4701E6256D90010D1C7 /* XMPPRoster.m in Sources */, D9DCD4711E6256D90010D1C7 /* XMPPStreamManagementStanzas.m in Sources */, @@ -3715,12 +3840,16 @@ D9DCD4821E6256D90010D1C7 /* XMPPSoftwareVersion.m in Sources */, D9DCD4831E6256D90010D1C7 /* XMPPIQ+LastActivity.m in Sources */, D9DCD4841E6256D90010D1C7 /* XMPPvCardCoreDataStorageObject.m in Sources */, + DDFFF40E1F3DE10700B99353 /* NSManagedObject+XMPPCoreDataStorage.m in Sources */, D9DCD4851E6256D90010D1C7 /* XMPPvCardTempCoreDataStorageObject.m in Sources */, D9DCD4861E6256D90010D1C7 /* XMPPMessage+XEP_0333.m in Sources */, D9DCD4871E6256D90010D1C7 /* XMPPRoomMessageMemoryStorageObject.m in Sources */, D9DCD4881E6256D90010D1C7 /* XMPPRosterCoreDataStorage.m in Sources */, D9DCD4891E6256D90010D1C7 /* DDList.m in Sources */, + DD17841F1F3C9FA800D662A6 /* XMPPMessageContextCoreDataStorageObject.m in Sources */, D9DCD48A1E6256D90010D1C7 /* XMPPSRVResolver.m in Sources */, + DD19E4111F8CA3E200CED8EF /* XMPPMessageCoreDataStorageObject+ContextHelpers.m in Sources */, + DD1784131F3C9FA800D662A6 /* XMPPMessage.xcdatamodeld in Sources */, D9DCD48B1E6256D90010D1C7 /* XMPPMessage+XEP_0280.m in Sources */, D9DCD48C1E6256D90010D1C7 /* XMPPIDTracker.m in Sources */, D9DCD48D1E6256D90010D1C7 /* XMPPMessage+XEP_0085.m in Sources */, @@ -3749,6 +3878,7 @@ D9DCD5701E6258CF0010D1C7 /* XEP_0223.m in Sources */, D9DCD5711E6258CF0010D1C7 /* XMPPIQ+OMEMO.m in Sources */, D9DCD5721E6258CF0010D1C7 /* XMPPAttentionModule.m in Sources */, + DD1E806C1F56AC4D003C21A0 /* XMPPMessageContextItemCoreDataStorageObject.m in Sources */, D9DCD5731E6258CF0010D1C7 /* XMPPOutgoingFileTransfer.m in Sources */, D9DCD5741E6258CF0010D1C7 /* XMPPRoomOccupantCoreDataStorageObject.m in Sources */, D9DCD5751E6258CF0010D1C7 /* XMPPRoomMessageCoreDataStorageObject.m in Sources */, @@ -3789,6 +3919,7 @@ D9DCD5951E6258CF0010D1C7 /* XMPPBandwidthMonitor.m in Sources */, D9DCD5961E6258CF0010D1C7 /* NSXMLElement+XEP_0203.m in Sources */, D9DCD5971E6258CF0010D1C7 /* XMPPXOAuth2Google.m in Sources */, + DD1784261F3C9FA800D662A6 /* XMPPMessageCoreDataStorage.m in Sources */, D9DCD5981E6258CF0010D1C7 /* XMPPMessageArchiving.xcdatamodeld in Sources */, D9DCD5991E6258CF0010D1C7 /* XMPPMessage+XEP0045.m in Sources */, D9DCD59A1E6258CF0010D1C7 /* XMPPTransports.m in Sources */, @@ -3848,6 +3979,7 @@ D9DCD5CF1E6258CF0010D1C7 /* XMPPRoom.m in Sources */, D9DCD5D01E6258CF0010D1C7 /* OMEMOBundle.m in Sources */, D9DCD5D11E6258CF0010D1C7 /* XMPPCapsCoreDataStorageObject.m in Sources */, + DD17841A1F3C9FA800D662A6 /* XMPPMessageCoreDataStorageObject.m in Sources */, D9DCD5D21E6258CF0010D1C7 /* XMPPRegistration.m in Sources */, D9DCD5D31E6258CF0010D1C7 /* XMPPRoster.m in Sources */, D9DCD5D41E6258CF0010D1C7 /* XMPPStreamManagementStanzas.m in Sources */, @@ -3871,12 +4003,16 @@ D9DCD5E51E6258CF0010D1C7 /* XMPPSoftwareVersion.m in Sources */, D9DCD5E61E6258CF0010D1C7 /* XMPPIQ+LastActivity.m in Sources */, D9DCD5E71E6258CF0010D1C7 /* XMPPvCardCoreDataStorageObject.m in Sources */, + DDFFF40F1F3DE10700B99353 /* NSManagedObject+XMPPCoreDataStorage.m in Sources */, D9DCD5E81E6258CF0010D1C7 /* XMPPvCardTempCoreDataStorageObject.m in Sources */, D9DCD5E91E6258CF0010D1C7 /* XMPPMessage+XEP_0333.m in Sources */, D9DCD5EA1E6258CF0010D1C7 /* XMPPRoomMessageMemoryStorageObject.m in Sources */, D9DCD5EB1E6258CF0010D1C7 /* XMPPRosterCoreDataStorage.m in Sources */, D9DCD5EC1E6258CF0010D1C7 /* DDList.m in Sources */, + DD1784201F3C9FA800D662A6 /* XMPPMessageContextCoreDataStorageObject.m in Sources */, D9DCD5ED1E6258CF0010D1C7 /* XMPPSRVResolver.m in Sources */, + DD19E4121F8CA3E200CED8EF /* XMPPMessageCoreDataStorageObject+ContextHelpers.m in Sources */, + DD1784141F3C9FA800D662A6 /* XMPPMessage.xcdatamodeld in Sources */, D9DCD5EE1E6258CF0010D1C7 /* XMPPMessage+XEP_0280.m in Sources */, D9DCD5EF1E6258CF0010D1C7 /* XMPPIDTracker.m in Sources */, D9DCD5F01E6258CF0010D1C7 /* XMPPMessage+XEP_0085.m in Sources */, @@ -4288,6 +4424,16 @@ sourceTree = ""; versionGroupType = wrapper.xcdatamodel; }; + DD1784061F3C9FA800D662A6 /* XMPPMessage.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + DD1784071F3C9FA800D662A6 /* XMPPMessage.xcdatamodel */, + ); + currentVersion = DD1784071F3C9FA800D662A6 /* XMPPMessage.xcdatamodel */; + path = XMPPMessage.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; /* End XCVersionGroup section */ }; rootObject = 0D44BAE11E537066000930E0 /* Project object */; diff --git a/Xcode/Testing-Carthage/XMPPFrameworkTests.xcodeproj/project.pbxproj b/Xcode/Testing-Carthage/XMPPFrameworkTests.xcodeproj/project.pbxproj index c7b0708fcb..22209bf74f 100644 --- a/Xcode/Testing-Carthage/XMPPFrameworkTests.xcodeproj/project.pbxproj +++ b/Xcode/Testing-Carthage/XMPPFrameworkTests.xcodeproj/project.pbxproj @@ -64,6 +64,9 @@ D9DCD70E1E625C560010D1C7 /* OMEMOTestStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = D99C5E0C1D99C48100FB068A /* OMEMOTestStorage.m */; }; D9DCD7191E625CAE0010D1C7 /* XMPPFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D9DCD6B01E625A9B0010D1C7 /* XMPPFramework.framework */; }; D9E35E701D90B894002E7CF7 /* OMEMOElementTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D9E35E6F1D90B894002E7CF7 /* OMEMOElementTests.m */; }; + DD4003DC1F7527B10078D144 /* XMPPMessageCoreDataStorageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DD4003DB1F7527B10078D144 /* XMPPMessageCoreDataStorageTests.m */; }; + DD4003DD1F7527D70078D144 /* XMPPMessageCoreDataStorageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DD4003DB1F7527B10078D144 /* XMPPMessageCoreDataStorageTests.m */; }; + DD4003DE1F7527D70078D144 /* XMPPMessageCoreDataStorageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DD4003DB1F7527B10078D144 /* XMPPMessageCoreDataStorageTests.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -137,6 +140,7 @@ D9DCD3EC1E6255E10010D1C7 /* XMPPFrameworkTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XMPPFrameworkTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D9DCD7151E625C560010D1C7 /* XMPPFrameworkTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XMPPFrameworkTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D9E35E6F1D90B894002E7CF7 /* OMEMOElementTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OMEMOElementTests.m; path = "../Testing-Shared/OMEMOElementTests.m"; sourceTree = SOURCE_ROOT; }; + DD4003DB1F7527B10078D144 /* XMPPMessageCoreDataStorageTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XMPPMessageCoreDataStorageTests.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -207,6 +211,7 @@ D973A0791D2F18040096F3ED /* XMPPStorageHintTests.m */, D973A07A1D2F18040096F3ED /* XMPPURITests.m */, D973A07B1D2F18040096F3ED /* XMPPvCardTests.m */, + DD4003DB1F7527B10078D144 /* XMPPMessageCoreDataStorageTests.m */, 63F50D971C60208200CA0201 /* Info.plist */, ); name = XMPPFrameworkTests; @@ -386,6 +391,7 @@ buildActionMask = 2147483647; files = ( D973A07C1D2F18040096F3ED /* CapabilitiesHashingTest.m in Sources */, + DD4003DC1F7527B10078D144 /* XMPPMessageCoreDataStorageTests.m in Sources */, D973A0811D2F18040096F3ED /* XMPPMUCLightTests.m in Sources */, D973A07D1D2F18040096F3ED /* EncodeDecodeTest.m in Sources */, D973A0831D2F18040096F3ED /* XMPPRoomLightCoreDataStorageTests.m in Sources */, @@ -411,6 +417,7 @@ buildActionMask = 2147483647; files = ( D9DCD3D51E6255E10010D1C7 /* CapabilitiesHashingTest.m in Sources */, + DD4003DD1F7527D70078D144 /* XMPPMessageCoreDataStorageTests.m in Sources */, D9DCD3D61E6255E10010D1C7 /* XMPPMUCLightTests.m in Sources */, D9DCD3D71E6255E10010D1C7 /* EncodeDecodeTest.m in Sources */, D9DCD3D81E6255E10010D1C7 /* XMPPRoomLightCoreDataStorageTests.m in Sources */, @@ -436,6 +443,7 @@ buildActionMask = 2147483647; files = ( D9DCD6FE1E625C560010D1C7 /* CapabilitiesHashingTest.m in Sources */, + DD4003DE1F7527D70078D144 /* XMPPMessageCoreDataStorageTests.m in Sources */, D9DCD6FF1E625C560010D1C7 /* XMPPMUCLightTests.m in Sources */, D9DCD7001E625C560010D1C7 /* EncodeDecodeTest.m in Sources */, D9DCD7011E625C560010D1C7 /* XMPPRoomLightCoreDataStorageTests.m in Sources */, diff --git a/Xcode/Testing-Shared/XMPPMessageCoreDataStorageTests.m b/Xcode/Testing-Shared/XMPPMessageCoreDataStorageTests.m new file mode 100644 index 0000000000..e49e73f28b --- /dev/null +++ b/Xcode/Testing-Shared/XMPPMessageCoreDataStorageTests.m @@ -0,0 +1,586 @@ +// +// XMPPMessageCoreDataStorageTests.m +// XMPPFrameworkTests +// +// Created by Piotr Wegrzynek on 10/08/2017. +// +// + +#import +@import XMPPFramework; + +@interface XMPPMessageCoreDataStorageTests : XCTestCase + +@property (nonatomic, strong) XMPPMessageCoreDataStorage *storage; + +@end + +@implementation XMPPMessageCoreDataStorageTests + +- (void)setUp +{ + [super setUp]; + + self.storage = [[XMPPMessageCoreDataStorage alloc] initWithDatabaseFilename:NSStringFromSelector(self.invocation.selector) + storeOptions:nil]; + self.storage.autoRemovePreviousDatabaseFile = YES; +} + +- (void)testMessageTransientPropertyDirectUpdates +{ + XMPPMessageCoreDataStorageObject *message = + [XMPPMessageCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + message.fromJID = [XMPPJID jidWithString:@"user1@domain1/resource1"]; + message.toJID = [XMPPJID jidWithString:@"user2@domain2/resource2"]; + + [self.storage.mainThreadManagedObjectContext save:NULL]; + [self.storage.mainThreadManagedObjectContext refreshObject:message mergeChanges:NO]; + + XCTAssertEqualObjects(message.fromJID, [XMPPJID jidWithString:@"user1@domain1/resource1"]); + XCTAssertEqualObjects(message.toJID, [XMPPJID jidWithString:@"user2@domain2/resource2"]); +} + +- (void)testMessageTransientPropertyMergeUpdates +{ + XMPPMessageCoreDataStorageObject *message = + [XMPPMessageCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + message.fromJID = [XMPPJID jidWithString:@"user1@domain1/resource1"]; + message.toJID = [XMPPJID jidWithString:@"user2@domain2/resource2"]; + + [self.storage.mainThreadManagedObjectContext save:NULL]; + + [self expectationForMainThreadStorageManagedObjectsChangeNotificationWithUserInfoKey:NSRefreshedObjectsKey count:1 handler: + ^BOOL(__kindof NSManagedObject *object) { + return [object isKindOfClass:[XMPPMessageCoreDataStorageObject class]]; + }]; + + [self.storage scheduleBlock:^{ + XMPPMessageCoreDataStorageObject *storageMessage = [self.storage.managedObjectContext objectWithID:message.objectID]; + storageMessage.fromJID = [XMPPJID jidWithString:@"user1a@domain1a/resource1a"]; + storageMessage.toJID = [XMPPJID jidWithString:@"user2a@domain2a/resource2a"]; + [self.storage save]; + }]; + + [self waitForExpectationsWithTimeout:5 handler:^(NSError * _Nullable error) { + XCTAssert([message.fromJID isEqualToJID:[XMPPJID jidWithString:@"user1a@domain1a/resource1a"]]); + XCTAssert([message.toJID isEqualToJID:[XMPPJID jidWithString:@"user2a@domain2a/resource2a"]]); + }]; +} + +- (void)testMessageTransientPropertyKeyValueObserving +{ + XMPPMessageCoreDataStorageObject *message = + [XMPPMessageCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + + [self keyValueObservingExpectationForObject:message + keyPath:NSStringFromSelector(@selector(fromJID)) + expectedValue:[XMPPJID jidWithString:@"user1@domain1/resource1"]]; + [self keyValueObservingExpectationForObject:message + keyPath:NSStringFromSelector(@selector(toJID)) + expectedValue:[XMPPJID jidWithString:@"user2@domain2/resource2"]]; + + message.fromJID = [XMPPJID jidWithString:@"user1@domain1/resource1"]; + message.toJID = [XMPPJID jidWithString:@"user2@domain2/resource2"]; + + [self waitForExpectationsWithTimeout:0 handler:nil]; +} + +- (void)testIncomingMessageRegistration +{ + NSDictionary *messageTypes = @{@"chat": @(XMPPMessageTypeChat), + @"error": @(XMPPMessageTypeError), + @"groupchat": @(XMPPMessageTypeGroupchat), + @"headline": @(XMPPMessageTypeHeadline), + @"normal": @(XMPPMessageTypeNormal)}; + + for (NSString *typeString in messageTypes) { + NSMutableString *messageString = [NSMutableString string]; + [messageString appendFormat: @"", typeString]; + [messageString appendString: @" body"]; + [messageString appendString: @" subject"]; + [messageString appendString: @" thread"]; + [messageString appendString: @""]; + + XMPPMessageCoreDataStorageObject *message = + [XMPPMessageCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + message.direction = XMPPMessageDirectionIncoming; + [message registerIncomingMessageStreamEventID:[NSString stringWithFormat:@"eventID_%@", typeString] + streamJID:[XMPPJID jidWithString:@"user2@domain2/resource2"] + streamEventTimestamp:[NSDate dateWithTimeIntervalSinceReferenceDate:0]]; + [message registerIncomingMessageCore:[[XMPPMessage alloc] initWithXMLString:messageString error:NULL]]; + + XCTAssertEqualObjects(message.fromJID, [XMPPJID jidWithString:@"user1@domain1/resource1"]); + XCTAssertEqualObjects(message.toJID, [XMPPJID jidWithString:@"user2@domain2/resource2"]); + XCTAssertEqualObjects(message.body, @"body"); + XCTAssertEqualObjects(message.stanzaID, @"messageID"); + XCTAssertEqualObjects(message.subject, @"subject"); + XCTAssertEqualObjects(message.thread, @"thread"); + XCTAssertEqual(message.type, messageTypes[typeString].intValue); + XCTAssertEqualObjects([message streamJID], [XMPPJID jidWithString:@"user2@domain2/resource2"]); + XCTAssertEqualObjects([message streamTimestamp], [NSDate dateWithTimeIntervalSinceReferenceDate:0]); + } +} + +- (void)testOutgoingMessageRegistration +{ + XMPPMessageCoreDataStorageObject *message = + [XMPPMessageCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + message.direction = XMPPMessageDirectionOutgoing; + [message registerOutgoingMessageStreamEventID:@"outgoingMessageEventID"]; + + XMPPMessageCoreDataStorageObject *foundMessage = [XMPPMessageCoreDataStorageObject findWithStreamEventID:@"outgoingMessageEventID" + inManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + + XCTAssertEqualObjects(message, foundMessage); +} + +- (void)testSentMessageRegistration +{ + XMPPMessageCoreDataStorageObject *message = + [XMPPMessageCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + message.direction = XMPPMessageDirectionOutgoing; + [message registerOutgoingMessageStreamEventID:@"outgoingMessageEventID"]; + [message registerOutgoingMessageStreamJID:[XMPPJID jidWithString:@"user@domain/resource"] + streamEventTimestamp:[NSDate dateWithTimeIntervalSinceReferenceDate:0]]; + + XCTAssertEqualObjects([message streamJID], [XMPPJID jidWithString:@"user@domain/resource"]); + XCTAssertEqualObjects([message streamTimestamp], [NSDate dateWithTimeIntervalSinceReferenceDate:0]); +} + +- (void)testRepeatedSentMessageRegistration +{ + XMPPMessageCoreDataStorageObject *message = + [XMPPMessageCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + message.direction = XMPPMessageDirectionOutgoing; + [message registerOutgoingMessageStreamEventID:@"initialEventID"]; + [message registerOutgoingMessageStreamJID:[XMPPJID jidWithString:@"user1@domain1/resource1"] + streamEventTimestamp:[NSDate dateWithTimeIntervalSinceReferenceDate:0]]; + [message registerOutgoingMessageStreamEventID:@"subsequentEventID"]; + [message registerOutgoingMessageStreamJID:[XMPPJID jidWithString:@"user2@domain2/resource2"] + streamEventTimestamp:[NSDate dateWithTimeIntervalSinceReferenceDate:1]]; + + XCTAssertEqualObjects([message streamJID], [XMPPJID jidWithString:@"user2@domain2/resource2"]); + XCTAssertEqualObjects([message streamTimestamp], [NSDate dateWithTimeIntervalSinceReferenceDate:1]); +} + +- (void)testRetiredSentMessageRegistration +{ + XMPPMessageCoreDataStorageObject *message = + [XMPPMessageCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + message.direction = XMPPMessageDirectionOutgoing; + [message registerOutgoingMessageStreamEventID:@"eventID"]; + [message retireStreamTimestamp]; + [message registerOutgoingMessageStreamJID:[XMPPJID jidWithString:@"user@domain/resource"] + streamEventTimestamp:[NSDate dateWithTimeIntervalSinceReferenceDate:0]]; + + XCTAssertEqualObjects([message streamJID], [XMPPJID jidWithString:@"user@domain/resource"]); + XCTAssertEqualObjects([message streamTimestamp], [NSDate dateWithTimeIntervalSinceReferenceDate:0]); +} + +- (void)testBasicStreamTimestampMessageContextFetch +{ + XMPPMessageCoreDataStorageObject *firstMessage = + [XMPPMessageCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + firstMessage.direction = XMPPMessageDirectionIncoming; + [firstMessage registerIncomingMessageStreamEventID:@"firstMessageEventID" + streamJID:[XMPPJID jidWithString:@"user@domain/resource"] + streamEventTimestamp:[NSDate dateWithTimeIntervalSinceReferenceDate:0]]; + + XMPPMessageCoreDataStorageObject *secondMessage = + [XMPPMessageCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + secondMessage.direction = XMPPMessageDirectionIncoming; + [secondMessage registerIncomingMessageStreamEventID:@"secondMessageEventID" + streamJID:[XMPPJID jidWithString:@"user@domain/resource"] + streamEventTimestamp:[NSDate dateWithTimeIntervalSinceReferenceDate:1]]; + + NSFetchRequest *fetchRequest = + [XMPPMessageContextItemCoreDataStorageObject requestByTimestampsWithPredicate:[XMPPMessageContextItemCoreDataStorageObject streamTimestampKindPredicate] + inAscendingOrder:YES + fromManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + NSArray *result = [self.storage.mainThreadManagedObjectContext executeFetchRequest:fetchRequest error:NULL]; + + XCTAssertEqual(result.count, 2); + XCTAssertEqualObjects(result[0].message, firstMessage); + XCTAssertEqualObjects(result[1].message, secondMessage); +} + +- (void)testRetiredStreamTimestampMessageContextFetch +{ + XMPPMessageCoreDataStorageObject *message = + [XMPPMessageCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + message.direction = XMPPMessageDirectionOutgoing; + [message registerOutgoingMessageStreamEventID:@"retiredMessageEventID"]; + [message registerOutgoingMessageStreamJID:[XMPPJID jidWithString:@"user@domain/resource"] + streamEventTimestamp:[NSDate dateWithTimeIntervalSinceReferenceDate:0]]; + [message registerOutgoingMessageStreamEventID:@"retiringMessageEventID"]; + [message registerOutgoingMessageStreamJID:[XMPPJID jidWithString:@"user@domain/resource"] + streamEventTimestamp:[NSDate dateWithTimeIntervalSinceReferenceDate:1]]; + + NSFetchRequest *fetchRequest = + [XMPPMessageContextItemCoreDataStorageObject requestByTimestampsWithPredicate:[XMPPMessageContextItemCoreDataStorageObject streamTimestampKindPredicate] + inAscendingOrder:YES + fromManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + NSArray *result = [self.storage.mainThreadManagedObjectContext executeFetchRequest:fetchRequest error:NULL]; + + XCTAssertEqual(result.count, 1); + XCTAssertEqualObjects([result[0].message streamTimestamp], [NSDate dateWithTimeIntervalSinceReferenceDate:1]); +} + +- (void)testRelevantMessageJIDContextFetch +{ + XMPPMessageCoreDataStorageObject *incomingMessage = + [XMPPMessageCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + incomingMessage.direction = XMPPMessageDirectionIncoming; + [incomingMessage registerIncomingMessageStreamEventID:@"incomingMessageEventID" + streamJID:[XMPPJID jidWithString:@"user1@domain1/resource1"] + streamEventTimestamp:[NSDate dateWithTimeIntervalSinceReferenceDate:0]]; + [incomingMessage registerIncomingMessageCore:[[XMPPMessage alloc] initWithXMLString:@"" error:NULL]]; + + XMPPMessageCoreDataStorageObject *outgoingMessage = + [XMPPMessageCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + outgoingMessage.direction = XMPPMessageDirectionOutgoing; + outgoingMessage.toJID = [XMPPJID jidWithString:@"user2@domain2/resource2"]; + [outgoingMessage registerOutgoingMessageStreamEventID:@"outgoingMessageEventID"]; + [outgoingMessage registerOutgoingMessageStreamJID:[XMPPJID jidWithString:@"user1@domain1/resource1"] + streamEventTimestamp:[NSDate dateWithTimeIntervalSinceReferenceDate:1]]; + + NSPredicate *fromJIDPredicate = + [XMPPMessageContextItemCoreDataStorageObject messageFromJIDPredicateWithValue:[XMPPJID jidWithString:@"user2@domain2/resource2"] + compareOptions:XMPPJIDCompareFull]; + NSFetchRequest *fromJIDFetchRequest = + [XMPPMessageContextItemCoreDataStorageObject requestByTimestampsWithPredicate:fromJIDPredicate + inAscendingOrder:YES + fromManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + NSArray *fromJIDResult = + [self.storage.mainThreadManagedObjectContext executeFetchRequest:fromJIDFetchRequest error:NULL]; + + NSPredicate *toJIDPredicate = [XMPPMessageContextItemCoreDataStorageObject messageToJIDPredicateWithValue:[XMPPJID jidWithString:@"user2@domain2/resource2"] + compareOptions:XMPPJIDCompareFull]; + NSFetchRequest *toJIDFetchRequest = + [XMPPMessageContextItemCoreDataStorageObject requestByTimestampsWithPredicate:toJIDPredicate + inAscendingOrder:YES + fromManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + NSArray *toJIDResult = + [self.storage.mainThreadManagedObjectContext executeFetchRequest:toJIDFetchRequest error:NULL]; + + NSPredicate *remotePartyJIDPredicate = + [XMPPMessageContextItemCoreDataStorageObject messageRemotePartyJIDPredicateWithValue:[XMPPJID jidWithString:@"user2@domain2/resource2"] + compareOptions:XMPPJIDCompareFull]; + NSFetchRequest *remotePartyJIDFetchRequest = + [XMPPMessageContextItemCoreDataStorageObject requestByTimestampsWithPredicate:remotePartyJIDPredicate + inAscendingOrder:YES + fromManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + NSArray *remotePartyJIDResult = + [self.storage.mainThreadManagedObjectContext executeFetchRequest:remotePartyJIDFetchRequest error:NULL]; + + XCTAssertEqual(fromJIDResult.count, 1); + XCTAssertEqualObjects(fromJIDResult[0].message, incomingMessage); + + XCTAssertEqual(toJIDResult.count, 1); + XCTAssertEqualObjects(toJIDResult[0].message, outgoingMessage); + + XCTAssertEqual(remotePartyJIDResult.count, 2); + XCTAssertEqualObjects(remotePartyJIDResult[0].message, incomingMessage); + XCTAssertEqualObjects(remotePartyJIDResult[1].message, outgoingMessage); +} + +- (void)testTimestampRangeContextFetch +{ + XMPPMessageCoreDataStorageObject *message = + [XMPPMessageCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + message.direction = XMPPMessageDirectionIncoming; + [message registerIncomingMessageStreamEventID:@"eventID" + streamJID:[XMPPJID jidWithString:@"user@domain/resource"] + streamEventTimestamp:[NSDate dateWithTimeIntervalSinceReferenceDate:0]]; + + NSPredicate *startEndPredicate = + [XMPPMessageContextItemCoreDataStorageObject timestampRangePredicateWithStartValue:[NSDate dateWithTimeIntervalSinceReferenceDate:-1] + endValue:[NSDate dateWithTimeIntervalSinceReferenceDate:1]]; + NSFetchRequest *startEndFetchRequest = + [XMPPMessageContextItemCoreDataStorageObject requestByTimestampsWithPredicate:startEndPredicate + inAscendingOrder:YES + fromManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + NSArray *startEndResult = + [self.storage.mainThreadManagedObjectContext executeFetchRequest:startEndFetchRequest error:NULL]; + + NSPredicate *startEndEdgeCasePredicate = + [XMPPMessageContextItemCoreDataStorageObject timestampRangePredicateWithStartValue:[NSDate dateWithTimeIntervalSinceReferenceDate:0] + endValue:[NSDate dateWithTimeIntervalSinceReferenceDate:0]]; + NSFetchRequest *startEndEdgeCaseFetchRequest = + [XMPPMessageContextItemCoreDataStorageObject requestByTimestampsWithPredicate:startEndEdgeCasePredicate + inAscendingOrder:YES + fromManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + NSArray *startEndEdgeCaseResult = + [self.storage.mainThreadManagedObjectContext executeFetchRequest:startEndEdgeCaseFetchRequest error:NULL]; + + NSPredicate *startPredicate = + [XMPPMessageContextItemCoreDataStorageObject timestampRangePredicateWithStartValue:[NSDate dateWithTimeIntervalSinceReferenceDate:-1] + endValue:nil]; + NSFetchRequest *startFetchRequest = + [XMPPMessageContextItemCoreDataStorageObject requestByTimestampsWithPredicate:startPredicate + inAscendingOrder:YES + fromManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + NSArray *startResult = + [self.storage.mainThreadManagedObjectContext executeFetchRequest:startFetchRequest error:NULL]; + + NSPredicate *startEdgeCasePredicate = + [XMPPMessageContextItemCoreDataStorageObject timestampRangePredicateWithStartValue:[NSDate dateWithTimeIntervalSinceReferenceDate:0] + endValue:nil]; + NSFetchRequest *startEdgeCaseFetchRequest = + [XMPPMessageContextItemCoreDataStorageObject requestByTimestampsWithPredicate:startEdgeCasePredicate + inAscendingOrder:YES + fromManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + NSArray *startEdgeCaseResult = + [self.storage.mainThreadManagedObjectContext executeFetchRequest:startEdgeCaseFetchRequest error:NULL]; + + NSPredicate *endPredicate = + [XMPPMessageContextItemCoreDataStorageObject timestampRangePredicateWithStartValue:nil + endValue:[NSDate dateWithTimeIntervalSinceReferenceDate:1]]; + NSFetchRequest *endFetchRequest = + [XMPPMessageContextItemCoreDataStorageObject requestByTimestampsWithPredicate:endPredicate + inAscendingOrder:YES + fromManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + NSArray *endResult = + [self.storage.mainThreadManagedObjectContext executeFetchRequest:endFetchRequest error:NULL]; + + NSPredicate *endEdgeCasePredicate = + [XMPPMessageContextItemCoreDataStorageObject timestampRangePredicateWithStartValue:nil + endValue:[NSDate dateWithTimeIntervalSinceReferenceDate:0]]; + NSFetchRequest *endEdgeCaseFetchRequest = + [XMPPMessageContextItemCoreDataStorageObject requestByTimestampsWithPredicate:endEdgeCasePredicate + inAscendingOrder:YES + fromManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + NSArray *endEdgeCaseResult = + [self.storage.mainThreadManagedObjectContext executeFetchRequest:endEdgeCaseFetchRequest error:NULL]; + + NSPredicate *missPredicate = + [XMPPMessageContextItemCoreDataStorageObject timestampRangePredicateWithStartValue:[NSDate dateWithTimeIntervalSinceReferenceDate:1] + endValue:[NSDate dateWithTimeIntervalSinceReferenceDate:2]]; + NSFetchRequest *missFetchRequest = + [XMPPMessageContextItemCoreDataStorageObject requestByTimestampsWithPredicate:missPredicate + inAscendingOrder:YES + fromManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + NSArray *missResult = + [self.storage.mainThreadManagedObjectContext executeFetchRequest:missFetchRequest error:NULL]; + + XCTAssertEqual(startEndResult.count, 1); + XCTAssertEqualObjects(startEndResult[0].message, message); + XCTAssertEqual(startEndEdgeCaseResult.count, 1); + XCTAssertEqualObjects(startEndEdgeCaseResult[0].message, message); + + XCTAssertEqual(startResult.count, 1); + XCTAssertEqualObjects(startResult[0].message, message); + XCTAssertEqual(startEdgeCaseResult.count, 1); + XCTAssertEqualObjects(startEdgeCaseResult[0].message, message); + + XCTAssertEqual(endResult.count, 1); + XCTAssertEqualObjects(endResult[0].message, message); + XCTAssertEqual(endEdgeCaseResult.count, 1); + XCTAssertEqualObjects(endEdgeCaseResult[0].message, message); + + XCTAssertEqual(missResult.count, 0); +} + +- (void)testMessageSubjectContextFetch +{ + XMPPMessageCoreDataStorageObject *matchingMessage = + [XMPPMessageCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + matchingMessage.direction = XMPPMessageDirectionIncoming; + [matchingMessage registerIncomingMessageStreamEventID:@"matchingMessageEventID" + streamJID:[XMPPJID jidWithString:@"user@domain/resource"] + streamEventTimestamp:[NSDate dateWithTimeIntervalSinceReferenceDate:0]]; + matchingMessage.subject = @"I implore you!"; + + XMPPMessageCoreDataStorageObject *otherMessage = + [XMPPMessageCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + otherMessage.direction = XMPPMessageDirectionIncoming; + [otherMessage registerIncomingMessageStreamEventID:@"otherMessageEventID" + streamJID:[XMPPJID jidWithString:@"user@domain/resource"] + streamEventTimestamp:[NSDate dateWithTimeIntervalSinceReferenceDate:1]]; + + XMPPMessageContentCompareOptions options = XMPPMessageContentCompareCaseInsensitive|XMPPMessageContentCompareDiacriticInsensitive; + + NSPredicate *equalityPredicate = [XMPPMessageContextItemCoreDataStorageObject messageSubjectPredicateWithValue:@"I implore you!" + compareOperator:XMPPMessageContentCompareOperatorEquals + options:options]; + NSPredicate *prefixPredicate = [XMPPMessageContextItemCoreDataStorageObject messageSubjectPredicateWithValue:@"i implore" + compareOperator:XMPPMessageContentCompareOperatorBeginsWith + options:options]; + NSPredicate *containmentPredicate = + [XMPPMessageContextItemCoreDataStorageObject messageSubjectPredicateWithValue:@"implore" + compareOperator:XMPPMessageContentCompareOperatorContains + options:options]; + NSPredicate *suffixPredicate = [XMPPMessageContextItemCoreDataStorageObject messageSubjectPredicateWithValue:@"you!" + compareOperator:XMPPMessageContentCompareOperatorEndsWith + options:options]; + NSPredicate *likePredicate = [XMPPMessageContextItemCoreDataStorageObject messageSubjectPredicateWithValue:@"I implore *!" + compareOperator:XMPPMessageContentCompareOperatorLike + options:options]; + NSPredicate *matchPredicate = [XMPPMessageContextItemCoreDataStorageObject messageSubjectPredicateWithValue:@"I implore .*!" + compareOperator:XMPPMessageContentCompareOperatorMatches + options:options]; + + for (NSPredicate *predicate in @[equalityPredicate, prefixPredicate, containmentPredicate, suffixPredicate, likePredicate, matchPredicate]) { + NSFetchRequest *fetchRequest = + [XMPPMessageContextItemCoreDataStorageObject requestByTimestampsWithPredicate:predicate + inAscendingOrder:YES + fromManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + NSArray *result = [self.storage.mainThreadManagedObjectContext executeFetchRequest:fetchRequest + error:NULL]; + + XCTAssertEqual(result.count, 1); + XCTAssertEqualObjects(result[0].message, matchingMessage); + } +} + +- (void)testMessageBodyContextFetch +{ + XMPPMessageCoreDataStorageObject *matchingMessage = + [XMPPMessageCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + matchingMessage.direction = XMPPMessageDirectionIncoming; + [matchingMessage registerIncomingMessageStreamEventID:@"matchingMessageEventID" + streamJID:[XMPPJID jidWithString:@"user@domain/resource"] + streamEventTimestamp:[NSDate dateWithTimeIntervalSinceReferenceDate:0]]; + matchingMessage.body = @"Wherefore art thou, Romeo?"; + + XMPPMessageCoreDataStorageObject *otherMessage = + [XMPPMessageCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + otherMessage.direction = XMPPMessageDirectionIncoming; + [otherMessage registerIncomingMessageStreamEventID:@"otherMessageEventID" + streamJID:[XMPPJID jidWithString:@"user@domain/resource"] + streamEventTimestamp:[NSDate dateWithTimeIntervalSinceReferenceDate:1]]; + + XMPPMessageContentCompareOptions options = XMPPMessageContentCompareCaseInsensitive|XMPPMessageContentCompareDiacriticInsensitive; + + NSPredicate *equalityPredicate = [XMPPMessageContextItemCoreDataStorageObject messageBodyPredicateWithValue:@"Wherefore art thou, Romeo?" + compareOperator:XMPPMessageContentCompareOperatorEquals + options:options]; + NSPredicate *prefixPredicate = [XMPPMessageContextItemCoreDataStorageObject messageBodyPredicateWithValue:@"wherefore" + compareOperator:XMPPMessageContentCompareOperatorBeginsWith + options:options]; + NSPredicate *containmentPredicate = [XMPPMessageContextItemCoreDataStorageObject messageBodyPredicateWithValue:@"art thou" + compareOperator:XMPPMessageContentCompareOperatorContains + options:options]; + NSPredicate *suffixPredicate = [XMPPMessageContextItemCoreDataStorageObject messageBodyPredicateWithValue:@"romeo?" + compareOperator:XMPPMessageContentCompareOperatorEndsWith + options:options]; + NSPredicate *likePredicate = [XMPPMessageContextItemCoreDataStorageObject messageBodyPredicateWithValue:@"Wherefore art thou, *" + compareOperator:XMPPMessageContentCompareOperatorLike + options:options]; + NSPredicate *matchPredicate = [XMPPMessageContextItemCoreDataStorageObject messageBodyPredicateWithValue:@"Wherefore art thou, .*" + compareOperator:XMPPMessageContentCompareOperatorMatches + options:options]; + + for (NSPredicate *predicate in @[equalityPredicate, prefixPredicate, containmentPredicate, suffixPredicate, likePredicate, matchPredicate]) { + NSFetchRequest *fetchRequest = + [XMPPMessageContextItemCoreDataStorageObject requestByTimestampsWithPredicate:predicate + inAscendingOrder:YES + fromManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + NSArray *result = [self.storage.mainThreadManagedObjectContext executeFetchRequest:fetchRequest + error:NULL]; + + XCTAssertEqual(result.count, 1); + XCTAssertEqualObjects(result[0].message, matchingMessage); + } +} + +- (void)testMessageThreadContextFetch +{ + XMPPMessageCoreDataStorageObject *matchingMessage = + [XMPPMessageCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + matchingMessage.direction = XMPPMessageDirectionIncoming; + [matchingMessage registerIncomingMessageStreamEventID:@"matchingMessageEventID" + streamJID:[XMPPJID jidWithString:@"user@domain/resource"] + streamEventTimestamp:[NSDate dateWithTimeIntervalSinceReferenceDate:0]]; + matchingMessage.thread = @"e0ffe42b28561960c6b12b944a092794b9683a38"; + + XMPPMessageCoreDataStorageObject *otherMessage = + [XMPPMessageCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + otherMessage.direction = XMPPMessageDirectionIncoming; + [otherMessage registerIncomingMessageStreamEventID:@"otherMessageEventID" + streamJID:[XMPPJID jidWithString:@"user@domain/resource"] + streamEventTimestamp:[NSDate dateWithTimeIntervalSinceReferenceDate:1]]; + + NSPredicate *predicate = [XMPPMessageContextItemCoreDataStorageObject messageThreadPredicateWithValue:@"e0ffe42b28561960c6b12b944a092794b9683a38"]; + NSFetchRequest *fetchRequest = + [XMPPMessageContextItemCoreDataStorageObject requestByTimestampsWithPredicate:predicate + inAscendingOrder:YES + fromManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + NSArray *result = [self.storage.mainThreadManagedObjectContext executeFetchRequest:fetchRequest error:NULL]; + + XCTAssertEqual(result.count, 1); + XCTAssertEqualObjects(result[0].message, matchingMessage); +} + +- (void)testMessageTypeContextFetch +{ + NSArray *messageTypes = @[@(XMPPMessageTypeChat), + @(XMPPMessageTypeError), + @(XMPPMessageTypeGroupchat), + @(XMPPMessageTypeHeadline), + @(XMPPMessageTypeNormal)]; + for (NSNumber *typeNumber in messageTypes) { + XMPPMessageCoreDataStorageObject *message = + [XMPPMessageCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + message.direction = XMPPMessageDirectionIncoming; + [message registerIncomingMessageStreamEventID:[NSString stringWithFormat:@"message%@EventID", typeNumber] + streamJID:[XMPPJID jidWithString:@"user@domain/resource"] + streamEventTimestamp:[NSDate dateWithTimeIntervalSinceReferenceDate:0]]; + message.stanzaID = [NSString stringWithFormat:@"message%@ID", typeNumber]; + message.type = typeNumber.integerValue; + } + + for (NSNumber *typeNumber in messageTypes) { + NSPredicate *predicate = [XMPPMessageContextItemCoreDataStorageObject messageTypePredicateWithValue:typeNumber.integerValue]; + NSFetchRequest *fetchRequest = + [XMPPMessageContextItemCoreDataStorageObject requestByTimestampsWithPredicate:predicate + inAscendingOrder:YES + fromManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + NSArray *result = [self.storage.mainThreadManagedObjectContext executeFetchRequest:fetchRequest error:NULL]; + + XCTAssertEqual(result.count, 1); + XCTAssertEqualObjects(result[0].message.stanzaID, ([NSString stringWithFormat:@"message%@ID", typeNumber])); + } +} + +- (void)testCoreMessageCreation +{ + XMPPMessageCoreDataStorageObject *message = + [XMPPMessageCoreDataStorageObject xmpp_insertNewObjectInManagedObjectContext:self.storage.mainThreadManagedObjectContext]; + message.toJID = [XMPPJID jidWithString:@"user2@domain2/resource2"]; + message.body = @"body"; + message.stanzaID = @"messageID"; + message.subject = @"subject"; + message.thread = @"thread"; + + NSDictionary *messageTypes = @{@"chat": @(XMPPMessageTypeChat), + @"error": @(XMPPMessageTypeError), + @"groupchat": @(XMPPMessageTypeGroupchat), + @"headline": @(XMPPMessageTypeHeadline), + @"normal": @(XMPPMessageTypeNormal)}; + + for (NSString *typeString in messageTypes){ + message.type = messageTypes[typeString].intValue; + + XMPPMessage *xmppMessage = [message coreMessage]; + + XCTAssertEqualObjects([xmppMessage to], [XMPPJID jidWithString:@"user2@domain2/resource2"]); + XCTAssertEqualObjects([xmppMessage body], @"body"); + XCTAssertEqualObjects([xmppMessage elementID], @"messageID"); + XCTAssertEqualObjects([xmppMessage subject], @"subject"); + XCTAssertEqualObjects([xmppMessage thread], @"thread"); + XCTAssertEqualObjects([xmppMessage type], typeString); + } +} + +- (XCTestExpectation *)expectationForMainThreadStorageManagedObjectsChangeNotificationWithUserInfoKey:(NSString *)userInfoKey count:(NSInteger)expectedObjectCount handler:(BOOL (^)(__kindof NSManagedObject *object))handler +{ + return [self expectationForNotification:NSManagedObjectContextObjectsDidChangeNotification object:self.storage.mainThreadManagedObjectContext handler: + ^BOOL(NSNotification * _Nonnull notification) { + return [notification.userInfo[userInfoKey] objectsPassingTest:^BOOL(id _Nonnull obj, BOOL * _Nonnull stop) { + return handler ? handler(obj) : YES; + }].count == expectedObjectCount; + }]; +} + +@end diff --git a/Xcode/Testing-iOS/XMPPFrameworkTests.xcodeproj/project.pbxproj b/Xcode/Testing-iOS/XMPPFrameworkTests.xcodeproj/project.pbxproj index 393a79e644..c34b137a77 100644 --- a/Xcode/Testing-iOS/XMPPFrameworkTests.xcodeproj/project.pbxproj +++ b/Xcode/Testing-iOS/XMPPFrameworkTests.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ D99C5E0E1D99C48100FB068A /* OMEMOTestStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = D99C5E0C1D99C48100FB068A /* OMEMOTestStorage.m */; }; D9E35E701D90B894002E7CF7 /* OMEMOElementTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D9E35E6F1D90B894002E7CF7 /* OMEMOElementTests.m */; }; DD1E732C1ED86B7D009B529B /* XMPPPubSubTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DD1E732B1ED86B7D009B529B /* XMPPPubSubTests.m */; }; + DD3559711F3CA50C000D25BA /* XMPPMessageCoreDataStorageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DD3559701F3CA50C000D25BA /* XMPPMessageCoreDataStorageTests.m */; }; FDD2AB232C05507F2045FFFC /* Pods_XMPPFrameworkTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD0B17267211A912DE2098E /* Pods_XMPPFrameworkTests.framework */; }; /* End PBXBuildFile section */ @@ -55,6 +56,7 @@ D99C5E0C1D99C48100FB068A /* OMEMOTestStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OMEMOTestStorage.m; path = "../../Testing-Shared/OMEMOTestStorage.m"; sourceTree = ""; }; D9E35E6F1D90B894002E7CF7 /* OMEMOElementTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OMEMOElementTests.m; path = "../../Testing-Shared/OMEMOElementTests.m"; sourceTree = ""; }; DD1E732B1ED86B7D009B529B /* XMPPPubSubTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XMPPPubSubTests.m; path = "../../Testing-Shared/XMPPPubSubTests.m"; sourceTree = ""; }; + DD3559701F3CA50C000D25BA /* XMPPMessageCoreDataStorageTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XMPPMessageCoreDataStorageTests.m; path = "../../Testing-Shared/XMPPMessageCoreDataStorageTests.m"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -108,6 +110,7 @@ D973A07A1D2F18040096F3ED /* XMPPURITests.m */, D973A07B1D2F18040096F3ED /* XMPPvCardTests.m */, DD1E732B1ED86B7D009B529B /* XMPPPubSubTests.m */, + DD3559701F3CA50C000D25BA /* XMPPMessageCoreDataStorageTests.m */, 63F50D971C60208200CA0201 /* Info.plist */, D973A06E1D2F18030096F3ED /* XMPPFrameworkTests-Bridging-Header.h */, ); @@ -263,6 +266,7 @@ D973A07E1D2F18040096F3ED /* XMPPHTTPFileUploadTests.m in Sources */, D973A0821D2F18040096F3ED /* XMPPPushTests.swift in Sources */, D9E35E701D90B894002E7CF7 /* OMEMOElementTests.m in Sources */, + DD3559711F3CA50C000D25BA /* XMPPMessageCoreDataStorageTests.m in Sources */, D973A0851D2F18040096F3ED /* XMPPStorageHintTests.m in Sources */, D973A0891D2F18310096F3ED /* XMPPSwift.swift in Sources */, DD1E732C1ED86B7D009B529B /* XMPPPubSubTests.m in Sources */, diff --git a/Xcode/Testing-macOS/XMPPFrameworkTests.xcodeproj/project.pbxproj b/Xcode/Testing-macOS/XMPPFrameworkTests.xcodeproj/project.pbxproj index 3c8030996f..391d4571d4 100644 --- a/Xcode/Testing-macOS/XMPPFrameworkTests.xcodeproj/project.pbxproj +++ b/Xcode/Testing-macOS/XMPPFrameworkTests.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ D99C5E091D95EBA100FB068A /* OMEMOTestStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = D99C5E081D95EBA100FB068A /* OMEMOTestStorage.m */; }; D9E35E6E1D90B2C5002E7CF7 /* OMEMOElementTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D9E35E6D1D90B2C5002E7CF7 /* OMEMOElementTests.m */; }; D9F20D011D836080002A8D6F /* OMEMOModuleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D9F20D001D836080002A8D6F /* OMEMOModuleTests.m */; }; + DD24E0031F70F31300FA813C /* XMPPMessageCoreDataStorageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DD24E0021F70F31300FA813C /* XMPPMessageCoreDataStorageTests.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -52,6 +53,7 @@ D99C5E081D95EBA100FB068A /* OMEMOTestStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OMEMOTestStorage.m; path = "../../Testing-Shared/OMEMOTestStorage.m"; sourceTree = ""; }; D9E35E6D1D90B2C5002E7CF7 /* OMEMOElementTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OMEMOElementTests.m; path = "../../Testing-Shared/OMEMOElementTests.m"; sourceTree = ""; }; D9F20D001D836080002A8D6F /* OMEMOModuleTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OMEMOModuleTests.m; path = "../../Testing-Shared/OMEMOModuleTests.m"; sourceTree = ""; }; + DD24E0021F70F31300FA813C /* XMPPMessageCoreDataStorageTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XMPPMessageCoreDataStorageTests.m; path = "../../Testing-Shared/XMPPMessageCoreDataStorageTests.m"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -108,6 +110,7 @@ D973A0A11D2F1EF60096F3ED /* XMPPSwift.swift */, D973A0A21D2F1EF60096F3ED /* XMPPURITests.m */, D973A0A31D2F1EF60096F3ED /* XMPPvCardTests.m */, + DD24E0021F70F31300FA813C /* XMPPMessageCoreDataStorageTests.m */, D973A0921D2F1EB10096F3ED /* Info.plist */, ); path = XMPPFrameworkTests; @@ -246,6 +249,7 @@ buildActionMask = 2147483647; files = ( D973A0A41D2F1EF60096F3ED /* CapabilitiesHashingTest.m in Sources */, + DD24E0031F70F31300FA813C /* XMPPMessageCoreDataStorageTests.m in Sources */, D9F20D011D836080002A8D6F /* OMEMOModuleTests.m in Sources */, D973A0A91D2F1EF60096F3ED /* XMPPMUCLightTests.m in Sources */, D973A0A51D2F1EF60096F3ED /* EncodeDecodeTest.m in Sources */,