mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			242 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Matlab
		
	
		
		
			
		
	
	
			242 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Matlab
		
	
| 
											5 years ago
										 | // | ||
|  | //  Copyright (c) 2018 Open Whisper Systems. All rights reserved. | ||
|  | // | ||
|  | 
 | ||
|  | #import "OWSDisappearingMessagesFinder.h" | ||
|  | #import "OWSPrimaryStorage.h" | ||
|  | #import "TSIncomingMessage.h" | ||
|  | #import "TSMessage.h" | ||
|  | #import "TSOutgoingMessage.h" | ||
|  | #import "TSThread.h" | ||
| 
											5 years ago
										 | #import <SignalCoreKit/NSDate+OWS.h> | ||
| 
											5 years ago
										 | #import <YapDatabase/YapDatabase.h> | ||
|  | #import <YapDatabase/YapDatabaseQuery.h> | ||
|  | #import <YapDatabase/YapDatabaseSecondaryIndex.h> | ||
|  | 
 | ||
|  | NS_ASSUME_NONNULL_BEGIN | ||
|  | 
 | ||
|  | static NSString *const OWSDisappearingMessageFinderThreadIdColumn = @"thread_id"; | ||
|  | static NSString *const OWSDisappearingMessageFinderExpiresAtColumn = @"expires_at"; | ||
|  | static NSString *const OWSDisappearingMessageFinderExpiresAtIndex = @"index_messages_on_expires_at_and_thread_id_v2"; | ||
|  | 
 | ||
|  | @implementation OWSDisappearingMessagesFinder | ||
|  | 
 | ||
|  | - (NSArray<NSString *> *)fetchUnstartedExpiringMessageIdsInThread:(TSThread *)thread | ||
|  |                                                       transaction:(YapDatabaseReadTransaction *_Nonnull)transaction | ||
|  | { | ||
|  |     NSMutableArray<NSString *> *messageIds = [NSMutableArray new]; | ||
|  |     NSString *formattedString = [NSString stringWithFormat:@"WHERE %@ = 0 AND %@ = \"%@\"", | ||
|  |                                           OWSDisappearingMessageFinderExpiresAtColumn, | ||
|  |                                           OWSDisappearingMessageFinderThreadIdColumn, | ||
|  |                                           thread.uniqueId]; | ||
|  | 
 | ||
|  |     YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString]; | ||
|  |     [[transaction ext:OWSDisappearingMessageFinderExpiresAtIndex] | ||
|  |         enumerateKeysMatchingQuery:query | ||
|  |                         usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) { | ||
|  |                             [messageIds addObject:key]; | ||
|  |                         }]; | ||
|  | 
 | ||
|  |     return [messageIds copy]; | ||
|  | } | ||
|  | 
 | ||
|  | - (NSArray<NSString *> *)fetchMessageIdsWhichFailedToStartExpiring:(YapDatabaseReadTransaction *_Nonnull)transaction | ||
|  | { | ||
|  |     NSMutableArray<NSString *> *messageIds = [NSMutableArray new]; | ||
|  |     NSString *formattedString = | ||
|  |         [NSString stringWithFormat:@"WHERE %@ = 0", OWSDisappearingMessageFinderExpiresAtColumn]; | ||
|  | 
 | ||
|  |     YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString]; | ||
|  |     [[transaction ext:OWSDisappearingMessageFinderExpiresAtIndex] | ||
|  |         enumerateKeysAndObjectsMatchingQuery:query | ||
|  |                                   usingBlock:^void(NSString *collection, NSString *key, id object, BOOL *stop) { | ||
|  |                                       if (![object isKindOfClass:[TSMessage class]]) { | ||
|  |                                           return; | ||
|  |                                       } | ||
|  |                                        | ||
|  |                                       TSMessage *message = (TSMessage *)object; | ||
|  |                                       if ([message shouldStartExpireTimerWithTransaction:transaction]) { | ||
|  |                                           if ([message isKindOfClass:[TSIncomingMessage class]]) { | ||
|  |                                               TSIncomingMessage *incomingMessage = (TSIncomingMessage *)message; | ||
|  |                                               if (!incomingMessage.wasRead) { | ||
|  |                                                   return; | ||
|  |                                               } | ||
|  |                                           } | ||
|  |                                           [messageIds addObject:key]; | ||
|  |                                       } | ||
|  |                                   }]; | ||
|  | 
 | ||
|  |     return [messageIds copy]; | ||
|  | } | ||
|  | 
 | ||
