mirror of https://github.com/oxen-io/session-ios
				
				
				
			Improve handling of incomplete and failed attachment downloads.
// FREEBIEpull/1/head
							parent
							
								
									bdde3c73c5
								
							
						
					
					
						commit
						49a24a4e6a
					
				| @ -0,0 +1,28 @@ | ||||
| //
 | ||||
| //  Copyright (c) 2017 Open Whisper Systems. All rights reserved.
 | ||||
| //
 | ||||
| 
 | ||||
| NS_ASSUME_NONNULL_BEGIN | ||||
| 
 | ||||
| @class TSStorageManager; | ||||
| 
 | ||||
| @interface OWSFailedAttachmentDownloadsJob : NSObject | ||||
| 
 | ||||
| - (instancetype)init NS_UNAVAILABLE; | ||||
| - (instancetype)initWithStorageManager:(TSStorageManager *)storageManager NS_DESIGNATED_INITIALIZER; | ||||
| 
 | ||||
| - (void)run; | ||||
| 
 | ||||
| /**
 | ||||
|  * Database extensions required for class to work. | ||||
|  */ | ||||
| - (void)asyncRegisterDatabaseExtensions; | ||||
| 
 | ||||
| /**
 | ||||
|  * Only use the sync version for testing, generally we'll want to register extensions async | ||||
|  */ | ||||
| - (void)blockingRegisterDatabaseExtensions; | ||||
| 
 | ||||
| @end | ||||
| 
 | ||||
| NS_ASSUME_NONNULL_END | ||||
| @ -0,0 +1,148 @@ | ||||
| // | ||||
| //  Copyright (c) 2017 Open Whisper Systems. All rights reserved. | ||||
| // | ||||
| 
 | ||||
| #import "OWSFailedAttachmentDownloadsJob.h" | ||||
| #import "TSAttachmentPointer.h" | ||||
| #import "TSStorageManager.h" | ||||
| #import <YapDatabase/YapDatabaseConnection.h> | ||||
| #import <YapDatabase/YapDatabaseQuery.h> | ||||
| #import <YapDatabase/YapDatabaseSecondaryIndex.h> | ||||
| 
 | ||||
| NS_ASSUME_NONNULL_BEGIN | ||||
| 
 | ||||
| static NSString *const OWSFailedAttachmentDownloadsJobAttachmentStateColumn = @"state"; | ||||
| static NSString *const OWSFailedAttachmentDownloadsJobAttachmentStateIndex = @"index_attachment_downloads_on_state"; | ||||
| 
 | ||||
| @interface OWSFailedAttachmentDownloadsJob () | ||||
| 
 | ||||
| @property (nonatomic, readonly) TSStorageManager *storageManager; | ||||
| 
 | ||||
| @end | ||||
| 
 | ||||
| #pragma mark - | ||||
| 
 | ||||
| @implementation OWSFailedAttachmentDownloadsJob | ||||
| 
 | ||||
| - (instancetype)initWithStorageManager:(TSStorageManager *)storageManager | ||||
| { | ||||
|     self = [super init]; | ||||
|     if (!self) { | ||||
|         return self; | ||||
|     } | ||||
| 
 | ||||
|     _storageManager = storageManager; | ||||
| 
 | ||||
|     return self; | ||||
| } | ||||
| 
 | ||||
| - (NSArray<NSString *> *)fetchAttemptingOutAttachmentIds:(YapDatabaseConnection *)dbConnection | ||||
| { | ||||
|     NSMutableArray<NSString *> *attachmentIds = [NSMutableArray new]; | ||||
| 
 | ||||
|     NSString *formattedString = [NSString stringWithFormat:@"WHERE %@ != %d", | ||||
|                                           OWSFailedAttachmentDownloadsJobAttachmentStateColumn, | ||||
|                                           (int)TSAttachmentPointerStateFailed]; | ||||
|     YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString]; | ||||
|     [dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { | ||||
|         [[transaction ext:OWSFailedAttachmentDownloadsJobAttachmentStateIndex] | ||||
|             enumerateKeysMatchingQuery:query | ||||
|                             usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) { | ||||
|                                 [attachmentIds addObject:key]; | ||||
|                             }]; | ||||
|     }]; | ||||
| 
 | ||||
|     return [attachmentIds copy]; | ||||
| } | ||||
| 
 | ||||
