BlobMessageSender.m 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2015-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. #import "BlobMessageSender.h"
  21. #import "Conversation.h"
  22. #import "Contact.h"
  23. #import "EntityManager.h"
  24. #import "MessageFetcher.h"
  25. #import "BlobUploader.h"
  26. #define MAX_CONCURRENT_UPLOADS 1
  27. #define TIMEOUT_INTERVAL_S 5
  28. #ifdef DEBUG
  29. static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
  30. #else
  31. static const DDLogLevel ddLogLevel = DDLogLevelWarning;
  32. #endif
  33. static dispatch_semaphore_t sema;
  34. static NSMutableArray *scheduledUploads;
  35. static dispatch_queue_t backgroundQueue;
  36. @interface BlobMessageSender ()
  37. @property BlobUploader *blobSender;
  38. @end
  39. @implementation BlobMessageSender
  40. + (void)initialize {
  41. static dispatch_once_t onceToken;
  42. dispatch_once(&onceToken, ^{
  43. sema = dispatch_semaphore_create(MAX_CONCURRENT_UPLOADS);
  44. scheduledUploads = [NSMutableArray array];
  45. backgroundQueue = dispatch_queue_create("ch.threema.blobSenderQueue", 0);
  46. });
  47. }
  48. #pragma mark - abstract methods
  49. - (void)sendItem:(URLSenderItem *)item inConversation:(Conversation *)conversation {
  50. [NSException raise:NSInternalInconsistencyException
  51. format:@"Method %@ is abstract, subclass it", NSStringFromSelector(_cmd)];
  52. }
  53. - (void)sendMessageTo:(Contact *)contact blobIds:(NSArray *)blobIds {
  54. [NSException raise:NSInternalInconsistencyException
  55. format:@"Method %@ is abstract, subclass it", NSStringFromSelector(_cmd)];
  56. }
  57. - (void)sendGroupMessageTo:(Contact *)contact blobIds:(NSArray *)blobIds {
  58. [NSException raise:NSInternalInconsistencyException
  59. format:@"Method %@ is abstract, subclass it", NSStringFromSelector(_cmd)];
  60. }
  61. - (void)prepareUpload {
  62. [NSException raise:NSInternalInconsistencyException
  63. format:@"Method %@ is abstract, subclass it", NSStringFromSelector(_cmd)];
  64. }
  65. - (NSData *)encryptedData {
  66. [NSException raise:NSInternalInconsistencyException
  67. format:@"Method %@ is abstract, subclass it", NSStringFromSelector(_cmd)];
  68. return nil;
  69. }
  70. - (NSData *)encryptedThumbnailData {
  71. // default implemenation has no thumbnail
  72. return nil;
  73. }
  74. - (void)createDBMessage {
  75. [NSException raise:NSInternalInconsistencyException
  76. format:@"Method %@ is abstract, subclass it", NSStringFromSelector(_cmd)];
  77. }
  78. - (BOOL)supportsCaption {
  79. return NO;
  80. }
  81. #pragma mark - private
  82. - (void)scheduleUpload {
  83. // use background queue to avoid blocking of main queue
  84. dispatch_async(backgroundQueue, ^{
  85. [scheduledUploads addObject:self];
  86. while (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, TIMEOUT_INTERVAL_S * NSEC_PER_SEC)) != 0) {
  87. if ([_uploadProgressDelegate blobMessageSenderUploadShouldCancel:self]) {
  88. [self didFinishUpload];
  89. return;
  90. }
  91. }
  92. // do DB modifications & upload in main queue
  93. dispatch_sync(dispatch_get_main_queue(), ^{
  94. if (self.message == nil) {
  95. @try {
  96. [self createDBMessage];
  97. }
  98. @catch (NSException *exception) {
  99. // if the external reference for the image/video/file cannot be fullfilled CoreData will throw a NSInternalInconsistencyException
  100. [self uploadFailed];
  101. return;
  102. }
  103. } else {
  104. EntityManager *entityManager = [[EntityManager alloc] init];
  105. [entityManager performSyncBlockAndSafe:^{
  106. self.message.sendFailed = [NSNumber numberWithBool:NO];
  107. }];
  108. }
  109. if (self.message == nil) {
  110. DDLogError(@"BlobMessageSender: no message to send");
  111. [self didFinishUpload];
  112. return;
  113. }
  114. _blobSender = [[BlobUploader alloc] init];
  115. NSData *data = [self encryptedData];
  116. if (data == nil) {
  117. DDLogError(@"BlobMessageSender: no data to send");
  118. [self didFinishUpload];
  119. return;
  120. }
  121. _blobSender.data = data;
  122. _blobSender.thumbnailData = [self encryptedThumbnailData];
  123. [_blobSender startUploadFor:self];
  124. });
  125. });
  126. }
  127. - (void)didFinishUpload {
  128. dispatch_semaphore_signal(sema);
  129. [scheduledUploads removeObject:self];
  130. }
  131. #pragma mark - UploadProgressDelegate
  132. - (BOOL)uploadShouldCancel {
  133. if ([_message wasDeleted]) {
  134. return YES;
  135. }
  136. if ([_uploadProgressDelegate blobMessageSenderUploadShouldCancel:self]) {
  137. return YES;
  138. }
  139. return NO;
  140. }
  141. - (void)uploadDidCancel {
  142. [self didFinishUpload];
  143. EntityManager *entityManager = [[EntityManager alloc] init];
  144. [entityManager performAsyncBlockAndSafe:^{
  145. Conversation *conversation = _message.conversation;
  146. conversation.lastMessage = nil;
  147. [[entityManager entityDestroyer] deleteObjectWithObject:_message];
  148. MessageFetcher *messageFetcher = [MessageFetcher messageFetcherFor:conversation withEntityFetcher:entityManager.entityFetcher];
  149. conversation.lastMessage = [messageFetcher lastMessage];
  150. }];
  151. }
  152. - (void)uploadSucceededWithBlobIds:(NSArray *)blobIds {
  153. [self didFinishUpload];
  154. if ([_message wasDeleted]) {
  155. DDLogWarn(@"Blob message has been deleted!");
  156. return;
  157. }
  158. /* send actual message */
  159. Conversation *conversation = _message.conversation;
  160. if (conversation.groupId != nil) {
  161. /* send to each group member */
  162. for (Contact *member in conversation.members) {
  163. DDLogVerbose(@"Sending group blob message to %@", member.identity);
  164. [self sendGroupMessageTo:member blobIds:blobIds];
  165. }
  166. } else {
  167. DDLogVerbose(@"Sending blob message to %@", conversation.contact);
  168. [self sendMessageTo:conversation.contact blobIds:blobIds];
  169. }
  170. // observer sent state in order to trigger [_uploadProgressDelegate uploadSucceededForMessage]
  171. [_message addObserver:self forKeyPath:@"sent" options:0 context:nil];
  172. }
  173. - (void)uploadFailed {
  174. [self didFinishUpload];
  175. if ([_message wasDeleted] == NO) {
  176. EntityManager *entityManager = [[EntityManager alloc] init];
  177. [entityManager performAsyncBlockAndSafe:^{
  178. _message.sendFailed = [NSNumber numberWithBool:YES];
  179. [_message blobUpdateProgress:nil];
  180. }];
  181. }
  182. [_uploadProgressDelegate blobMessageSender:self uploadFailedForMessage:_message error:UploadErrorSendFailed];
  183. }
  184. - (void)uploadProgress:(NSNumber *)progress {
  185. if ([_message wasDeleted]) {
  186. return;
  187. }
  188. [_message blobUpdateProgress:progress];
  189. [_uploadProgressDelegate blobMessageSender:self uploadProgress:progress forMessage:_message];
  190. }
  191. #pragma mark - KVO
  192. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  193. if (object == _message) {
  194. [_message removeObserver:self forKeyPath:@"sent"];
  195. [_message blobUpdateProgress:nil];
  196. [_uploadProgressDelegate blobMessageSender:self uploadSucceededForMessage:_message];
  197. }
  198. }
  199. @end