NCChatFileController.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. /**
  2. * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
  3. * SPDX-License-Identifier: GPL-3.0-or-later
  4. */
  5. #import "NCChatFileController.h"
  6. @import NextcloudKit;
  7. #import "NCAPIController.h"
  8. #import "NCDatabaseManager.h"
  9. #import "NextcloudTalk-Swift.h"
  10. NSString * const NCChatFileControllerDidChangeIsDownloadingNotification = @"NCChatFileControllerDidChangeIsDownloadingNotification";
  11. NSString * const NCChatFileControllerDidChangeDownloadProgressNotification = @"NCChatFileControllerDidChangeDownloadProgressNotification";
  12. int const kNCChatFileControllerDeleteFilesOlderThanDays = 7;
  13. @interface NCChatFileController ()
  14. @property (nonatomic, strong) NCChatFileStatus *fileStatus;
  15. @property (nonatomic, strong) NSString *tempDirectoryPath;
  16. @end
  17. @implementation NCChatFileController
  18. - (void)initDownloadDirectoryForAccount:(TalkAccount *)account
  19. {
  20. NSString *encodedAccountId = [account.accountId stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLHostAllowedCharacterSet];
  21. NSFileManager *fileManager = [NSFileManager defaultManager];
  22. _tempDirectoryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"/download/"];
  23. _tempDirectoryPath = [_tempDirectoryPath stringByAppendingPathComponent:encodedAccountId];
  24. NSLog(@"Directory for downloads: %@", _tempDirectoryPath);
  25. if (![fileManager fileExistsAtPath:_tempDirectoryPath]) {
  26. // Make sure our download directory exists
  27. [fileManager createDirectoryAtPath:_tempDirectoryPath withIntermediateDirectories:YES attributes:nil error:nil];
  28. }
  29. [self removeOldFilesFromCache:kNCChatFileControllerDeleteFilesOlderThanDays];
  30. }
  31. - (void)removeOldFilesFromCache:(int)thresholdDays
  32. {
  33. NSFileManager *fileManager = [NSFileManager defaultManager];
  34. NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtPath:_tempDirectoryPath];
  35. NSDateComponents *dayComponent = [[NSDateComponents alloc] init];
  36. dayComponent.day = -thresholdDays;
  37. NSDate *thresholdDate = [[NSCalendar currentCalendar] dateByAddingComponents:dayComponent toDate:[NSDate date] options:0];
  38. NSString *file;
  39. while (file = [enumerator nextObject])
  40. {
  41. NSString *filePath = [_tempDirectoryPath stringByAppendingPathComponent:file];
  42. NSDate *creationDate = [[fileManager attributesOfItemAtPath:filePath error:nil] fileCreationDate];
  43. if ([creationDate compare:thresholdDate] == NSOrderedAscending) {
  44. NSLog(@"Deleting file from cache: %@", filePath);
  45. [fileManager removeItemAtPath:filePath error:nil];
  46. }
  47. }
  48. }
  49. - (void)deleteDownloadDirectoryForAccount:(TalkAccount *)account
  50. {
  51. NSFileManager *fileManager = [NSFileManager defaultManager];
  52. [self initDownloadDirectoryForAccount:account];
  53. [fileManager removeItemAtPath:_tempDirectoryPath error:nil];
  54. NSLog(@"Deleted download directory: %@", _tempDirectoryPath);
  55. }
  56. - (void)clearDownloadDirectoryForAccount:(TalkAccount *)account
  57. {
  58. [self deleteDownloadDirectoryForAccount:account];
  59. [self initDownloadDirectoryForAccount:account];
  60. }
  61. - (NSInteger)getDiskUsageForAccount:(TalkAccount *)account
  62. {
  63. [self initDownloadDirectoryForAccount:account];
  64. NSFileManager *fileManager = [NSFileManager defaultManager];
  65. NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtPath:_tempDirectoryPath];
  66. NSString *file;
  67. NSInteger folderSize = 0;
  68. while (file = [enumerator nextObject])
  69. {
  70. NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[_tempDirectoryPath stringByAppendingPathComponent:file] error:nil];
  71. folderSize += [[fileAttributes objectForKey:NSFileSize] intValue];
  72. }
  73. return folderSize;
  74. }
  75. - (BOOL)isFileInCache:(NSString *)filePath withModificationDate:(NSDate *)date withSize:(double)size
  76. {
  77. NSFileManager *fileManager = [NSFileManager defaultManager];
  78. if (![fileManager fileExistsAtPath:filePath]) {
  79. return NO;
  80. }
  81. NSError *error = nil;
  82. NSDictionary<NSFileAttributeKey, id> *fileAttributes = [fileManager attributesOfItemAtPath:filePath error:&error];
  83. NSDate *modificationDate = [fileAttributes fileModificationDate];
  84. long long fileSize = [fileAttributes fileSize];
  85. if ([date compare:modificationDate] == NSOrderedSame && fileSize == (long long)size) {
  86. return YES;
  87. }
  88. // At this point there's a file in our cache but there's a different one on the server
  89. NSLog(@"Deleting file from cache: %@", filePath);
  90. [fileManager removeItemAtPath:filePath error:nil];
  91. return NO;
  92. }
  93. - (void)setCreationDateOnFile:(NSString *)filePath withCreationDate:(NSDate *)date
  94. {
  95. NSFileManager *fileManager = [NSFileManager defaultManager];
  96. NSDictionary *creationDateAttr = [NSDictionary dictionaryWithObjectsAndKeys:date, NSFileCreationDate, nil];
  97. [fileManager setAttributes:creationDateAttr ofItemAtPath:filePath error:nil];
  98. }
  99. - (void)setModificationDateOnFile:(NSString *)filePath withModificationDate:(NSDate *)date
  100. {
  101. NSFileManager *fileManager = [NSFileManager defaultManager];
  102. NSDictionary *modificationDateAttr = [NSDictionary dictionaryWithObjectsAndKeys:date, NSFileModificationDate, nil];
  103. [fileManager setAttributes:modificationDateAttr ofItemAtPath:filePath error:nil];
  104. }
  105. - (void)downloadFileFromMessage:(NCMessageFileParameter *)fileParameter
  106. {
  107. _fileStatus = [[NCChatFileStatus alloc] initWithFileId:fileParameter.parameterId fileName:fileParameter.name filePath:fileParameter.path];
  108. fileParameter.fileStatus = _fileStatus;
  109. [self startDownload];
  110. }
  111. - (void)downloadFileWithFileId:(NSString *)fileId
  112. {
  113. TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount];
  114. [[NCAPIController sharedInstance] getFileByFileId:activeAccount fileId:fileId withCompletionBlock:^(NKFile *file, NSInteger error, NSString *errorDescription) {
  115. if (file) {
  116. NSString *remoteDavPrefix = [NSString stringWithFormat:@"/remote.php/dav/files/%@/", activeAccount.userId];
  117. NSString *directoryPath = [file.path componentsSeparatedByString:remoteDavPrefix].lastObject;
  118. NSString *filePath = [NSString stringWithFormat:@"%@%@", directoryPath, file.fileName];
  119. self->_fileStatus = [[NCChatFileStatus alloc] initWithFileId:file.fileId fileName:file.fileName filePath:filePath];
  120. [self startDownload];
  121. } else {
  122. NSLog(@"An error occurred while getting file with fileId %@: %@", fileId, errorDescription);
  123. [self.delegate fileControllerDidFailLoadingFile:self withErrorDescription:errorDescription];
  124. }
  125. }];
  126. }
  127. - (void)startDownload
  128. {
  129. TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount];
  130. [[NCAPIController sharedInstance] setupNCCommunicationForAccount:activeAccount];
  131. [self initDownloadDirectoryForAccount:activeAccount];
  132. NSString *serverUrlFileName = [NSString stringWithFormat:@"%@%@/%@", activeAccount.server, [[NCAPIController sharedInstance] filesPathForAccount:activeAccount], _fileStatus.filePath];
  133. _fileStatus.fileLocalPath = [_tempDirectoryPath stringByAppendingPathComponent:_fileStatus.fileName];
  134. // Setting just isDownloading without a concrete progress will show an indeterminate activity indicator
  135. [self didChangeIsDownloadingNotification:YES];
  136. // First read metadata from the file and check if we already downloaded it
  137. NKRequestOptions *options = [[NKRequestOptions alloc] initWithEndpoint:nil customHeader:nil customUserAgent:nil contentType:nil e2eToken:nil timeout:60 queue:dispatch_get_main_queue()];
  138. [[NextcloudKit shared] readFileOrFolderWithServerUrlFileName:serverUrlFileName depth:@"0" showHiddenFiles:NO includeHiddenFiles:@[] requestBody:nil options:options completion:^(NSString *account, NSArray<NKFile *> *files, NSData *responseDates, NKError *error) {
  139. if (error.errorCode == 0 && files.count == 1) {
  140. // File exists on server -> check our cache
  141. NKFile *file = files.firstObject;
  142. if ([self isFileInCache:self->_fileStatus.fileLocalPath withModificationDate:file.date withSize:file.size]) {
  143. NSLog(@"Found file in cache: %@", self->_fileStatus.fileLocalPath);
  144. [self.delegate fileControllerDidLoadFile:self withFileStatus:self->_fileStatus];
  145. [self didChangeIsDownloadingNotification:NO];
  146. return;
  147. }
  148. [[NextcloudKit shared] downloadWithServerUrlFileName:serverUrlFileName fileNameLocalPath:self->_fileStatus.fileLocalPath customUserAgent:nil addCustomHeaders:nil queue:dispatch_get_main_queue() taskHandler:^(NSURLSessionTask *task) {
  149. NSLog(@"Download task");
  150. } progressHandler:^(NSProgress *progress) {
  151. [self didChangeDownloadProgressNotification:progress];
  152. } completionHandler:^(NSString *account, NSString *etag, NSDate *date, int64_t length, NSDictionary *allHeaderFields, NKError *error) {
  153. if (error.errorCode == 0) {
  154. // Set modification date to invalidate our cache
  155. [self setModificationDateOnFile:self->_fileStatus.fileLocalPath withModificationDate:file.date];
  156. // Set creation date to delete older files from cache
  157. [self setCreationDateOnFile:self->_fileStatus.fileLocalPath withCreationDate:[NSDate date]];
  158. [self.delegate fileControllerDidLoadFile:self withFileStatus:self->_fileStatus];
  159. } else {
  160. NSLog(@"Error downloading file: %ld - %@", error.errorCode, error.errorDescription);
  161. [self.delegate fileControllerDidFailLoadingFile:self withErrorDescription:error.errorDescription];
  162. }
  163. [self didChangeIsDownloadingNotification:NO];
  164. }];
  165. } else {
  166. [self didChangeIsDownloadingNotification:NO];
  167. NSLog(@"Error downloading file: %ld - %@", error.errorCode, error.errorDescription);
  168. [self.delegate fileControllerDidFailLoadingFile:self withErrorDescription:error.errorDescription];
  169. }
  170. }];
  171. }
  172. - (void)didChangeIsDownloadingNotification:(BOOL)isDownloading
  173. {
  174. _fileStatus.isDownloading = isDownloading;
  175. NSMutableDictionary *userInfo = [NSMutableDictionary new];
  176. [userInfo setObject:_fileStatus forKey:@"fileStatus"];
  177. [[NSNotificationCenter defaultCenter] postNotificationName:NCChatFileControllerDidChangeIsDownloadingNotification
  178. object:self
  179. userInfo:userInfo];
  180. }
  181. - (void)didChangeDownloadProgressNotification:(NSProgress *)progress
  182. {
  183. _fileStatus.downloadProgress = progress.fractionCompleted;
  184. _fileStatus.canReportProgress = (progress.totalUnitCount != -1);
  185. NSMutableDictionary *userInfo = [NSMutableDictionary new];
  186. [userInfo setObject:_fileStatus forKey:@"fileStatus"];
  187. [[NSNotificationCenter defaultCenter] postNotificationName:NCChatFileControllerDidChangeDownloadProgressNotification
  188. object:self
  189. userInfo:userInfo];
  190. }
  191. @end