VLCHTTPConnection.m 37 KB

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