| - (void)enumerateAttemptingOutAttachmentsWithBlock:(void (^_Nonnull)(TSAttachmentPointer *attachment))block | ||||
| { | ||||
|     YapDatabaseConnection *dbConnection = [self.storageManager newDatabaseConnection]; | ||||
| 
 | ||||
|     // Since we can't directly mutate the enumerated attachments, we store only their ids in hopes | ||||
|     // of saving a little memory and then enumerate the (larger) TSAttachment objects one at a time. | ||||
|     for (NSString *attachmentId in [self fetchAttemptingOutAttachmentIds:dbConnection]) { | ||||
|         TSAttachmentPointer *_Nullable attachment = [TSAttachmentPointer fetchObjectWithUniqueID:attachmentId]; | ||||
|         if ([attachment isKindOfClass:[TSAttachmentPointer class]]) { | ||||
|             block(attachment); | ||||
|         } else { | ||||
|             DDLogError(@"%@ unexpected object: %@", self.tag, attachment); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| - (void)run | ||||
| { | ||||
|     __block uint count = 0; | ||||
|     [self enumerateAttemptingOutAttachmentsWithBlock:^(TSAttachmentPointer *attachment) { | ||||
|         // sanity check | ||||
|         if (attachment.state != TSAttachmentPointerStateFailed) { | ||||
|             DDLogDebug(@"%@ marking attachment as failed", self.tag); | ||||
|             attachment.state = TSAttachmentPointerStateFailed; | ||||
|             [attachment save]; | ||||
|             count++; | ||||
|         } | ||||
|     }]; | ||||
| 
 | ||||
|     DDLogDebug(@"%@ Marked %u attachments as unsent", self.tag, count); | ||||
| } | ||||
| 
 | ||||
| #pragma mark - YapDatabaseExtension | ||||
| 
 | ||||
| - (YapDatabaseSecondaryIndex *)indexDatabaseExtension | ||||
| { | ||||
|     YapDatabaseSecondaryIndexSetup *setup = [YapDatabaseSecondaryIndexSetup new]; | ||||
|     [setup addColumn:OWSFailedAttachmentDownloadsJobAttachmentStateColumn | ||||
|             withType:YapDatabaseSecondaryIndexTypeInteger]; | ||||
| 
 | ||||
|     YapDatabaseSecondaryIndexHandler *handler = | ||||
|         [YapDatabaseSecondaryIndexHandler withObjectBlock:^(YapDatabaseReadTransaction *transaction, | ||||
|             NSMutableDictionary *dict, | ||||
|             NSString *collection, | ||||
|             NSString *key, | ||||
|             id object) { | ||||
|             if (![object isKindOfClass:[TSAttachmentPointer class]]) { | ||||
|                 return; | ||||
|             } | ||||
|             TSAttachmentPointer *attachment = (TSAttachmentPointer *)object; | ||||
|             dict[OWSFailedAttachmentDownloadsJobAttachmentStateColumn] = @(attachment.state); | ||||
|         }]; | ||||
| 
 | ||||
|     return [[YapDatabaseSecondaryIndex alloc] initWithSetup:setup handler:handler]; | ||||
| } | ||||
| 
 | ||||
| // Useful for tests, don't use in app startup path because it's slow. | ||||
| - (void)blockingRegisterDatabaseExtensions | ||||
| { | ||||
|     [self.storageManager.database registerExtension:[self indexDatabaseExtension] | ||||
|                                            withName:OWSFailedAttachmentDownloadsJobAttachmentStateIndex]; | ||||
| } | ||||
| 
 | ||||
| - (void)asyncRegisterDatabaseExtensions | ||||
| { | ||||
|     [self.storageManager.database asyncRegisterExtension:[self indexDatabaseExtension] | ||||
|                                                 withName:OWSFailedAttachmentDownloadsJobAttachmentStateIndex | ||||
|                                          completionBlock:^(BOOL ready) { | ||||
|                                              if (ready) { | ||||
|                                                  DDLogDebug(@"%@ completed registering extension async.", self.tag); | ||||
|                                              } else { | ||||
|                                                  DDLogError(@"%@ failed registering extension async.", self.tag); | ||||
|                                              } | ||||
|                                          }]; | ||||
| } | ||||
| 
 | ||||
| #pragma mark - Logging | ||||
| 
 | ||||
| + (NSString *)tag | ||||
| { | ||||
|     return [NSString stringWithFormat:@"[%@]", self.class]; | ||||
| } | ||||
| 
 | ||||
| - (NSString *)tag | ||||
| { | ||||
|     return self.class.tag; | ||||
| } | ||||
| 
 | ||||
| @end | ||||
| 
 | ||||
| NS_ASSUME_NONNULL_END | ||||
					Loading…
					
					
				
		Reference in New Issue