GatewayAvatarMaker.m 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  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 "GatewayAvatarMaker.h"
  21. #import "EntityManager.h"
  22. #import "Contact.h"
  23. #import "AppGroup.h"
  24. #import "HTTPSURLLoader.h"
  25. #import "BundleUtil.h"
  26. #define AVATAR_EXPIRES_DICTIONARY @"GatewayAvatarExpiresDictionary"
  27. #define AVATAR_REFRESH_TIMESTAMP @"GatewayAvatarLastRefreshDate"
  28. #define AVATAR_CHECK_INTERVAL (24 * 60 * 60)
  29. @interface GatewayAvatarMaker ()
  30. @property (nonatomic) EntityManager *entityManager;
  31. @property (nonatomic) NSMutableDictionary *expiresDictionary;
  32. @property BOOL forceRefresh;
  33. @end
  34. @implementation GatewayAvatarMaker
  35. + (instancetype)gatewayAvatarMaker {
  36. return [[GatewayAvatarMaker alloc] init];
  37. }
  38. - (void)refresh {
  39. _forceRefresh = NO;
  40. [self loadCache];
  41. }
  42. - (void)refreshForced {
  43. _forceRefresh = YES;
  44. [self deleteExpires];
  45. [self loadCache];
  46. }
  47. - (void)loadAndSaveAvatarForId:(NSString *)identity {
  48. _forceRefresh = YES;
  49. [self updateIdentity:identity];
  50. }
  51. - (void)loadAvatarForId:(NSString *)identity onCompletion:(void (^)(UIImage *))onCompletion onError:(void (^)(NSError *))onError {
  52. NSURLRequestCachePolicy cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
  53. if (_forceRefresh == NO && [self isExpiredForIdentiy:identity] == NO) {
  54. onError(nil);
  55. }
  56. NSURL *url = [self urlForGatewayId:identity];
  57. NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:cachePolicy timeoutInterval:kBlobLoadTimeout];
  58. HTTPSURLLoader *httpsLoader = [[HTTPSURLLoader alloc] init];
  59. [httpsLoader startWithURLRequest:request onCompletion:^(NSData *data) {
  60. onCompletion([UIImage imageWithData:data]);
  61. } onError:^(NSError *error) {
  62. onError(error);
  63. }];
  64. }
  65. - (EntityManager *)entityManager {
  66. if (_entityManager == nil) {
  67. _entityManager = [[EntityManager alloc] init];
  68. }
  69. return _entityManager;
  70. }
  71. - (NSMutableDictionary *)expiresDictionary {
  72. if (_expiresDictionary == nil) {
  73. NSDictionary *currentDictionary = [[AppGroup userDefaults] objectForKey:AVATAR_EXPIRES_DICTIONARY];
  74. if (currentDictionary) {
  75. _expiresDictionary = [NSMutableDictionary dictionaryWithDictionary:currentDictionary];
  76. } else {
  77. _expiresDictionary = [NSMutableDictionary dictionary];
  78. }
  79. }
  80. return _expiresDictionary;
  81. }
  82. - (void)loadCache {
  83. NSArray *gatewayContacts = [self.entityManager.entityFetcher allGatewayContacts];
  84. if ([gatewayContacts count] > 0) {
  85. for (Contact *contact in gatewayContacts) {
  86. [self updateIdentity:contact.identity];
  87. }
  88. [self updateLastRefreshDate];
  89. }
  90. }
  91. - (void)updateIdentity:(NSString *)identity {
  92. NSURLRequestCachePolicy cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
  93. if (_forceRefresh == NO && [self isExpiredForIdentiy:identity] == NO) {
  94. return;
  95. }
  96. NSURL *url = [self urlForGatewayId:identity];
  97. NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:cachePolicy timeoutInterval:kBlobLoadTimeout];
  98. HTTPSURLLoader *httpsLoader = [[HTTPSURLLoader alloc] init];
  99. [httpsLoader startWithURLRequest:request onCompletion:^(NSData *data) {
  100. [self.entityManager performSyncBlockAndSafe:^{
  101. Contact *contact = [self.entityManager.entityFetcher contactForId:identity];
  102. contact.imageData = data;
  103. }];
  104. NSString *expires = [httpsLoader.responseHeaderFields objectForKey:@"Expires"];
  105. if (expires) {
  106. NSDate *expiresDate = [self parseExpiresString:expires];
  107. [self updateExpires:expiresDate forIdentity:identity];
  108. }
  109. [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationIdentityAvatarChanged object:identity];
  110. } onError:^(NSError *error) {
  111. if (error.code == 404) {
  112. [self.entityManager performSyncBlockAndSafe:^{
  113. Contact *contact = [self.entityManager.entityFetcher contactForId:identity];
  114. contact.imageData = nil;
  115. }];
  116. NSDate *expires = [self expiresIn:AVATAR_CHECK_INTERVAL];
  117. [self updateExpires:expires forIdentity:identity];
  118. }
  119. }];
  120. }
  121. - (NSDate *)expiresIn:(NSInteger)seconds {
  122. NSDate *expires = [[NSDate date] dateByAddingTimeInterval:seconds];
  123. return expires;
  124. }
  125. - (NSURL *)urlForGatewayId:(NSString *)gatewayId {
  126. NSString *urlString = [BundleUtil objectForInfoDictionaryKey:@"ThreemaAvatarURL"];
  127. urlString = [urlString stringByAppendingString:gatewayId];
  128. return [NSURL URLWithString:urlString];
  129. }
  130. - (NSDate *)parseExpiresString:(NSString *)expiresString {
  131. static NSDateFormatter *rfc1123Formatter;
  132. static dispatch_once_t onceToken;
  133. dispatch_once(&onceToken, ^{
  134. rfc1123Formatter = [[NSDateFormatter alloc] init];
  135. rfc1123Formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
  136. rfc1123Formatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
  137. rfc1123Formatter.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss z";
  138. });
  139. return [rfc1123Formatter dateFromString:expiresString];
  140. }
  141. - (void)updateExpires:(NSDate *)expires forIdentity:(NSString *)identity {
  142. [self.expiresDictionary setObject:expires forKey:identity];
  143. [[AppGroup userDefaults] setObject:self.expiresDictionary forKey:AVATAR_EXPIRES_DICTIONARY];
  144. [[AppGroup userDefaults] synchronize];
  145. }
  146. - (BOOL)isExpiredForIdentiy:(NSString *)identity {
  147. NSDate *expires = [self.expiresDictionary objectForKey:identity];
  148. NSDate *now = [NSDate date];
  149. if (expires && [now timeIntervalSinceDate:expires] < 0.0) {
  150. return NO;
  151. }
  152. return YES;
  153. }
  154. - (void)deleteExpires {
  155. [[AppGroup userDefaults] removeObjectForKey:AVATAR_EXPIRES_DICTIONARY];
  156. [[AppGroup userDefaults] synchronize];
  157. }
  158. - (void)updateLastRefreshDate {
  159. NSDate *date = [NSDate date];
  160. [[AppGroup userDefaults] setObject:date forKey:AVATAR_REFRESH_TIMESTAMP];
  161. [[AppGroup userDefaults] synchronize];
  162. }
  163. - (NSDate *)lastRefreshDate {
  164. return [[AppGroup userDefaults] objectForKey:AVATAR_REFRESH_TIMESTAMP];
  165. }
  166. @end