VLCBoxController.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. /*****************************************************************************
  2. * VLCBoxController.m
  3. * VLC for iOS
  4. *****************************************************************************
  5. * Copyright (c) 2014 VideoLAN. All rights reserved.
  6. * $Id$
  7. *
  8. * Authors: Carola Nitz <nitz.carola # googlemail.com>
  9. *
  10. * Refer to the COPYING file of the official project for license.
  11. *****************************************************************************/
  12. #import "VLCBoxController.h"
  13. #import "NSString+SupportedMedia.h"
  14. #import "VLCAppDelegate.h"
  15. #import <SSKeychain/SSKeychain.h>
  16. @interface VLCBoxController () <NSURLConnectionDataDelegate>
  17. {
  18. BoxCollection *_fileList;
  19. BoxAPIJSONOperation *_operation;
  20. NSArray *_currentFileList;
  21. NSMutableArray *_listOfBoxFilesToDownload;
  22. BOOL _downloadInProgress;
  23. int _maxOffset;
  24. int _offset;
  25. NSString *_folderId;
  26. CGFloat _averageSpeed;
  27. NSTimeInterval _startDL;
  28. NSTimeInterval _lastStatsUpdate;
  29. }
  30. @end
  31. @implementation VLCBoxController
  32. #pragma mark - session handling
  33. + (VLCCloudStorageController *)sharedInstance
  34. {
  35. static VLCBoxController *sharedInstance = nil;
  36. static dispatch_once_t pred;
  37. dispatch_once(&pred, ^{
  38. sharedInstance = [VLCBoxController new];
  39. });
  40. return sharedInstance;
  41. }
  42. - (void)startSession
  43. {
  44. [BoxSDK sharedSDK].OAuth2Session.clientID = kVLCBoxClientID;
  45. [BoxSDK sharedSDK].OAuth2Session.clientSecret = kVLCBoxClientSecret;
  46. NSString *token = [SSKeychain passwordForService:kVLCBoxService account:kVLCBoxAccount];
  47. if (token != nil) {
  48. [BoxSDK sharedSDK].OAuth2Session.refreshToken = token;
  49. }
  50. }
  51. - (void)stopSession
  52. {
  53. [_operation cancel];
  54. _offset = 0;
  55. _currentFileList = nil;
  56. }
  57. - (void)logout
  58. {
  59. [SSKeychain deletePasswordForService:kVLCBoxService account:kVLCBoxAccount];
  60. [[BoxSDK sharedSDK].OAuth2Session logout];
  61. [self stopSession];
  62. if ([self.delegate respondsToSelector:@selector(mediaListUpdated)])
  63. [self.delegate mediaListUpdated];
  64. }
  65. - (BOOL)isAuthorized
  66. {
  67. return [[BoxSDK sharedSDK].OAuth2Session isAuthorized];
  68. }
  69. - (void)showAlert:(NSString *)title message:(NSString *)message
  70. {
  71. VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:title
  72. message:message
  73. delegate:nil
  74. cancelButtonTitle:NSLocalizedString(@"BUTTON_OK", nil)
  75. otherButtonTitles:nil];
  76. [alert show];
  77. }
  78. #pragma mark - file management
  79. - (void)requestDirectoryListingAtPath:(NSString *)path
  80. {
  81. //we entered a different folder so discard all current files
  82. if (![path isEqualToString:_folderId])
  83. _currentFileList = nil;
  84. [self listFilesWithID:path];
  85. }
  86. - (BOOL)hasMoreFiles
  87. {
  88. return _offset < _maxOffset;
  89. }
  90. - (void)downloadFileToDocumentFolder:(BoxItem *)file
  91. {
  92. if ([file.type isEqualToString:BoxAPIItemTypeFolder]) return;
  93. if (!_listOfBoxFilesToDownload)
  94. _listOfBoxFilesToDownload = [NSMutableArray new];
  95. [_listOfBoxFilesToDownload addObject:file];
  96. if ([self.delegate respondsToSelector:@selector(numberOfFilesWaitingToBeDownloadedChanged)])
  97. [self.delegate numberOfFilesWaitingToBeDownloadedChanged];
  98. [self _triggerNextDownload];
  99. }
  100. - (void)listFilesWithID:(NSString *)folderId
  101. {
  102. _fileList = nil;
  103. _folderId = folderId;
  104. if (_folderId == nil || [_folderId isEqualToString:@""]) {
  105. _folderId = BoxAPIFolderIDRoot;
  106. }
  107. BoxCollectionBlock success = ^(BoxCollection *collection)
  108. {
  109. _fileList = collection;
  110. [self _listOfGoodFilesAndFolders];
  111. };
  112. BoxAPIJSONFailureBlock failure = ^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSDictionary *JSONDictionary)
  113. {
  114. APLog(@"there was an error getting the files but we don't show an error. this request is used to check if we need to refresh the token");
  115. };
  116. [_operation cancel];
  117. _operation = [[BoxSDK sharedSDK].foldersManager folderItemsWithID:_folderId requestBuilder:nil success:success failure:failure];
  118. }
  119. - (void)streamFile:(BoxFile *)file
  120. {
  121. /* the Box API requires us to set an HTTP header to get the actual URL:
  122. * curl -L https://api.box.com/2.0/files/FILE_ID/content -H "Authorization: Bearer ACCESS_TOKEN"
  123. *
  124. * ... however, libvlc does not support setting custom HTTP headers, so we are resolving the redirect ourselves with a NSURLConnection
  125. * and pass the final location to libvlc, which does not require a custom HTTP header */
  126. NSURL *baseURL = [[[BoxSDK sharedSDK] filesManager] URLWithResource:@"files"
  127. ID:file.modelID
  128. subresource:@"content"
  129. subID:nil];
  130. NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:baseURL
  131. cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
  132. timeoutInterval:60];
  133. [urlRequest setValue:[NSString stringWithFormat:@"Bearer %@", [BoxSDK sharedSDK].OAuth2Session.accessToken] forHTTPHeaderField:@"Authorization"];
  134. NSURLConnection *theTestConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
  135. [theTestConnection start];
  136. }
  137. - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
  138. {
  139. if (response != nil) {
  140. /* we have 1 redirect from the original URL, so as soon as we'd do that,
  141. * we grab the URL and cancel the connection */
  142. NSURL *theActualURL = request.URL;
  143. [connection cancel];
  144. /* now ask VLC to stream the URL we were just passed */
  145. VLCAppDelegate *appDelegate = (VLCAppDelegate *)[UIApplication sharedApplication].delegate;
  146. [appDelegate openMovieFromURL:theActualURL];
  147. }
  148. return request;
  149. }
  150. - (void)_triggerNextDownload
  151. {
  152. if (_listOfBoxFilesToDownload.count > 0 && !_downloadInProgress) {
  153. [self _reallyDownloadFileToDocumentFolder:_listOfBoxFilesToDownload[0]];
  154. [_listOfBoxFilesToDownload removeObjectAtIndex:0];
  155. if ([self.delegate respondsToSelector:@selector(numberOfFilesWaitingToBeDownloadedChanged)])
  156. [self.delegate numberOfFilesWaitingToBeDownloadedChanged];
  157. }
  158. }
  159. - (void)_reallyDownloadFileToDocumentFolder:(BoxFile *)file
  160. {
  161. NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  162. NSString *filePath = [searchPaths[0] stringByAppendingFormat:@"/%@", file.name];
  163. [self loadFile:file intoPath:filePath];
  164. if ([self.delegate respondsToSelector:@selector(operationWithProgressInformationStarted)])
  165. [self.delegate operationWithProgressInformationStarted];
  166. _downloadInProgress = YES;
  167. }
  168. - (BOOL)_supportedFileExtension:(NSString *)filename
  169. {
  170. if ([filename isSupportedMediaFormat] || [filename isSupportedAudioMediaFormat] || [filename isSupportedSubtitleFormat])
  171. return YES;
  172. return NO;
  173. }
  174. //just pick out Directories and supported formats.
  175. //if the resulting list contains less than 10 items try to get more
  176. - (void)_listOfGoodFilesAndFolders
  177. {
  178. NSMutableArray *listOfGoodFilesAndFolders = [NSMutableArray new];
  179. _maxOffset = _fileList.totalCount.intValue;
  180. _offset += _fileList.numberOfEntries;
  181. NSUInteger numberOfEntries = _fileList.numberOfEntries;
  182. for (int i = 0; i < numberOfEntries; i++)
  183. {
  184. BoxModel *boxFile = [_fileList modelAtIndex:i];
  185. BOOL isDirectory = [boxFile.type isEqualToString:BoxAPIItemTypeFolder];
  186. BOOL supportedFile = NO;
  187. if (!isDirectory) {
  188. BoxFile * file = (BoxFile *)boxFile;
  189. supportedFile = [self _supportedFileExtension:[NSString stringWithFormat:@".%@",file.name.lastPathComponent]];
  190. }
  191. if (isDirectory || supportedFile)
  192. [listOfGoodFilesAndFolders addObject:boxFile];
  193. }
  194. _currentFileList = [_currentFileList count] ? [_currentFileList arrayByAddingObjectsFromArray:listOfGoodFilesAndFolders] : [NSArray arrayWithArray:listOfGoodFilesAndFolders];
  195. if ([_currentFileList count] <= 10 && [self hasMoreFiles]) {
  196. [self listFilesWithID:_folderId];
  197. return;
  198. }
  199. APLog(@"found filtered metadata for %lu files", (unsigned long)_currentFileList.count);
  200. if ([self.delegate respondsToSelector:@selector(mediaListUpdated)])
  201. [self.delegate mediaListUpdated];
  202. }
  203. - (void)loadFile:(BoxFile *)file intoPath:(NSString*)destinationPath
  204. {
  205. NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:destinationPath append:NO];
  206. _startDL = [NSDate timeIntervalSinceReferenceDate];
  207. BoxDownloadSuccessBlock successBlock = ^(NSString *downloadedFileID, long long expectedContentLength)
  208. {
  209. [self downloadSuccessful];
  210. };
  211. BoxDownloadFailureBlock failureBlock = ^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error)
  212. {
  213. [self showAlert:NSLocalizedString(@"GDRIVE_ERROR_DOWNLOADING_FILE_TITLE",nil) message:NSLocalizedString(@"GDRIVE_ERROR_DOWNLOADING_FILE",nil)];
  214. [self downloadFailedWithError:error];
  215. };
  216. BoxAPIDataProgressBlock progressBlock = ^(long long expectedTotalBytes, unsigned long long bytesReceived)
  217. {
  218. if ((_lastStatsUpdate > 0 && ([NSDate timeIntervalSinceReferenceDate] - _lastStatsUpdate > .5)) || _lastStatsUpdate <= 0) {
  219. [self calculateRemainingTime:(CGFloat)bytesReceived expectedDownloadSize:(CGFloat)expectedTotalBytes];
  220. _lastStatsUpdate = [NSDate timeIntervalSinceReferenceDate];
  221. }
  222. CGFloat progress = (CGFloat)bytesReceived / (CGFloat)expectedTotalBytes;
  223. if ([self.delegate respondsToSelector:@selector(currentProgressInformation:)])
  224. [self.delegate currentProgressInformation:progress];
  225. };
  226. [[BoxSDK sharedSDK].filesManager downloadFileWithID:file.modelID outputStream:outputStream requestBuilder:nil success:successBlock failure:failureBlock progress:progressBlock];
  227. }
  228. - (void)calculateRemainingTime:(CGFloat)receivedDataSize expectedDownloadSize:(CGFloat)expectedDownloadSize
  229. {
  230. CGFloat lastSpeed = receivedDataSize / ([NSDate timeIntervalSinceReferenceDate] - _startDL);
  231. CGFloat smoothingFactor = 0.005;
  232. _averageSpeed = isnan(_averageSpeed) ? lastSpeed : smoothingFactor * lastSpeed + (1 - smoothingFactor) * _averageSpeed;
  233. CGFloat remainingInSeconds = (expectedDownloadSize - receivedDataSize) / _averageSpeed;
  234. NSDate *date = [NSDate dateWithTimeIntervalSince1970:remainingInSeconds];
  235. NSDateFormatter *formatter = [NSDateFormatter new];
  236. [formatter setDateFormat:@"HH:mm:ss"];
  237. [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
  238. NSString *remainingTime = [formatter stringFromDate:date];
  239. if ([self.delegate respondsToSelector:@selector(updateRemainingTime:)])
  240. [self.delegate updateRemainingTime:remainingTime];
  241. }
  242. - (void)downloadSuccessful
  243. {
  244. /* update library now that we got a file */
  245. APLog(@"BoxFile download was successful");
  246. VLCAppDelegate *appDelegate = (VLCAppDelegate *) [UIApplication sharedApplication].delegate;
  247. [appDelegate performSelectorOnMainThread:@selector(updateMediaList) withObject:nil waitUntilDone:NO];
  248. if ([self.delegate respondsToSelector:@selector(operationWithProgressInformationStopped)])
  249. [self.delegate operationWithProgressInformationStopped];
  250. _downloadInProgress = NO;
  251. [self _triggerNextDownload];
  252. }
  253. - (void)downloadFailedWithError:(NSError*)error
  254. {
  255. APLog(@"BoxFile download failed with error %li", (long)error.code);
  256. if ([self.delegate respondsToSelector:@selector(operationWithProgressInformationStopped)])
  257. [self.delegate operationWithProgressInformationStopped];
  258. _downloadInProgress = NO;
  259. [self _triggerNextDownload];
  260. }
  261. #pragma mark - VLC internal communication and delegate
  262. - (NSArray *)currentListFiles
  263. {
  264. return _currentFileList;
  265. }
  266. - (NSInteger)numberOfFilesWaitingToBeDownloaded
  267. {
  268. if (_listOfBoxFilesToDownload)
  269. return _listOfBoxFilesToDownload.count;
  270. return 0;
  271. }
  272. @end