VLCHTTPConnection.m 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. /*****************************************************************************
  2. * VLCHTTPConnection.m
  3. * VLC for iOS
  4. *****************************************************************************
  5. * Copyright (c) 2013-2015 VideoLAN. All rights reserved.
  6. * $Id$
  7. *
  8. * Authors: Felix Paul Kühne <fkuehne # videolan.org>
  9. * Pierre Sagaspe <pierre.sagaspe # me.com>
  10. * Carola Nitz <caro # videolan.org>
  11. * Jean-Baptiste Kempf <jb # videolan.org>
  12. *
  13. * Refer to the COPYING file of the official project for license.
  14. *****************************************************************************/
  15. #import "VLCActivityManager.h"
  16. #import "VLCHTTPConnection.h"
  17. #import "MultipartFormDataParser.h"
  18. #import "HTTPMessage.h"
  19. #import "HTTPDataResponse.h"
  20. #import "HTTPFileResponse.h"
  21. #import "MultipartMessageHeaderField.h"
  22. #import "HTTPDynamicFileResponse.h"
  23. #import "NSString+SupportedMedia.h"
  24. #import "UIDevice+VLC.h"
  25. #import "VLCHTTPUploaderController.h"
  26. #if TARGET_OS_IOS
  27. #import "VLCThumbnailsCache.h"
  28. #endif
  29. @interface VLCHTTPConnection()
  30. {
  31. MultipartFormDataParser *_parser;
  32. NSFileHandle *_storeFile;
  33. NSString *_filepath;
  34. UInt64 _contentLength;
  35. UInt64 _receivedContent;
  36. #if TARGET_OS_TV
  37. BOOL _playbackStarted;
  38. #endif
  39. }
  40. @end
  41. @implementation VLCHTTPConnection
  42. - (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path
  43. {
  44. // Add support for POST
  45. if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.json"])
  46. return YES;
  47. return [super supportsMethod:method atPath:path];
  48. }
  49. - (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path
  50. {
  51. // Inform HTTP server that we expect a body to accompany a POST request
  52. if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.json"]) {
  53. // here we need to make sure, boundary is set in header
  54. NSString* contentType = [request headerField:@"Content-Type"];
  55. NSUInteger paramsSeparator = [contentType rangeOfString:@";"].location;
  56. if (NSNotFound == paramsSeparator)
  57. return NO;
  58. if (paramsSeparator >= contentType.length - 1)
  59. return NO;
  60. NSString* type = [contentType substringToIndex:paramsSeparator];
  61. if (![type isEqualToString:@"multipart/form-data"]) {
  62. // we expect multipart/form-data content type
  63. return NO;
  64. }
  65. // enumerate all params in content-type, and find boundary there
  66. NSArray* params = [[contentType substringFromIndex:paramsSeparator + 1] componentsSeparatedByString:@";"];
  67. NSUInteger count = params.count;
  68. for (NSUInteger i = 0; i < count; i++) {
  69. NSString *param = params[i];
  70. paramsSeparator = [param rangeOfString:@"="].location;
  71. if ((NSNotFound == paramsSeparator) || paramsSeparator >= param.length - 1)
  72. continue;
  73. NSString* paramName = [param substringWithRange:NSMakeRange(1, paramsSeparator-1)];
  74. NSString* paramValue = [param substringFromIndex:paramsSeparator+1];
  75. if ([paramName isEqualToString: @"boundary"])
  76. // let's separate the boundary from content-type, to make it more handy to handle
  77. [request setHeaderField:@"boundary" value:paramValue];
  78. }
  79. // check if boundary specified
  80. if (nil == [request headerField:@"boundary"])
  81. return NO;
  82. return YES;
  83. }
  84. return [super expectsRequestBodyFromMethod:method atPath:path];
  85. }
  86. - (NSObject<HTTPResponse> *)_httpPOSTresponseUploadJSON
  87. {
  88. return [[HTTPDataResponse alloc] initWithData:[@"\"OK\"" dataUsingEncoding:NSUTF8StringEncoding]];
  89. }
  90. #if TARGET_OS_IOS
  91. - (NSObject<HTTPResponse> *)_httpGETDownloadForPath:(NSString *)path
  92. {
  93. NSString *filePath = [[path stringByReplacingOccurrencesOfString:@"/download/" withString:@""]stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  94. HTTPFileResponse *fileResponse = [[HTTPFileResponse alloc] initWithFilePath:filePath forConnection:self];
  95. fileResponse.contentType = @"application/octet-stream";
  96. return fileResponse;
  97. }
  98. - (NSObject<HTTPResponse> *)_httpGETThumbnailForPath:(NSString *)path
  99. {
  100. NSString *filePath = [[path stringByReplacingOccurrencesOfString:@"/thumbnail/" withString:@""]stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  101. filePath = [filePath stringByReplacingOccurrencesOfString:@".png" withString:@""];
  102. NSManagedObjectContext *moc = [[MLMediaLibrary sharedMediaLibrary] managedObjectContext];
  103. if (moc) {
  104. NSPersistentStoreCoordinator *psc = [moc persistentStoreCoordinator];
  105. if (psc) {
  106. NSManagedObject *mo = nil;
  107. @try {
  108. mo = [moc existingObjectWithID:[psc managedObjectIDForURIRepresentation:[NSURL URLWithString:filePath]] error:nil];
  109. }@catch (NSException *exeption) {
  110. // somebody gave us a malformed or stale URIRepresentation
  111. }
  112. NSData *theData;
  113. NSString *contentType;
  114. /* devices category 3 and faster include HW accelerated JPEG encoding
  115. * so we can make our transfers faster by using waaay smaller images */
  116. if ([[UIDevice currentDevice] speedCategory] < 3) {
  117. theData = UIImagePNGRepresentation([VLCThumbnailsCache thumbnailForManagedObject:mo]);
  118. contentType = @"image/png";
  119. } else {
  120. theData = UIImageJPEGRepresentation([VLCThumbnailsCache thumbnailForManagedObject:mo], .9);
  121. contentType = @"image/jpg";
  122. }
  123. if (theData) {
  124. HTTPDataResponse *dataResponse = [[HTTPDataResponse alloc] initWithData:theData];
  125. dataResponse.contentType = contentType;
  126. return dataResponse;
  127. }
  128. }
  129. }
  130. }
  131. - (NSObject<HTTPResponse> *)_httpGETLibraryForPath:(NSString *)path
  132. {
  133. NSString *filePath = [self filePathForURI:path];
  134. NSString *documentRoot = [config documentRoot];
  135. NSString *relativePath = [filePath substringFromIndex:[documentRoot length]];
  136. BOOL shouldReturnLibVLCXML = [relativePath isEqualToString:@"/libMediaVLC.xml"];
  137. NSMutableArray *allMedia = [[NSMutableArray alloc] init];
  138. /* add all albums */
  139. NSArray *allAlbums = [MLAlbum allAlbums];
  140. for (MLAlbum *album in allAlbums) {
  141. if (album.name.length > 0 && album.tracks.count > 1)
  142. [allMedia addObject:album];
  143. }
  144. /* add all shows */
  145. NSArray *allShows = [MLShow allShows];
  146. for (MLShow *show in allShows) {
  147. if (show.name.length > 0 && show.episodes.count > 1)
  148. [allMedia addObject:show];
  149. }
  150. /* add all folders*/
  151. NSArray *allFolders = [MLLabel allLabels];
  152. for (MLLabel *folder in allFolders)
  153. [allMedia addObject:folder];
  154. /* add all remaining files */
  155. NSArray *allFiles = [MLFile allFiles];
  156. for (MLFile *file in allFiles) {
  157. if (file.labels.count > 0) continue;
  158. if (!file.isShowEpisode && !file.isAlbumTrack)
  159. [allMedia addObject:file];
  160. else if (file.isShowEpisode) {
  161. if (file.showEpisode.show.episodes.count < 2)
  162. [allMedia addObject:file];
  163. } else if (file.isAlbumTrack) {
  164. if (file.albumTrack.album.tracks.count < 2)
  165. [allMedia addObject:file];
  166. }
  167. }
  168. NSUInteger mediaCount = allMedia.count;
  169. NSMutableArray *mediaInHtml = [[NSMutableArray alloc] initWithCapacity:mediaCount];
  170. NSMutableArray *mediaInXml = [[NSMutableArray alloc] initWithCapacity:mediaCount];
  171. NSString *hostName = [[VLCHTTPUploaderController sharedInstance] hostname];
  172. NSString *duration;
  173. for (NSManagedObject *mo in allMedia) {
  174. if ([mo isKindOfClass:[MLFile class]]) {
  175. MLFile *file = (MLFile *)mo;
  176. duration = [[VLCTime timeWithNumber:file.duration] stringValue];
  177. [mediaInHtml addObject:[NSString stringWithFormat:
  178. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  179. <a href=\"download/%@\" class=\"inner\"> \
  180. <div class=\"down icon\"></div> \
  181. <div class=\"infos\"> \
  182. <span class=\"first-line\">%@</span> \
  183. <span class=\"second-line\">%@ - %0.2f MB</span> \
  184. </div> \
  185. </a> \
  186. </div>",
  187. file.objectID.URIRepresentation,
  188. [file.url.path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
  189. file.title,
  190. duration, (float)(file.fileSizeInBytes / 1e6)]];
  191. if (shouldReturnLibVLCXML) {
  192. NSString *pathSub = [self _checkIfSubtitleWasFound:file.path];
  193. if (pathSub)
  194. pathSub = [NSString stringWithFormat:@"http://%@/download/%@", hostName, pathSub];
  195. [mediaInXml addObject:[NSString stringWithFormat:@"<Media title=\"%@\" thumb=\"http://%@/thumbnail/%@.png\" duration=\"%@\" size=\"%li\" pathfile=\"http://%@/download/%@\" pathSubtitle=\"%@\"/>", file.title, hostName, file.objectID.URIRepresentation.absoluteString, duration, file.fileSizeInBytes, hostName, [file.url.path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding], pathSub]];
  196. }
  197. }
  198. else if ([mo isKindOfClass:[MLShow class]]) {
  199. MLShow *show = (MLShow *)mo;
  200. NSArray *episodes = [show sortedEpisodes];
  201. [mediaInHtml addObject:[NSString stringWithFormat:
  202. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  203. <a href=\"#\" class=\"inner folder\"> \
  204. <div class=\"open icon\"></div> \
  205. <div class=\"infos\"> \
  206. <span class=\"first-line\">%@</span> \
  207. <span class=\"second-line\">%lu items</span> \
  208. </div> \
  209. </a> \
  210. <div class=\"content\">",
  211. mo.objectID.URIRepresentation,
  212. show.name,
  213. (unsigned long)[episodes count]]];
  214. for (MLShowEpisode *showEp in episodes) {
  215. MLFile *anyFileFromEpisode = (MLFile *)[[showEp files] anyObject];
  216. duration = [[VLCTime timeWithNumber:[anyFileFromEpisode duration]] stringValue];
  217. [mediaInHtml addObject:[NSString stringWithFormat:
  218. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  219. <a href=\"download/%@\" class=\"inner\"> \
  220. <div class=\"down icon\"></div> \
  221. <div class=\"infos\"> \
  222. <span class=\"first-line\">S%@E%@ - %@</span> \
  223. <span class=\"second-line\">%@ - %0.2f MB</span> \
  224. </div> \
  225. </a> \
  226. </div>",
  227. showEp.objectID.URIRepresentation,
  228. [anyFileFromEpisode.url.path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
  229. showEp.seasonNumber,
  230. showEp.episodeNumber,
  231. showEp.name,
  232. duration, (float)([anyFileFromEpisode fileSizeInBytes] / 1e6)]];
  233. if (shouldReturnLibVLCXML) {
  234. NSString *pathSub = [self _checkIfSubtitleWasFound:[anyFileFromEpisode path]];
  235. if (![pathSub isEqualToString:@""])
  236. pathSub = [NSString stringWithFormat:@"http://%@/download/%@", hostName, pathSub];
  237. [mediaInXml addObject:[NSString stringWithFormat:@"<Media title=\"%@ - S%@E%@\" thumb=\"http://%@/thumbnail/%@.png\" duration=\"%@\" size=\"%li\" pathfile=\"http://%@/download/%@\" pathSubtitle=\"%@\"/>", show.name, showEp.seasonNumber, showEp.episodeNumber, hostName, showEp.objectID.URIRepresentation, duration, [anyFileFromEpisode fileSizeInBytes], hostName, [anyFileFromEpisode.url.path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding], pathSub]];
  238. }
  239. }
  240. [mediaInHtml addObject:@"</div></div>"];
  241. } else if ([mo isKindOfClass:[MLLabel class]]) {
  242. MLLabel *label = (MLLabel *)mo;
  243. NSArray *folderItems = [label sortedFolderItems];
  244. [mediaInHtml addObject:[NSString stringWithFormat:
  245. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  246. <a href=\"#\" class=\"inner folder\"> \
  247. <div class=\"open icon\"></div> \
  248. <div class=\"infos\"> \
  249. <span class=\"first-line\">%@</span> \
  250. <span class=\"second-line\">%lu items</span> \
  251. </div> \
  252. </a> \
  253. <div class=\"content\">",
  254. label.objectID.URIRepresentation,
  255. label.name,
  256. (unsigned long)folderItems.count]];
  257. for (MLFile *file in folderItems) {
  258. duration = [[VLCTime timeWithNumber:[file duration]] stringValue];
  259. [mediaInHtml addObject:[NSString stringWithFormat:
  260. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  261. <a href=\"download/%@\" class=\"inner\"> \
  262. <div class=\"down icon\"></div> \
  263. <div class=\"infos\"> \
  264. <span class=\"first-line\">%@</span> \
  265. <span class=\"second-line\">%@ - %0.2f MB</span> \
  266. </div> \
  267. </a> \
  268. </div>",
  269. file.objectID.URIRepresentation,
  270. [file.url.path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
  271. file.title,
  272. duration, (float)(file.fileSizeInBytes / 1e6)]];
  273. if (shouldReturnLibVLCXML) {
  274. NSString *pathSub = [self _checkIfSubtitleWasFound:file.path];
  275. if (pathSub)
  276. pathSub = [NSString stringWithFormat:@"http://%@/download/%@", hostName, pathSub];
  277. [mediaInXml addObject:[NSString stringWithFormat:@"<Media title=\"%@\" thumb=\"http://%@/thumbnail/%@.png\" duration=\"%@\" size=\"%li\" pathfile=\"http://%@/download/%@\" pathSubtitle=\"%@\"/>", file.title, hostName, file.objectID.URIRepresentation, duration, file.fileSizeInBytes, hostName, [file.url.path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding], pathSub]];
  278. }
  279. }
  280. [mediaInHtml addObject:@"</div></div>"];
  281. } else if ([mo isKindOfClass:[MLAlbum class]]) {
  282. MLAlbum *album = (MLAlbum *)mo;
  283. NSArray *albumTracks = [album sortedTracks];
  284. [mediaInHtml addObject:[NSString stringWithFormat:
  285. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  286. <a href=\"#\" class=\"inner folder\"> \
  287. <div class=\"open icon\"></div> \
  288. <div class=\"infos\"> \
  289. <span class=\"first-line\">%@</span> \
  290. <span class=\"second-line\">%lu items</span> \
  291. </div> \
  292. </a> \
  293. <div class=\"content\">",
  294. album.objectID.URIRepresentation,
  295. album.name,
  296. (unsigned long)albumTracks.count]];
  297. for (MLAlbumTrack *track in albumTracks) {
  298. MLFile *anyFileFromTrack = [track anyFileFromTrack];
  299. duration = [[VLCTime timeWithNumber:[anyFileFromTrack duration]] stringValue];
  300. [mediaInHtml addObject:[NSString stringWithFormat:
  301. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  302. <a href=\"download/%@\" class=\"inner\"> \
  303. <div class=\"down icon\"></div> \
  304. <div class=\"infos\"> \
  305. <span class=\"first-line\">%@</span> \
  306. <span class=\"second-line\">%@ - %0.2f MB</span> \
  307. </div> \
  308. </a> \
  309. </div>",
  310. track.objectID.URIRepresentation,
  311. [anyFileFromTrack.url.path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
  312. track.title,
  313. duration, (float)([anyFileFromTrack fileSizeInBytes] / 1e6)]];
  314. if (shouldReturnLibVLCXML)
  315. [mediaInXml addObject:[NSString stringWithFormat:@"<Media title=\"%@\" thumb=\"http://%@/thumbnail/%@.png\" duration=\"%@\" size=\"%li\" pathfile=\"http://%@/download/%@\" pathSubtitle=\"\"/>", track.title, hostName, track.objectID.URIRepresentation, duration, [anyFileFromTrack fileSizeInBytes], hostName, [anyFileFromTrack.url.path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
  316. }
  317. [mediaInHtml addObject:@"</div></div>"];
  318. }
  319. }
  320. UIDevice *currentDevice = [UIDevice currentDevice];
  321. NSString *deviceModel = [currentDevice model];
  322. NSDictionary *replacementDict;
  323. HTTPDynamicFileResponse *fileResponse;
  324. if (shouldReturnLibVLCXML) {
  325. replacementDict = @{@"FILES" : [mediaInXml componentsJoinedByString:@" "],
  326. @"NB_FILE" : [NSString stringWithFormat:@"%li", (unsigned long)mediaInXml.count],
  327. @"LIB_TITLE" : [currentDevice name]};
  328. fileResponse = [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
  329. forConnection:self
  330. separator:@"%%"
  331. replacementDictionary:replacementDict];
  332. fileResponse.contentType = @"application/xml";
  333. } else {
  334. replacementDict = @{@"FILES" : [mediaInHtml componentsJoinedByString:@" "],
  335. @"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE", nil),
  336. @"WEBINTF_DROPFILES" : NSLocalizedString(@"WEBINTF_DROPFILES", nil),
  337. @"WEBINTF_DROPFILES_LONG" : [NSString stringWithFormat:NSLocalizedString(@"WEBINTF_DROPFILES_LONG", nil), deviceModel],
  338. @"WEBINTF_DOWNLOADFILES" : NSLocalizedString(@"WEBINTF_DOWNLOADFILES", nil),
  339. @"WEBINTF_DOWNLOADFILES_LONG" : [NSString stringWithFormat: NSLocalizedString(@"WEBINTF_DOWNLOADFILES_LONG", nil), deviceModel]};
  340. fileResponse = [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
  341. forConnection:self
  342. separator:@"%%"
  343. replacementDictionary:replacementDict];
  344. fileResponse.contentType = @"text/html";
  345. }
  346. return fileResponse;
  347. }
  348. #else
  349. - (NSObject<HTTPResponse> *)_httpGETLibraryForPath:(NSString *)path
  350. {
  351. UIDevice *currentDevice = [UIDevice currentDevice];
  352. NSString *deviceModel = [currentDevice model];
  353. NSString *filePath = [self filePathForURI:path];
  354. NSString *documentRoot = [config documentRoot];
  355. NSString *relativePath = [filePath substringFromIndex:[documentRoot length]];
  356. NSDictionary *replacementDict = @{@"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE_ATV", nil),
  357. @"WEBINTF_DROPFILES" : NSLocalizedString(@"WEBINTF_DROPFILES", nil),
  358. @"WEBINTF_DROPFILES_LONG" : [NSString stringWithFormat:NSLocalizedString(@"WEBINTF_DROPFILES_LONG_ATV", nil), deviceModel]};
  359. HTTPDynamicFileResponse *fileResponse;
  360. if ([relativePath isEqualToString:@"/index.html"]) {
  361. fileResponse = [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
  362. forConnection:self
  363. separator:@"%%"
  364. replacementDictionary:replacementDict];
  365. fileResponse.contentType = @"text/html";
  366. }
  367. return fileResponse;
  368. }
  369. #endif
  370. - (NSObject<HTTPResponse> *)_httpGETCSSForPath:(NSString *)path
  371. {
  372. #if TARGET_OS_IOS
  373. NSDictionary *replacementDict = @{@"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE", nil)};
  374. #else
  375. NSDictionary *replacementDict = @{@"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE_ATV", nil)};
  376. #endif
  377. HTTPDynamicFileResponse *fileResponse = [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
  378. forConnection:self
  379. separator:@"%%"
  380. replacementDictionary:replacementDict];
  381. fileResponse.contentType = @"text/css";
  382. return fileResponse;
  383. }
  384. - (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path
  385. {
  386. if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.json"])
  387. return [self _httpPOSTresponseUploadJSON];
  388. #if TARGET_OS_IOS
  389. if ([path hasPrefix:@"/download/"]) {
  390. return [self _httpGETDownloadForPath:path];
  391. }
  392. if ([path hasPrefix:@"/thumbnail"]) {
  393. return [self _httpGETThumbnailForPath:path];
  394. }
  395. #endif
  396. NSString *filePath = [self filePathForURI:path];
  397. NSString *documentRoot = [config documentRoot];
  398. NSString *relativePath = [filePath substringFromIndex:[documentRoot length]];
  399. if ([relativePath isEqualToString:@"/index.html"] || [relativePath isEqualToString:@"/libMediaVLC.xml"]) {
  400. return [self _httpGETLibraryForPath:path];
  401. } else if ([relativePath isEqualToString:@"/style.css"]) {
  402. return [self _httpGETCSSForPath:path];
  403. }
  404. return [super httpResponseForMethod:method URI:path];
  405. }
  406. - (void)prepareForBodyWithSize:(UInt64)contentLength
  407. {
  408. // set up mime parser
  409. NSString* boundary = [request headerField:@"boundary"];
  410. _parser = [[MultipartFormDataParser alloc] initWithBoundary:boundary formEncoding:NSUTF8StringEncoding];
  411. _parser.delegate = self;
  412. APLog(@"expecting file of size %lli kB", contentLength / 1024);
  413. _contentLength = contentLength;
  414. }
  415. - (void)processBodyData:(NSData *)postDataChunk
  416. {
  417. /* append data to the parser. It will invoke callbacks to let us handle
  418. * parsed data. */
  419. [_parser appendData:postDataChunk];
  420. _receivedContent += postDataChunk.length;
  421. long long percentage = ((_receivedContent * 100) / _contentLength);
  422. APLog(@"received %lli kB (%lli %%)", _receivedContent / 1024, percentage);
  423. #if TARGET_OS_TV
  424. if (!_playbackStarted) {
  425. if (percentage >= 10) {
  426. _playbackStarted = YES;
  427. APLog(@"Starting playback of %@", _filepath);
  428. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  429. [vpc playURL:[NSURL fileURLWithPath:_filepath] successCallback:nil errorCallback:nil];
  430. VLCFullscreenMovieTVViewController *moviewVC = [VLCFullscreenMovieTVViewController fullscreenMovieTVViewController];
  431. [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:moviewVC
  432. animated:YES
  433. completion:nil];
  434. }
  435. }
  436. #endif
  437. }
  438. //-----------------------------------------------------------------
  439. #pragma mark multipart form data parser delegate
  440. - (void)processStartOfPartWithHeader:(MultipartMessageHeader*) header
  441. {
  442. /* in this sample, we are not interested in parts, other then file parts.
  443. * check content disposition to find out filename */
  444. MultipartMessageHeaderField* disposition = (header.fields)[@"Content-Disposition"];
  445. NSString* filename = [(disposition.params)[@"filename"] lastPathComponent];
  446. if ((nil == filename) || [filename isEqualToString: @""]) {
  447. // it's either not a file part, or
  448. // an empty form sent. we won't handle it.
  449. return;
  450. }
  451. // create the path where to store the media temporarily
  452. NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  453. NSString *uploadDirPath = [searchPaths[0] stringByAppendingPathComponent:@"Upload"];
  454. NSFileManager *fileManager = [NSFileManager defaultManager];
  455. BOOL isDir = YES;
  456. if (![fileManager fileExistsAtPath:uploadDirPath isDirectory:&isDir])
  457. [fileManager createDirectoryAtPath:uploadDirPath withIntermediateDirectories:YES attributes:nil error:nil];
  458. _filepath = [uploadDirPath stringByAppendingPathComponent: filename];
  459. NSNumber *freeSpace = [[UIDevice currentDevice] freeDiskspace];
  460. if (_contentLength >= freeSpace.longLongValue) {
  461. /* avoid deadlock since we are on a background thread */
  462. [self performSelectorOnMainThread:@selector(notifyUserAboutEndOfFreeStorage:) withObject:filename waitUntilDone:NO];
  463. [self handleResourceNotFound];
  464. [self stop];
  465. return;
  466. }
  467. APLog(@"Saving file to %@", _filepath);
  468. if (![fileManager createDirectoryAtPath:uploadDirPath withIntermediateDirectories:true attributes:nil error:nil])
  469. APLog(@"Could not create directory at path: %@", _filepath);
  470. if (![fileManager createFileAtPath:_filepath contents:nil attributes:nil])
  471. APLog(@"Could not create file at path: %@", _filepath);
  472. _storeFile = [NSFileHandle fileHandleForWritingAtPath:_filepath];
  473. VLCActivityManager *activityManager = [VLCActivityManager defaultManager];
  474. [activityManager networkActivityStarted];
  475. [activityManager disableIdleTimer];
  476. }
  477. - (void)notifyUserAboutEndOfFreeStorage:(NSString *)filename
  478. {
  479. #if TARGET_OS_IOS
  480. VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"DISK_FULL", nil)
  481. message:[NSString stringWithFormat:
  482. NSLocalizedString(@"DISK_FULL_FORMAT", nil),
  483. filename,
  484. [[UIDevice currentDevice] model]]
  485. delegate:self
  486. cancelButtonTitle:NSLocalizedString(@"BUTTON_OK", nil)
  487. otherButtonTitles:nil];
  488. [alert show];
  489. #else
  490. UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"DISK_FULL", nil)
  491. message:[NSString stringWithFormat:
  492. NSLocalizedString(@"DISK_FULL_FORMAT", nil),
  493. filename,
  494. [[UIDevice currentDevice] model]]
  495. preferredStyle:UIAlertControllerStyleAlert];
  496. [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_CANCEL", nil)
  497. style:UIAlertActionStyleCancel
  498. handler:nil]];
  499. [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
  500. #endif
  501. }
  502. - (void)processContent:(NSData*)data WithHeader:(MultipartMessageHeader*) header
  503. {
  504. // here we just write the output from parser to the file.
  505. if (_storeFile) {
  506. @try {
  507. [_storeFile writeData:data];
  508. }
  509. @catch (NSException *exception) {
  510. APLog(@"File to write further data because storage is full.");
  511. [_storeFile closeFile];
  512. _storeFile = nil;
  513. /* don't block */
  514. [self performSelector:@selector(stop) withObject:nil afterDelay:0.1];
  515. }
  516. }
  517. }
  518. - (void)processEndOfPartWithHeader:(MultipartMessageHeader*)header
  519. {
  520. // as the file part is over, we close the file.
  521. APLog(@"closing file");
  522. [_storeFile closeFile];
  523. _storeFile = nil;
  524. }
  525. - (BOOL)shouldDie
  526. {
  527. if (_filepath) {
  528. if (_filepath.length > 0)
  529. [[VLCHTTPUploaderController sharedInstance] moveFileFrom:_filepath];
  530. }
  531. return [super shouldDie];
  532. }
  533. #pragma mark subtitle
  534. - (NSMutableArray *)_listOfSubtitles
  535. {
  536. NSMutableArray *listOfSubtitles = [[NSMutableArray alloc] init];
  537. NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
  538. NSArray *allFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsDirectory error:nil];
  539. NSString *filePath;
  540. NSUInteger count = allFiles.count;
  541. for (NSUInteger i = 0; i < count; i++) {
  542. filePath = [[NSString stringWithFormat:@"%@/%@", documentsDirectory, allFiles[i]] stringByReplacingOccurrencesOfString:@"file://"withString:@""];
  543. if ([filePath isSupportedSubtitleFormat])
  544. [listOfSubtitles addObject:filePath];
  545. }
  546. return listOfSubtitles;
  547. }
  548. - (NSString *)_checkIfSubtitleWasFound:(NSString *)filePath
  549. {
  550. NSString *subtitlePath;
  551. NSString *fileSub;
  552. NSString *currentPath;
  553. NSString *fileName = [[filePath lastPathComponent] stringByDeletingPathExtension];
  554. if (fileName == nil)
  555. return nil;
  556. NSMutableArray *listOfSubtitles = [self _listOfSubtitles];
  557. NSUInteger count = listOfSubtitles.count;
  558. for (NSUInteger i = 0; i < count; i++) {
  559. currentPath = listOfSubtitles[i];
  560. fileSub = [NSString stringWithFormat:@"%@", currentPath];
  561. if ([fileSub rangeOfString:fileName].location != NSNotFound)
  562. subtitlePath = currentPath;
  563. }
  564. return subtitlePath;
  565. }
  566. @end