VLCHTTPConnection.m 37 KB

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