|  | - (NSArray<NSString *> *)fetchExpiredMessageIdsWithTransaction:(YapDatabaseReadTransaction *_Nonnull)transaction | ||
|  | { | ||
|  |     NSMutableArray<NSString *> *messageIds = [NSMutableArray new]; | ||
|  | 
 | ||
|  |     uint64_t now = [NSDate ows_millisecondTimeStamp]; | ||
|  |     // When (expiresAt == 0) the message SHOULD NOT expire. Careful ;) | ||
|  |     NSString *formattedString = [NSString stringWithFormat:@"WHERE %@ > 0 AND %@ <= %lld", | ||
|  |                                           OWSDisappearingMessageFinderExpiresAtColumn, | ||
|  |                                           OWSDisappearingMessageFinderExpiresAtColumn, | ||
|  |                                           now]; | ||
|  |     YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString]; | ||
|  |     [[transaction ext:OWSDisappearingMessageFinderExpiresAtIndex] | ||
|  |         enumerateKeysMatchingQuery:query | ||
|  |                         usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) { | ||
|  |                             [messageIds addObject:key]; | ||
|  |                         }]; | ||
|  | 
 | ||
|  |     return [messageIds copy]; | ||
|  | } | ||
|  | 
 | ||
|  | - (nullable NSNumber *)nextExpirationTimestampWithTransaction:(YapDatabaseReadTransaction *)transaction | ||
|  | { | ||
|  |     NSString *formattedString = [NSString stringWithFormat:@"WHERE %@ > 0 ORDER BY %@ ASC", | ||
|  |                                           OWSDisappearingMessageFinderExpiresAtColumn, | ||
|  |                                           OWSDisappearingMessageFinderExpiresAtColumn]; | ||
|  |     YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString]; | ||
|  | 
 | ||
|  |     __block TSMessage *firstMessage; | ||
|  |     [[transaction ext:OWSDisappearingMessageFinderExpiresAtIndex] | ||
|  |         enumerateKeysAndObjectsMatchingQuery:query | ||
|  |                                   usingBlock:^void(NSString *collection, NSString *key, id object, BOOL *stop) { | ||
|  |                                       firstMessage = (TSMessage *)object; | ||
|  |                                       *stop = YES; | ||
|  |                                   }]; | ||
|  | 
 | ||
|  |     if (firstMessage && firstMessage.expiresAt > 0) { | ||
|  |         return [NSNumber numberWithUnsignedLongLong:firstMessage.expiresAt]; | ||
|  |     } | ||
|  | 
 | ||
|  |     return nil; | ||
|  | } | ||
|  | 
 | ||
