VLCHTTPConnection.m 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833
  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 "HTTPErrorResponse.h"
  24. #import "NSString+SupportedMedia.h"
  25. #import "UIDevice+VLC.h"
  26. #import "VLCHTTPUploaderController.h"
  27. #if TARGET_OS_IOS
  28. #import "VLCThumbnailsCache.h"
  29. #endif
  30. #if TARGET_OS_TV
  31. #import "VLCPlayerControlWebSocket.h"
  32. #endif
  33. @interface VLCHTTPConnection()
  34. {
  35. MultipartFormDataParser *_parser;
  36. NSFileHandle *_storeFile;
  37. NSString *_filepath;
  38. UInt64 _contentLength;
  39. UInt64 _receivedContent;
  40. #if TARGET_OS_TV
  41. NSMutableArray *_receivedFiles;
  42. #endif
  43. }
  44. @end
  45. @implementation VLCHTTPConnection
  46. - (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path
  47. {
  48. // Add support for POST
  49. if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.json"])
  50. return YES;
  51. return [super supportsMethod:method atPath:path];
  52. }
  53. - (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path
  54. {
  55. // Inform HTTP server that we expect a body to accompany a POST request
  56. if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.json"]) {
  57. // here we need to make sure, boundary is set in header
  58. NSString* contentType = [request headerField:@"Content-Type"];
  59. NSUInteger paramsSeparator = [contentType rangeOfString:@";"].location;
  60. if (NSNotFound == paramsSeparator)
  61. return NO;
  62. if (paramsSeparator >= contentType.length - 1)
  63. return NO;
  64. NSString* type = [contentType substringToIndex:paramsSeparator];
  65. if (![type isEqualToString:@"multipart/form-data"]) {
  66. // we expect multipart/form-data content type
  67. return NO;
  68. }
  69. // enumerate all params in content-type, and find boundary there
  70. NSArray* params = [[contentType substringFromIndex:paramsSeparator + 1] componentsSeparatedByString:@";"];
  71. NSUInteger count = params.count;
  72. for (NSUInteger i = 0; i < count; i++) {
  73. NSString *param = params[i];
  74. paramsSeparator = [param rangeOfString:@"="].location;
  75. if ((NSNotFound == paramsSeparator) || paramsSeparator >= param.length - 1)
  76. continue;
  77. NSString* paramName = [param substringWithRange:NSMakeRange(1, paramsSeparator-1)];
  78. NSString* paramValue = [param substringFromIndex:paramsSeparator+1];
  79. if ([paramName isEqualToString: @"boundary"])
  80. // let's separate the boundary from content-type, to make it more handy to handle
  81. [request setHeaderField:@"boundary" value:paramValue];
  82. }
  83. // check if boundary specified
  84. if (nil == [request headerField:@"boundary"])
  85. return NO;
  86. return YES;
  87. }
  88. return [super expectsRequestBodyFromMethod:method atPath:path];
  89. }
  90. - (NSObject<HTTPResponse> *)_httpPOSTresponseUploadJSON
  91. {
  92. return [[HTTPDataResponse alloc] initWithData:[@"\"OK\"" dataUsingEncoding:NSUTF8StringEncoding]];
  93. }
  94. - (BOOL)fileIsInDocumentFolder:(NSString*)filepath
  95. {
  96. NSError *error;
  97. NSURLRelationship relationship;
  98. NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  99. NSString *directoryPath = [searchPaths firstObject];
  100. [[NSFileManager defaultManager] getRelationship:&relationship ofDirectoryAtURL:[NSURL fileURLWithPath:directoryPath] toItemAtURL:[NSURL fileURLWithPath:filepath] error:&error];
  101. return relationship == NSURLRelationshipContains;
  102. }
  103. #if TARGET_OS_IOS
  104. - (NSObject<HTTPResponse> *)_httpGETDownloadForPath:(NSString *)path
  105. {
  106. NSString *filePath = [[path stringByReplacingOccurrencesOfString:@"/download/" withString:@""]stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  107. if (![self fileIsInDocumentFolder:filePath]) {
  108. //return nil which gets handled as resource not found
  109. return nil;
  110. }
  111. HTTPFileResponse *fileResponse = [[HTTPFileResponse alloc] initWithFilePath:filePath forConnection:self];
  112. fileResponse.contentType = @"application/octet-stream";
  113. return fileResponse;
  114. }
  115. - (NSObject<HTTPResponse> *)_httpGETThumbnailForPath:(NSString *)path
  116. {
  117. NSString *filePath = [[path stringByReplacingOccurrencesOfString:@"/thumbnail/" withString:@""]stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  118. filePath = [filePath stringByReplacingOccurrencesOfString:@".png" withString:@""];
  119. NSManagedObjectContext *moc = [[MLMediaLibrary sharedMediaLibrary] managedObjectContext];
  120. if (moc) {
  121. NSPersistentStoreCoordinator *psc = [moc persistentStoreCoordinator];
  122. if (psc) {
  123. NSManagedObject *mo = nil;
  124. @try {
  125. mo = [moc existingObjectWithID:[psc managedObjectIDForURIRepresentation:[NSURL URLWithString:filePath]] error:nil];
  126. }@catch (NSException *exeption) {
  127. return [[HTTPErrorResponse alloc] initWithErrorCode:404];
  128. }
  129. NSData *theData;
  130. NSString *contentType;
  131. /* devices category 3 and faster include HW accelerated JPEG encoding
  132. * so we can make our transfers faster by using waaay smaller images */
  133. if ([[UIDevice currentDevice] VLCSpeedCategory] < 3) {
  134. theData = UIImagePNGRepresentation([VLCThumbnailsCache thumbnailForManagedObject:mo]);
  135. contentType = @"image/png";
  136. } else {
  137. theData = UIImageJPEGRepresentation([VLCThumbnailsCache thumbnailForManagedObject:mo], .9);
  138. contentType = @"image/jpg";
  139. }
  140. if (theData) {
  141. HTTPDataResponse *dataResponse = [[HTTPDataResponse alloc] initWithData:theData];
  142. dataResponse.contentType = contentType;
  143. return dataResponse;
  144. }
  145. }
  146. }
  147. return [[HTTPErrorResponse alloc] initWithErrorCode:404];
  148. }
  149. - (NSObject<HTTPResponse> *)_httpGETLibraryForPath:(NSString *)path
  150. {
  151. NSString *filePath = [self filePathForURI:path];
  152. NSString *documentRoot = [config documentRoot];
  153. NSString *relativePath = [filePath substringFromIndex:[documentRoot length]];
  154. BOOL shouldReturnLibVLCXML = [relativePath isEqualToString:@"/libMediaVLC.xml"];
  155. NSMutableArray *allMedia = [[NSMutableArray alloc] init];
  156. /* add all albums */
  157. NSArray *allAlbums = [MLAlbum allAlbums];
  158. for (MLAlbum *album in allAlbums) {
  159. if (album.name.length > 0 && album.tracks.count > 1)
  160. [allMedia addObject:album];
  161. }
  162. /* add all shows */
  163. NSArray *allShows = [MLShow allShows];
  164. for (MLShow *show in allShows) {
  165. if (show.name.length > 0 && show.episodes.count > 1)
  166. [allMedia addObject:show];
  167. }
  168. /* add all folders*/
  169. NSArray *allFolders = [MLLabel allLabels];
  170. for (MLLabel *folder in allFolders)
  171. [allMedia addObject:folder];
  172. /* add all remaining files */
  173. NSArray *allFiles = [MLFile allFiles];
  174. for (MLFile *file in allFiles) {
  175. if (file.labels.count > 0) continue;
  176. if (!file.isShowEpisode && !file.isAlbumTrack)
  177. [allMedia addObject:file];
  178. else if (file.isShowEpisode) {
  179. if (file.showEpisode.show.episodes.count < 2)
  180. [allMedia addObject:file];
  181. } else if (file.isAlbumTrack) {
  182. if (file.albumTrack.album.tracks.count < 2)
  183. [allMedia addObject:file];
  184. }
  185. }
  186. NSUInteger mediaCount = allMedia.count;
  187. NSMutableArray *mediaInHtml = [[NSMutableArray alloc] initWithCapacity:mediaCount];
  188. NSMutableArray *mediaInXml = [[NSMutableArray alloc] initWithCapacity:mediaCount];
  189. NSString *hostName = [[VLCHTTPUploaderController sharedInstance] hostname];
  190. NSString *duration;
  191. for (NSManagedObject *mo in allMedia) {
  192. if ([mo isKindOfClass:[MLFile class]]) {
  193. MLFile *file = (MLFile *)mo;
  194. duration = [[VLCTime timeWithNumber:file.duration] stringValue];
  195. [mediaInHtml addObject:[NSString stringWithFormat:
  196. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  197. <a href=\"download/%@\" class=\"inner\"> \
  198. <div class=\"down icon\"></div> \
  199. <div class=\"infos\"> \
  200. <span class=\"first-line\">%@</span> \
  201. <span class=\"second-line\">%@ - %0.2f MB</span> \
  202. </div> \
  203. </a> \
  204. </div>",
  205. file.objectID.URIRepresentation,
  206. [file.url.path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
  207. file.title,
  208. duration, (float)(file.fileSizeInBytes / 1e6)]];
  209. if (shouldReturnLibVLCXML) {
  210. NSString *pathSub = [self _checkIfSubtitleWasFound:file.path];
  211. if (pathSub)
  212. pathSub = [NSString stringWithFormat:@"http://%@/download/%@", hostName, pathSub];
  213. [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]];
  214. }
  215. }
  216. else if ([mo isKindOfClass:[MLShow class]]) {
  217. MLShow *show = (MLShow *)mo;
  218. NSArray *episodes = [show sortedEpisodes];
  219. [mediaInHtml addObject:[NSString stringWithFormat:
  220. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  221. <a href=\"#\" class=\"inner folder\"> \
  222. <div class=\"open icon\"></div> \
  223. <div class=\"infos\"> \
  224. <span class=\"first-line\">%@</span> \
  225. <span class=\"second-line\">%lu items</span> \
  226. </div> \
  227. </a> \
  228. <div class=\"content\">",
  229. mo.objectID.URIRepresentation,
  230. show.name,
  231. (unsigned long)[episodes count]]];
  232. for (MLShowEpisode *showEp in episodes) {
  233. MLFile *anyFileFromEpisode = (MLFile *)[[showEp files] anyObject];
  234. duration = [[VLCTime timeWithNumber:[anyFileFromEpisode duration]] stringValue];
  235. [mediaInHtml addObject:[NSString stringWithFormat:
  236. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  237. <a href=\"download/%@\" class=\"inner\"> \
  238. <div class=\"down icon\"></div> \
  239. <div class=\"infos\"> \
  240. <span class=\"first-line\">S%@E%@ - %@</span> \
  241. <span class=\"second-line\">%@ - %0.2f MB</span> \
  242. </div> \
  243. </a> \
  244. </div>",
  245. showEp.objectID.URIRepresentation,
  246. [anyFileFromEpisode.url.path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
  247. showEp.seasonNumber,
  248. showEp.episodeNumber,
  249. showEp.name,
  250. duration, (float)([anyFileFromEpisode fileSizeInBytes] / 1e6)]];
  251. if (shouldReturnLibVLCXML) {
  252. NSString *pathSub = [self _checkIfSubtitleWasFound:[anyFileFromEpisode path]];
  253. if (![pathSub isEqualToString:@""])
  254. pathSub = [NSString stringWithFormat:@"http://%@/download/%@", hostName, pathSub];
  255. [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]];
  256. }
  257. }
  258. [mediaInHtml addObject:@"</div></div>"];
  259. } else if ([mo isKindOfClass:[MLLabel class]]) {
  260. MLLabel *label = (MLLabel *)mo;
  261. NSArray *folderItems = [label sortedFolderItems];
  262. [mediaInHtml addObject:[NSString stringWithFormat:
  263. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  264. <a href=\"#\" class=\"inner folder\"> \
  265. <div class=\"open icon\"></div> \
  266. <div class=\"infos\"> \
  267. <span class=\"first-line\">%@</span> \
  268. <span class=\"second-line\">%lu items</span> \
  269. </div> \
  270. </a> \
  271. <div class=\"content\">",
  272. label.objectID.URIRepresentation,
  273. label.name,
  274. (unsigned long)folderItems.count]];
  275. for (MLFile *file in folderItems) {
  276. duration = [[VLCTime timeWithNumber:[file duration]] stringValue];
  277. [mediaInHtml addObject:[NSString stringWithFormat:
  278. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  279. <a href=\"download/%@\" class=\"inner\"> \
  280. <div class=\"down icon\"></div> \
  281. <div class=\"infos\"> \
  282. <span class=\"first-line\">%@</span> \
  283. <span class=\"second-line\">%@ - %0.2f MB</span> \
  284. </div> \
  285. </a> \
  286. </div>",
  287. file.objectID.URIRepresentation,
  288. [file.url.path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
  289. file.title,
  290. duration, (float)(file.fileSizeInBytes / 1e6)]];
  291. if (shouldReturnLibVLCXML) {
  292. NSString *pathSub = [self _checkIfSubtitleWasFound:file.path];
  293. if (pathSub)
  294. pathSub = [NSString stringWithFormat:@"http://%@/download/%@", hostName, pathSub];
  295. [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]];
  296. }
  297. }
  298. [mediaInHtml addObject:@"</div></div>"];
  299. } else if ([mo isKindOfClass:[MLAlbum class]]) {
  300. MLAlbum *album = (MLAlbum *)mo;
  301. NSArray *albumTracks = [album sortedTracks];
  302. [mediaInHtml addObject:[NSString stringWithFormat:
  303. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  304. <a href=\"#\" class=\"inner folder\"> \
  305. <div class=\"open icon\"></div> \
  306. <div class=\"infos\"> \
  307. <span class=\"first-line\">%@</span> \
  308. <span class=\"second-line\">%lu items</span> \
  309. </div> \
  310. </a> \
  311. <div class=\"content\">",
  312. album.objectID.URIRepresentation,
  313. album.name,
  314. (unsigned long)albumTracks.count]];
  315. for (MLAlbumTrack *track in albumTracks) {
  316. MLFile *anyFileFromTrack = [track anyFileFromTrack];
  317. duration = [[VLCTime timeWithNumber:[anyFileFromTrack duration]] stringValue];
  318. [mediaInHtml addObject:[NSString stringWithFormat:
  319. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  320. <a href=\"download/%@\" class=\"inner\"> \
  321. <div class=\"down icon\"></div> \
  322. <div class=\"infos\"> \
  323. <span class=\"first-line\">%@</span> \
  324. <span class=\"second-line\">%@ - %0.2f MB</span> \
  325. </div> \
  326. </a> \
  327. </div>",
  328. track.objectID.URIRepresentation,
  329. [anyFileFromTrack.url.path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
  330. track.title,
  331. duration, (float)([anyFileFromTrack fileSizeInBytes] / 1e6)]];
  332. if (shouldReturnLibVLCXML)
  333. [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]]];
  334. }
  335. [mediaInHtml addObject:@"</div></div>"];
  336. }
  337. }
  338. UIDevice *currentDevice = [UIDevice currentDevice];
  339. NSString *deviceModel = [currentDevice model];
  340. NSDictionary *replacementDict;
  341. HTTPDynamicFileResponse *fileResponse;
  342. if (shouldReturnLibVLCXML) {
  343. replacementDict = @{@"FILES" : [mediaInXml componentsJoinedByString:@" "],
  344. @"NB_FILE" : [NSString stringWithFormat:@"%li", (unsigned long)mediaInXml.count],
  345. @"LIB_TITLE" : [currentDevice name]};
  346. fileResponse = [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
  347. forConnection:self
  348. separator:@"%%"
  349. replacementDictionary:replacementDict];
  350. fileResponse.contentType = @"application/xml";
  351. } else {
  352. replacementDict = @{@"FILES" : [mediaInHtml componentsJoinedByString:@" "],
  353. @"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE", nil),
  354. @"WEBINTF_DROPFILES" : NSLocalizedString(@"WEBINTF_DROPFILES", nil),
  355. @"WEBINTF_DROPFILES_LONG" : [NSString stringWithFormat:NSLocalizedString(@"WEBINTF_DROPFILES_LONG", nil), deviceModel],
  356. @"WEBINTF_DOWNLOADFILES" : NSLocalizedString(@"WEBINTF_DOWNLOADFILES", nil),
  357. @"WEBINTF_DOWNLOADFILES_LONG" : [NSString stringWithFormat: NSLocalizedString(@"WEBINTF_DOWNLOADFILES_LONG", nil), deviceModel]};
  358. fileResponse = [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
  359. forConnection:self
  360. separator:@"%%"
  361. replacementDictionary:replacementDict];
  362. fileResponse.contentType = @"text/html";
  363. }
  364. return fileResponse;
  365. }
  366. #else
  367. - (NSObject<HTTPResponse> *)_httpGETLibraryForPath:(NSString *)path
  368. {
  369. UIDevice *currentDevice = [UIDevice currentDevice];
  370. NSString *deviceModel = [currentDevice model];
  371. NSString *filePath = [self filePathForURI:path];
  372. NSString *documentRoot = [config documentRoot];
  373. NSString *relativePath = [filePath substringFromIndex:[documentRoot length]];
  374. NSDictionary *replacementDict = @{@"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE_ATV", nil),
  375. @"WEBINTF_DROPFILES" : NSLocalizedString(@"WEBINTF_DROPFILES", nil),
  376. @"WEBINTF_DROPFILES_LONG" : [NSString stringWithFormat:NSLocalizedString(@"WEBINTF_DROPFILES_LONG_ATV", nil), deviceModel],
  377. @"WEBINTF_OPEN_URL" : NSLocalizedString(@"ENTER_URL", nil)};
  378. HTTPDynamicFileResponse *fileResponse;
  379. if ([relativePath isEqualToString:@"/index.html"]) {
  380. fileResponse = [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
  381. forConnection:self
  382. separator:@"%%"
  383. replacementDictionary:replacementDict];
  384. fileResponse.contentType = @"text/html";
  385. }
  386. return fileResponse;
  387. }
  388. #endif
  389. - (NSObject<HTTPResponse> *)_httpGETCSSForPath:(NSString *)path
  390. {
  391. #if TARGET_OS_IOS
  392. NSDictionary *replacementDict = @{@"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE", nil)};
  393. #else
  394. NSDictionary *replacementDict = @{@"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE_ATV", nil)};
  395. #endif
  396. HTTPDynamicFileResponse *fileResponse = [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
  397. forConnection:self
  398. separator:@"%%"
  399. replacementDictionary:replacementDict];
  400. fileResponse.contentType = @"text/css";
  401. return fileResponse;
  402. }
  403. #if TARGET_OS_TV
  404. - (NSObject <HTTPResponse> *)_HTTPGETPlaying
  405. {
  406. /* JSON response:
  407. {
  408. "currentTime": 42,
  409. "media": {
  410. "id": "some id",
  411. "title": "some title",
  412. "duration": 120000
  413. }
  414. }
  415. */
  416. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  417. if (!vpc.activePlaybackSession) {
  418. return [[HTTPErrorResponse alloc] initWithErrorCode:404];
  419. }
  420. VLCMediaPlayer *player = vpc.mediaPlayer;
  421. VLCMedia *media = player.media;
  422. if (!media) {
  423. return [[HTTPErrorResponse alloc] initWithErrorCode:404];
  424. }
  425. NSString *mediaTitle = vpc.mediaTitle;
  426. if (!mediaTitle)
  427. mediaTitle = @"";
  428. NSDictionary *mediaDict = @{ @"id" : media.url.absoluteString,
  429. @"title" : mediaTitle,
  430. @"duration" : @(media.length.intValue)};
  431. NSDictionary *returnDict = @{ @"currentTime" : @(player.time.intValue),
  432. @"media" : mediaDict };
  433. NSError *error;
  434. NSData *returnData = [NSJSONSerialization dataWithJSONObject:returnDict options:0 error:&error];
  435. if (error != nil) {
  436. APLog(@"JSON serialization failed %@", error);
  437. return [[HTTPErrorResponse alloc] initWithErrorCode:500];
  438. }
  439. return [[HTTPDataResponse alloc] initWithData:returnData];
  440. }
  441. - (NSObject <HTTPResponse> *)_HTTPGETwebResources
  442. {
  443. /* JS response
  444. {
  445. "WEBINTF_URL_SENT" : "URL sent successfully.",
  446. "WEBINTF_URL_EMPTY" :"'URL cannot be empty.",
  447. "WEBINTF_URL_INVALID" : "Not a valid URL."
  448. }
  449. */
  450. NSString *returnString = [NSString stringWithFormat:
  451. @"var LOCALES = {\n" \
  452. "PLAYER_CONTROL: {\n" \
  453. "URL: {\n" \
  454. "EMPTY: \"%@\",\n" \
  455. "NOT_VALID: \"%@\",\n" \
  456. "SENT_SUCCESSFULLY: \"%@\"\n" \
  457. "}\n" \
  458. "}\n" \
  459. "}",
  460. NSLocalizedString(@"WEBINTF_URL_EMPTY", nil),
  461. NSLocalizedString(@"WEBINTF_URL_INVALID", nil),
  462. NSLocalizedString(@"WEBINTF_URL_SENT", nil)];
  463. NSData *returnData = [returnString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
  464. return [[HTTPDataResponse alloc] initWithData:returnData];
  465. }
  466. - (NSObject <HTTPResponse> *)_HTTPGETPlaylist
  467. {
  468. /* JSON response:
  469. [
  470. {
  471. "media": {
  472. "id": "some id 1",
  473. "title": "some title 1",
  474. "duration": 120000
  475. }
  476. },
  477. ...]
  478. */
  479. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  480. if (!vpc.activePlaybackSession || !vpc.mediaList) {
  481. return [[HTTPErrorResponse alloc] initWithErrorCode:404];
  482. }
  483. VLCMediaList *mediaList = vpc.mediaList;
  484. [mediaList lock];
  485. NSUInteger mediaCount = mediaList.count;
  486. NSMutableArray *retArray = [NSMutableArray array];
  487. for (NSUInteger x = 0; x < mediaCount; x++) {
  488. VLCMedia *media = [mediaList mediaAtIndex:x];
  489. NSString *mediaTitle;
  490. if (media.isParsed) {
  491. mediaTitle = [media metadataForKey:VLCMetaInformationTitle];
  492. } else {
  493. mediaTitle = media.url.lastPathComponent;
  494. }
  495. NSDictionary *mediaDict = @{ @"id" : media.url.absoluteString,
  496. @"title" : mediaTitle,
  497. @"duration" : @(media.length.intValue) };
  498. [retArray addObject:@{ @"media" : mediaDict }];
  499. }
  500. [mediaList unlock];
  501. NSError *error;
  502. NSData *returnData = [NSJSONSerialization dataWithJSONObject:retArray options:0 error:&error];
  503. if (error != nil) {
  504. APLog(@"JSON serialization failed %@", error);
  505. return [[HTTPErrorResponse alloc] initWithErrorCode:500];
  506. }
  507. return [[HTTPDataResponse alloc] initWithData:returnData];
  508. }
  509. #endif
  510. - (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path
  511. {
  512. if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.json"])
  513. return [self _httpPOSTresponseUploadJSON];
  514. #if TARGET_OS_IOS
  515. if ([path hasPrefix:@"/download/"]) {
  516. return [self _httpGETDownloadForPath:path];
  517. }
  518. if ([path hasPrefix:@"/thumbnail"]) {
  519. return [self _httpGETThumbnailForPath:path];
  520. }
  521. #else
  522. if ([path hasPrefix:@"/playing"]) {
  523. return [self _HTTPGETPlaying];
  524. }
  525. if ([path hasPrefix:@"/playlist"]) {
  526. return [self _HTTPGETPlaylist];
  527. }
  528. if ([path hasPrefix:@"/web_resources.js"]) {
  529. return [self _HTTPGETwebResources];
  530. }
  531. #endif
  532. NSString *filePath = [self filePathForURI:path];
  533. NSString *documentRoot = [config documentRoot];
  534. NSString *relativePath = [filePath substringFromIndex:[documentRoot length]];
  535. if ([relativePath isEqualToString:@"/index.html"] || [relativePath isEqualToString:@"/libMediaVLC.xml"]) {
  536. return [self _httpGETLibraryForPath:path];
  537. } else if ([relativePath isEqualToString:@"/style.css"]) {
  538. return [self _httpGETCSSForPath:path];
  539. }
  540. return [super httpResponseForMethod:method URI:path];
  541. }
  542. #if TARGET_OS_TV
  543. - (WebSocket *)webSocketForURI:(NSString *)path
  544. {
  545. return [[VLCPlayerControlWebSocket alloc] initWithRequest:request socket:asyncSocket];
  546. }
  547. #endif
  548. - (void)prepareForBodyWithSize:(UInt64)contentLength
  549. {
  550. // set up mime parser
  551. NSString* boundary = [request headerField:@"boundary"];
  552. _parser = [[MultipartFormDataParser alloc] initWithBoundary:boundary formEncoding:NSUTF8StringEncoding];
  553. _parser.delegate = self;
  554. APLog(@"expecting file of size %lli kB", contentLength / 1024);
  555. _contentLength = contentLength;
  556. }
  557. - (void)processBodyData:(NSData *)postDataChunk
  558. {
  559. /* append data to the parser. It will invoke callbacks to let us handle
  560. * parsed data. */
  561. [_parser appendData:postDataChunk];
  562. _receivedContent += postDataChunk.length;
  563. long long percentage = ((_receivedContent * 100) / _contentLength);
  564. APLog(@"received %lli kB (%lli %%)", _receivedContent / 1024, percentage);
  565. #if TARGET_OS_TV
  566. if (percentage >= 10) {
  567. [self performSelectorOnMainThread:@selector(startPlaybackOfPath:) withObject:_filepath waitUntilDone:NO];
  568. }
  569. #endif
  570. }
  571. #if TARGET_OS_TV
  572. - (void)startPlaybackOfPath:(NSString *)path
  573. {
  574. APLog(@"Starting playback of %@", path);
  575. if (_receivedFiles == nil)
  576. _receivedFiles = [[NSMutableArray alloc] init];
  577. if ([_receivedFiles containsObject:path])
  578. return;
  579. [_receivedFiles addObject:path];
  580. VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
  581. BOOL needsMediaList;
  582. VLCMediaList *mediaList = vpc.mediaList;
  583. if (!mediaList) {
  584. mediaList = [[VLCMediaList alloc] init];
  585. needsMediaList = YES;
  586. }
  587. [mediaList addMedia:[VLCMedia mediaWithURL:[NSURL fileURLWithPath:path]]];
  588. if (needsMediaList) {
  589. [vpc playMediaList:mediaList firstIndex:0];
  590. }
  591. VLCFullscreenMovieTVViewController *movieVC = [VLCFullscreenMovieTVViewController fullscreenMovieTVViewController];
  592. if (![movieVC isBeingPresented]) {
  593. [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:movieVC
  594. animated:YES
  595. completion:nil];
  596. }
  597. }
  598. #endif
  599. //-----------------------------------------------------------------
  600. #pragma mark multipart form data parser delegate
  601. - (void)processStartOfPartWithHeader:(MultipartMessageHeader*) header
  602. {
  603. /* in this sample, we are not interested in parts, other then file parts.
  604. * check content disposition to find out filename */
  605. MultipartMessageHeaderField* disposition = (header.fields)[@"Content-Disposition"];
  606. NSString* filename = [(disposition.params)[@"filename"] lastPathComponent];
  607. if ((nil == filename) || [filename isEqualToString: @""]) {
  608. // it's either not a file part, or
  609. // an empty form sent. we won't handle it.
  610. return;
  611. }
  612. // create the path where to store the media temporarily
  613. NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  614. NSString *uploadDirPath = [searchPaths[0] stringByAppendingPathComponent:@"Upload"];
  615. NSFileManager *fileManager = [NSFileManager defaultManager];
  616. BOOL isDir = YES;
  617. if (![fileManager fileExistsAtPath:uploadDirPath isDirectory:&isDir])
  618. [fileManager createDirectoryAtPath:uploadDirPath withIntermediateDirectories:YES attributes:nil error:nil];
  619. _filepath = [uploadDirPath stringByAppendingPathComponent: filename];
  620. NSNumber *freeSpace = [[UIDevice currentDevice] VLCFreeDiskSpace];
  621. if (_contentLength >= freeSpace.longLongValue) {
  622. /* avoid deadlock since we are on a background thread */
  623. [self performSelectorOnMainThread:@selector(notifyUserAboutEndOfFreeStorage:) withObject:filename waitUntilDone:NO];
  624. [self handleResourceNotFound];
  625. [self stop];
  626. return;
  627. }
  628. APLog(@"Saving file to %@", _filepath);
  629. if (![fileManager createDirectoryAtPath:uploadDirPath withIntermediateDirectories:true attributes:nil error:nil])
  630. APLog(@"Could not create directory at path: %@", _filepath);
  631. if (![fileManager createFileAtPath:_filepath contents:nil attributes:nil])
  632. APLog(@"Could not create file at path: %@", _filepath);
  633. _storeFile = [NSFileHandle fileHandleForWritingAtPath:_filepath];
  634. VLCActivityManager *activityManager = [VLCActivityManager defaultManager];
  635. [activityManager networkActivityStarted];
  636. [activityManager disableIdleTimer];
  637. }
  638. - (void)notifyUserAboutEndOfFreeStorage:(NSString *)filename
  639. {
  640. #if TARGET_OS_IOS
  641. VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"DISK_FULL", nil)
  642. message:[NSString stringWithFormat:
  643. NSLocalizedString(@"DISK_FULL_FORMAT", nil),
  644. filename,
  645. [[UIDevice currentDevice] model]]
  646. delegate:self
  647. cancelButtonTitle:NSLocalizedString(@"BUTTON_OK", nil)
  648. otherButtonTitles:nil];
  649. [alert show];
  650. #else
  651. UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"DISK_FULL", nil)
  652. message:[NSString stringWithFormat:
  653. NSLocalizedString(@"DISK_FULL_FORMAT", nil),
  654. filename,
  655. [[UIDevice currentDevice] model]]
  656. preferredStyle:UIAlertControllerStyleAlert];
  657. [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_CANCEL", nil)
  658. style:UIAlertActionStyleCancel
  659. handler:nil]];
  660. [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
  661. #endif
  662. }
  663. - (void)processContent:(NSData*)data WithHeader:(MultipartMessageHeader*) header
  664. {
  665. // here we just write the output from parser to the file.
  666. if (_storeFile) {
  667. @try {
  668. [_storeFile writeData:data];
  669. }
  670. @catch (NSException *exception) {
  671. APLog(@"File to write further data because storage is full.");
  672. [_storeFile closeFile];
  673. _storeFile = nil;
  674. /* don't block */
  675. [self performSelector:@selector(stop) withObject:nil afterDelay:0.1];
  676. }
  677. }
  678. }
  679. - (void)processEndOfPartWithHeader:(MultipartMessageHeader*)header
  680. {
  681. // as the file part is over, we close the file.
  682. APLog(@"closing file");
  683. [_storeFile closeFile];
  684. _storeFile = nil;
  685. }
  686. - (BOOL)shouldDie
  687. {
  688. if (_filepath) {
  689. if (_filepath.length > 0) {
  690. [[VLCHTTPUploaderController sharedInstance] moveFileFrom:_filepath];
  691. #if TARGET_OS_TV
  692. [_receivedFiles removeObject:_filepath];
  693. #endif
  694. }
  695. }
  696. return [super shouldDie];
  697. }
  698. #pragma mark subtitle
  699. - (NSMutableArray *)_listOfSubtitles
  700. {
  701. NSMutableArray *listOfSubtitles = [[NSMutableArray alloc] init];
  702. NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
  703. NSArray *allFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsDirectory error:nil];
  704. NSString *filePath;
  705. NSUInteger count = allFiles.count;
  706. for (NSUInteger i = 0; i < count; i++) {
  707. filePath = [[NSString stringWithFormat:@"%@/%@", documentsDirectory, allFiles[i]] stringByReplacingOccurrencesOfString:@"file://"withString:@""];
  708. if ([filePath isSupportedSubtitleFormat])
  709. [listOfSubtitles addObject:filePath];
  710. }
  711. return listOfSubtitles;
  712. }
  713. - (NSString *)_checkIfSubtitleWasFound:(NSString *)filePath
  714. {
  715. NSString *subtitlePath;
  716. NSString *fileSub;
  717. NSString *currentPath;
  718. NSString *fileName = [[filePath lastPathComponent] stringByDeletingPathExtension];
  719. if (fileName == nil)
  720. return nil;
  721. NSMutableArray *listOfSubtitles = [self _listOfSubtitles];
  722. NSUInteger count = listOfSubtitles.count;
  723. for (NSUInteger i = 0; i < count; i++) {
  724. currentPath = listOfSubtitles[i];
  725. fileSub = [NSString stringWithFormat:@"%@", currentPath];
  726. if ([fileSub rangeOfString:fileName].location != NSNotFound)
  727. subtitlePath = currentPath;
  728. }
  729. return subtitlePath;
  730. }
  731. @end