|  | - (void)enumerateUnstartedExpiringMessagesInThread:(TSThread *)thread | ||
|  |                                              block:(void (^_Nonnull)(TSMessage *message))block | ||
|  |                                        transaction:(YapDatabaseReadTransaction *)transaction | ||
|  | { | ||
|  |     for (NSString *expiringMessageId in | ||
|  |         [self fetchUnstartedExpiringMessageIdsInThread:thread transaction:transaction]) { | ||
|  |         TSMessage *_Nullable message = [TSMessage fetchObjectWithUniqueID:expiringMessageId transaction:transaction]; | ||
|  |         if ([message isKindOfClass:[TSMessage class]]) { | ||
|  |             block(message); | ||
|  |         } | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | - (void)enumerateMessagesWhichFailedToStartExpiringWithBlock:(void (^_Nonnull)(TSMessage *message))block | ||
|  |                                                  transaction:(YapDatabaseReadTransaction *)transaction | ||
|  | { | ||
|  |     for (NSString *expiringMessageId in [self fetchMessageIdsWhichFailedToStartExpiring:transaction]) { | ||
|  | 
 | ||
|  |         TSMessage *_Nullable message = [TSMessage fetchObjectWithUniqueID:expiringMessageId transaction:transaction]; | ||
|  |         if (![message isKindOfClass:[TSMessage class]]) { | ||
|  |             continue; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (![message shouldStartExpireTimerWithTransaction:transaction]) { | ||
|  |             continue; | ||
|  |         } | ||
|  | 
 | ||
|  |         block(message); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Don't use this in production. Useful for testing. | ||
|  |  * We don't want to instantiate potentially many messages at once. | ||
|  |  */ | ||
|  | - (NSArray<TSMessage *> *)fetchUnstartedExpiringMessagesInThread:(TSThread *)thread | ||
|  |                                                      transaction:(YapDatabaseReadTransaction *)transaction | ||
|  | { | ||
|  |     NSMutableArray<TSMessage *> *messages = [NSMutableArray new]; | ||
|  |     [self enumerateUnstartedExpiringMessagesInThread:thread | ||
|  |                                                block:^(TSMessage *message) { | ||
|  |                                                    [messages addObject:message]; | ||
|  |                                                } | ||
|  |                                          transaction:transaction]; | ||
|  | 
 | ||
|  |     return [messages copy]; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | - (void)enumerateExpiredMessagesWithBlock:(void (^_Nonnull)(TSMessage *message))block | ||
|  |                               transaction:(YapDatabaseReadTransaction *)transaction | ||
|  | { | ||
|  |     // Since we can't directly mutate the enumerated expired messages, we store only their ids in hopes of saving a | ||
|  |     // little memory and then enumerate the (larger) TSMessage objects one at a time. | ||
|  |     for (NSString *expiredMessageId in [self fetchExpiredMessageIdsWithTransaction:transaction]) { | ||
|  |         TSMessage *_Nullable message = [TSMessage fetchObjectWithUniqueID:expiredMessageId transaction:transaction]; | ||
|  |         if ([message isKindOfClass:[TSMessage class]]) { | ||
|  |             block(message); | ||
|  |         } | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Don't use this in production. Useful for testing. | ||
|  |  * We don't want to instantiate potentially many messages at once. | ||
|  |  */ | ||
|  | - (NSArray<TSMessage *> *)fetchExpiredMessagesWithTransaction:(YapDatabaseReadTransaction *)transaction | ||
|  | { | ||
|  |     NSMutableArray<TSMessage *> *messages = [NSMutableArray new]; | ||
|  |     [self enumerateExpiredMessagesWithBlock:^(TSMessage *message) { | ||
|  |         [messages addObject:message]; | ||
|  |     } | ||
|  |                                 transaction:transaction]; | ||
|  | 
 | ||
|  |     return [messages copy]; | ||
|  | } | ||
|  | 
 | ||
|  | #pragma mark - YapDatabaseExtension | ||
|  | 
 | ||
|  | + (YapDatabaseSecondaryIndex *)indexDatabaseExtension | ||
|  | { | ||
|  |     YapDatabaseSecondaryIndexSetup *setup = [YapDatabaseSecondaryIndexSetup new]; | ||
|  |     [setup addColumn:OWSDisappearingMessageFinderExpiresAtColumn withType:YapDatabaseSecondaryIndexTypeInteger]; | ||
|  |     [setup addColumn:OWSDisappearingMessageFinderThreadIdColumn withType:YapDatabaseSecondaryIndexTypeText]; | ||
|  | 
 | ||
|  |     YapDatabaseSecondaryIndexHandler *handler = | ||
|  |         [YapDatabaseSecondaryIndexHandler withObjectBlock:^(YapDatabaseReadTransaction *transaction, | ||
|  |             NSMutableDictionary *dict, | ||
|  |             NSString *collection, | ||
|  |             NSString *key, | ||
|  |             id object) { | ||
|  |             if (![object isKindOfClass:[TSMessage class]]) { | ||
|  |                 return; | ||
|  |             } | ||
|  |             TSMessage *message = (TSMessage *)object; | ||
|  | 
 | ||
|  |             if (![message shouldStartExpireTimerWithTransaction:transaction]) { | ||
|  |                 return; | ||
|  |             } | ||
|  | 
 | ||
|  |             dict[OWSDisappearingMessageFinderExpiresAtColumn] = @(message.expiresAt); | ||
|  |             dict[OWSDisappearingMessageFinderThreadIdColumn] = message.uniqueThreadId; | ||
|  |         }]; | ||
|  | 
 | ||
|  |     return [[YapDatabaseSecondaryIndex alloc] initWithSetup:setup handler:handler versionTag:@"1"]; | ||
|  | } | ||
|  | 
 | ||
|  | #ifdef DEBUG | ||
|  | // Useful for tests, don't use in app startup path because it's slow. | ||
|  | + (void)blockingRegisterDatabaseExtensions:(OWSStorage *)storage | ||
|  | { | ||
|  |     [storage registerExtension:[self indexDatabaseExtension] withName:OWSDisappearingMessageFinderExpiresAtIndex]; | ||
|  | } | ||
|  | #endif | ||
|  | 
 | ||
|  | + (NSString *)databaseExtensionName | ||
|  | { | ||
|  |     return OWSDisappearingMessageFinderExpiresAtIndex; | ||
|  | } | ||
|  | 
 | ||
|  | + (void)asyncRegisterDatabaseExtensions:(OWSStorage *)storage | ||
|  | { | ||
|  |     [storage asyncRegisterExtension:[self indexDatabaseExtension] withName:OWSDisappearingMessageFinderExpiresAtIndex]; | ||
|  | } | ||
|  | 
 | ||
|  | @end | ||
|  | 
 | ||
|  | NS_ASSUME_NONNULL_END |