VLCHTTPConnection.m 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. /*****************************************************************************
  2. * VLCHTTPConnection.m
  3. * VLC for iOS
  4. *****************************************************************************
  5. * Copyright (c) 2013 VideoLAN. All rights reserved.
  6. * $Id$
  7. *
  8. * Authors: Felix Paul Kühne <fkuehne # videolan.org>
  9. * Jean-Baptiste Kempf <jb # videolan.org>
  10. *
  11. * Refer to the COPYING file of the official project for license.
  12. *****************************************************************************/
  13. #import "VLCAppDelegate.h"
  14. #import "VLCHTTPConnection.h"
  15. #import "HTTPConnection.h"
  16. #import "MultipartFormDataParser.h"
  17. #import "HTTPMessage.h"
  18. #import "HTTPDataResponse.h"
  19. #import "HTTPFileResponse.h"
  20. #import "MultipartMessageHeaderField.h"
  21. #import "VLCHTTPUploaderController.h"
  22. #import "HTTPDynamicFileResponse.h"
  23. #import "VLCThumbnailsCache.h"
  24. @interface VLCHTTPConnection()
  25. {
  26. MultipartFormDataParser *_parser;
  27. NSFileHandle *_storeFile;
  28. NSString *_filepath;
  29. UInt64 _contentLength;
  30. UInt64 _receivedContent;
  31. }
  32. @property (nonatomic) VLCHTTPUploaderController *uploadController;
  33. @end
  34. @implementation VLCHTTPConnection
  35. - (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path
  36. {
  37. // Add support for POST
  38. if ([method isEqualToString:@"POST"]) {
  39. if ([path isEqualToString:@"/upload.json"])
  40. return YES;
  41. }
  42. return [super supportsMethod:method atPath:path];
  43. }
  44. - (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path
  45. {
  46. // Inform HTTP server that we expect a body to accompany a POST request
  47. if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.json"]) {
  48. // here we need to make sure, boundary is set in header
  49. NSString* contentType = [request headerField:@"Content-Type"];
  50. NSUInteger paramsSeparator = [contentType rangeOfString:@";"].location;
  51. if (NSNotFound == paramsSeparator)
  52. return NO;
  53. if (paramsSeparator >= contentType.length - 1)
  54. return NO;
  55. NSString* type = [contentType substringToIndex:paramsSeparator];
  56. if (![type isEqualToString:@"multipart/form-data"]) {
  57. // we expect multipart/form-data content type
  58. return NO;
  59. }
  60. // enumerate all params in content-type, and find boundary there
  61. NSArray* params = [[contentType substringFromIndex:paramsSeparator + 1] componentsSeparatedByString:@";"];
  62. for (NSString* param in params) {
  63. paramsSeparator = [param rangeOfString:@"="].location;
  64. if ((NSNotFound == paramsSeparator) || paramsSeparator >= param.length - 1)
  65. continue;
  66. NSString* paramName = [param substringWithRange:NSMakeRange(1, paramsSeparator-1)];
  67. NSString* paramValue = [param substringFromIndex:paramsSeparator+1];
  68. if ([paramName isEqualToString: @"boundary"])
  69. // let's separate the boundary from content-type, to make it more handy to handle
  70. [request setHeaderField:@"boundary" value:paramValue];
  71. }
  72. // check if boundary specified
  73. if (nil == [request headerField:@"boundary"])
  74. return NO;
  75. return YES;
  76. }
  77. return [super expectsRequestBodyFromMethod:method atPath:path];
  78. }
  79. - (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path
  80. {
  81. if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.json"]) {
  82. return [[HTTPDataResponse alloc] initWithData:[@"\"OK\"" dataUsingEncoding:NSUTF8StringEncoding]];
  83. }
  84. if ([path hasPrefix:@"/download/"]) {
  85. NSString *filePath = [[path stringByReplacingOccurrencesOfString:@"/download/" withString:@""]stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  86. HTTPFileResponse *fileResponse = [[HTTPFileResponse alloc] initWithFilePath:filePath forConnection:self];
  87. fileResponse.contentType = @"application/octet-stream";
  88. return fileResponse;
  89. }
  90. if ([path hasPrefix:@"/thumbnail"]) {
  91. NSString *filePath = [[path stringByReplacingOccurrencesOfString:@"/thumbnail/" withString:@""]stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  92. filePath = [filePath stringByReplacingOccurrencesOfString:@".png" withString:@""];
  93. NSManagedObjectContext *moc = [[MLMediaLibrary sharedMediaLibrary] managedObjectContext];
  94. NSPersistentStoreCoordinator *psc = [moc persistentStoreCoordinator];
  95. NSManagedObject *mo = [moc existingObjectWithID:[psc managedObjectIDForURIRepresentation:[NSURL URLWithString:filePath]] error:nil];
  96. NSData *theData;
  97. if ([mo isKindOfClass:[MLFile class]])
  98. theData = UIImagePNGRepresentation([VLCThumbnailsCache thumbnailForMediaFile:(MLFile *)mo]);
  99. else if ([mo isKindOfClass:[MLShow class]])
  100. theData = UIImagePNGRepresentation([VLCThumbnailsCache thumbnailForShow:(MLShow *)mo]);
  101. else if ([mo isKindOfClass:[MLLabel class]])
  102. theData = UIImagePNGRepresentation([VLCThumbnailsCache thumbnailForLabel:(MLLabel *)mo]);
  103. else if ([mo isKindOfClass:[MLAlbum class]])
  104. theData = UIImagePNGRepresentation([VLCThumbnailsCache thumbnailForMediaFile:[[(MLAlbum *)mo tracks].anyObject files].anyObject]);
  105. else if ([mo isKindOfClass:[MLAlbumTrack class]])
  106. theData = UIImagePNGRepresentation([VLCThumbnailsCache thumbnailForMediaFile:[(MLAlbumTrack *)mo files].anyObject]);
  107. else if ([mo isKindOfClass:[MLShowEpisode class]])
  108. theData = UIImagePNGRepresentation([VLCThumbnailsCache thumbnailForMediaFile:[(MLShowEpisode *)mo files].anyObject]);
  109. if (theData) {
  110. HTTPDataResponse *dataResponse = [[HTTPDataResponse alloc] initWithData:theData];
  111. dataResponse.contentType = @"image/png";
  112. return dataResponse;
  113. }
  114. }
  115. NSString *filePath = [self filePathForURI:path];
  116. NSString *documentRoot = [config documentRoot];
  117. NSString *relativePath = [filePath substringFromIndex:[documentRoot length]];
  118. if (([relativePath isEqualToString:@"/index.html"]) || ([relativePath isEqualToString:@"/libMediaVLC.xml"])) {
  119. NSMutableArray *allMedia = [[NSMutableArray alloc] init];
  120. /* add all albums */
  121. NSArray *allAlbums = [MLAlbum allAlbums];
  122. for (MLAlbum *album in allAlbums) {
  123. if (album.name.length > 0 && album.tracks.count > 1)
  124. [allMedia addObject:album];
  125. }
  126. /* add all shows */
  127. NSArray *allShows = [MLShow allShows];
  128. for (MLShow *show in allShows) {
  129. if (show.name.length > 0 && show.episodes.count > 1)
  130. [allMedia addObject:show];
  131. }
  132. /* add all folders*/
  133. NSArray *allFolders = [MLLabel allLabels];
  134. for (MLLabel *folder in allFolders)
  135. [allMedia addObject:folder];
  136. /* add all remaining files */
  137. NSArray *allFiles = [MLFile allFiles];
  138. for (MLFile *file in allFiles) {
  139. if (file.labels.count > 0) continue;
  140. if (!file.isShowEpisode && !file.isAlbumTrack)
  141. [allMedia addObject:file];
  142. else if (file.isShowEpisode) {
  143. if (file.showEpisode.show.episodes.count < 2)
  144. [allMedia addObject:file];
  145. } else if (file.isAlbumTrack) {
  146. if (file.albumTrack.album.tracks.count < 2)
  147. [allMedia addObject:file];
  148. }
  149. }
  150. NSMutableArray *mediaInHtml = [[NSMutableArray alloc] initWithCapacity:allMedia.count];
  151. NSMutableArray *mediaInXml = [[NSMutableArray alloc] initWithCapacity:allMedia.count];
  152. self.uploadController = [[VLCHTTPUploaderController alloc] init];
  153. NSString *hostName = [self.uploadController hostname];
  154. NSString *pathLibrary =[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
  155. NSString *duration;
  156. for (NSManagedObject *mo in allMedia) {
  157. if ([mo isKindOfClass:[MLFile class]]) {
  158. duration = [[VLCTime timeWithNumber:[(MLFile *)mo duration]] stringValue];
  159. [mediaInHtml addObject:[NSString stringWithFormat:
  160. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  161. <a href=\"download/%@\" class=\"inner\"> \
  162. <div class=\"down icon\"></div> \
  163. <div class=\"infos\"> \
  164. <span class=\"first-line\">%@</span> \
  165. <span class=\"second-line\">%@ - %0.2f MB</span> \
  166. </div> \
  167. </a> \
  168. </div>",
  169. mo.objectID.URIRepresentation,
  170. [[(MLFile *)mo url] stringByReplacingOccurrencesOfString:@"file://"withString:@""],
  171. [(MLFile *)mo title],
  172. duration, (float)([(MLFile *)mo fileSizeInBytes] / 1e6)]];
  173. [mediaInXml addObject:[NSString stringWithFormat:@"<Media title=\"%@\" thumb=\"http://%@/download/%@/Thumbnails/File/%@.png\" duration=\"%@\" size=\"%li\" pathfile=\"http://%@/download/%@\"/>",
  174. [(MLFile *)mo title], hostName, pathLibrary, [[NSString stringWithFormat:@"%@", mo.objectID.URIRepresentation] lastPathComponent], duration, [(MLFile *)mo fileSizeInBytes],
  175. hostName, [[(MLFile *)mo url] stringByReplacingOccurrencesOfString:@"file://"withString:@""]]];
  176. }
  177. else if ([mo isKindOfClass:[MLShow class]]) {
  178. NSArray *episodes = [(MLShow *)mo sortedEpisodes];
  179. [mediaInHtml addObject:[NSString stringWithFormat:
  180. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  181. <a href=\"#\" class=\"inner folder\"> \
  182. <div class=\"open icon\"></div> \
  183. <div class=\"infos\"> \
  184. <span class=\"first-line\">%@</span> \
  185. <span class=\"second-line\">%lu items</span> \
  186. </div> \
  187. </a> \
  188. <div class=\"content\">",
  189. mo.objectID.URIRepresentation,
  190. [(MLShow *)mo name],
  191. (unsigned long)[episodes count]]];
  192. for (MLShowEpisode *showEp in episodes) {
  193. duration = [[VLCTime timeWithNumber:[(MLFile *)[[showEp files] anyObject] duration]] stringValue];
  194. [mediaInHtml addObject:[NSString stringWithFormat:
  195. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  196. <a href=\"download/%@\" class=\"inner\"> \
  197. <div class=\"down icon\"></div> \
  198. <div class=\"infos\"> \
  199. <span class=\"first-line\">S%@E%@ - %@</span> \
  200. <span class=\"second-line\">%@ - %0.2f MB</span> \
  201. </div> \
  202. </a> \
  203. </div>",
  204. showEp.objectID.URIRepresentation,
  205. [[(MLFile *)[[showEp files] anyObject] url] stringByReplacingOccurrencesOfString:@"file://"withString:@""],
  206. showEp.seasonNumber,
  207. showEp.episodeNumber,
  208. showEp.name,
  209. duration, (float)([(MLFile *)[[showEp files] anyObject] fileSizeInBytes] / 1e6)]];
  210. [mediaInXml addObject:[NSString stringWithFormat:@"<Media title=\"%@ - S%@E%@\" thumb=\"http://%@/download/%@/Thumbnails/File/%@.png\" duration=\"%@\" size=\"%li\" pathfile=\"http://%@/download/%@\"/>",
  211. [(MLShow *)mo name], showEp.seasonNumber, showEp.episodeNumber,
  212. hostName, pathLibrary, [[NSString stringWithFormat:@"%@", showEp.objectID.URIRepresentation] lastPathComponent], duration,
  213. [(MLFile *)[[showEp files] anyObject] fileSizeInBytes],
  214. hostName, [[(MLFile *)[[showEp files] anyObject] url] stringByReplacingOccurrencesOfString:@"file://"withString:@""]]];
  215. }
  216. [mediaInHtml addObject:@"</div></div>"];
  217. } else if ([mo isKindOfClass:[MLLabel class]]) {
  218. NSArray *folderItems = [(MLLabel *)mo sortedFolderItems];
  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. [(MLLabel *)mo name],
  231. (unsigned long)[folderItems count]]];
  232. for (MLFile *file in folderItems) {
  233. duration = [[VLCTime timeWithNumber:[file duration]] stringValue];
  234. [mediaInHtml addObject:[NSString stringWithFormat:
  235. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  236. <a href=\"download/%@\" class=\"inner\"> \
  237. <div class=\"down icon\"></div> \
  238. <div class=\"infos\"> \
  239. <span class=\"first-line\">%@</span> \
  240. <span class=\"second-line\">%@ - %0.2f MB</span> \
  241. </div> \
  242. </a> \
  243. </div>",
  244. file.objectID.URIRepresentation,
  245. [[file url] stringByReplacingOccurrencesOfString:@"file://"withString:@""],
  246. file.title,
  247. duration, (float)([file fileSizeInBytes] / 1e6)]];
  248. [mediaInXml addObject:[NSString stringWithFormat:@"<Media title=\"%@\" thumb=\"http://%@/download/%@/Thumbnails/File/%@.png\" duration=\"%@\" size=\"%li\" pathfile=\"http://%@/download/%@\"/>",
  249. file.title, hostName, pathLibrary, [[NSString stringWithFormat:@"%@", file.objectID.URIRepresentation] lastPathComponent], duration, [file fileSizeInBytes],
  250. hostName, [[file url] stringByReplacingOccurrencesOfString:@"file://"withString:@""]]];
  251. }
  252. [mediaInHtml addObject:@"</div></div>"];
  253. } else if ([mo isKindOfClass:[MLAlbum class]]) {
  254. NSArray *albumTracks = [(MLAlbum *)mo sortedTracks];
  255. [mediaInHtml addObject:[NSString stringWithFormat:
  256. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  257. <a href=\"#\" class=\"inner folder\"> \
  258. <div class=\"open icon\"></div> \
  259. <div class=\"infos\"> \
  260. <span class=\"first-line\">%@</span> \
  261. <span class=\"second-line\">%lu items</span> \
  262. </div> \
  263. </a> \
  264. <div class=\"content\">",
  265. mo.objectID.URIRepresentation,
  266. [(MLAlbum *)mo name],
  267. (unsigned long)[albumTracks count]]];
  268. for (MLAlbumTrack *track in albumTracks) {
  269. duration = [[VLCTime timeWithNumber:[(MLFile *)[[track files] anyObject] duration]] stringValue];
  270. [mediaInHtml addObject:[NSString stringWithFormat:
  271. @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
  272. <a href=\"download/%@\" class=\"inner\"> \
  273. <div class=\"down icon\"></div> \
  274. <div class=\"infos\"> \
  275. <span class=\"first-line\">%@</span> \
  276. <span class=\"second-line\">%@ - %0.2f MB</span> \
  277. </div> \
  278. </a> \
  279. </div>",
  280. track.objectID.URIRepresentation,
  281. [[(MLFile *)[[track files] anyObject] url] stringByReplacingOccurrencesOfString:@"file://"withString:@""],
  282. track.title,
  283. duration, (float)([(MLFile *)[[track files] anyObject] fileSizeInBytes] / 1e6)]];
  284. [mediaInXml addObject:[NSString stringWithFormat:@"<Media title=\"%@\" thumb=\"http://%@/download/%@/Thumbnails/File/%@.png\" duration=\"%@\" size=\"%li\" pathfile=\"http://%@/download/%@\"/>",
  285. track.title, hostName, pathLibrary, [[NSString stringWithFormat:@"%@", track.objectID.URIRepresentation] lastPathComponent], duration, [(MLFile *)[[track files] anyObject] fileSizeInBytes],
  286. hostName, [[(MLFile *)[[track files] anyObject] url] stringByReplacingOccurrencesOfString:@"file://"withString:@""]]];
  287. }
  288. [mediaInHtml addObject:@"</div></div>"];
  289. }
  290. }
  291. NSString *deviceModel = [[UIDevice currentDevice] model];
  292. NSDictionary *replacementDict;
  293. if ([relativePath isEqualToString:@"/libMediaVLC.xml"]) {
  294. replacementDict = @{@"FILES" : [mediaInXml componentsJoinedByString:@" "],
  295. @"NB_FILE" : [NSString stringWithFormat:@"%li", (unsigned long)mediaInXml.count],
  296. @"LIB_TITLE" : [[UIDevice currentDevice] name]};
  297. } else
  298. replacementDict = @{@"FILES" : [mediaInHtml componentsJoinedByString:@" "],
  299. @"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE", nil),
  300. @"WEBINTF_DROPFILES" : NSLocalizedString(@"WEBINTF_DROPFILES", nil),
  301. @"WEBINTF_DROPFILES_LONG" : [NSString stringWithFormat:NSLocalizedString(@"WEBINTF_DROPFILES_LONG", nil), deviceModel],
  302. @"WEBINTF_DOWNLOADFILES" : NSLocalizedString(@"WEBINTF_DOWNLOADFILES", nil),
  303. @"WEBINTF_DOWNLOADFILES_LONG" : [NSString stringWithFormat: NSLocalizedString(@"WEBINTF_DOWNLOADFILES_LONG", nil), deviceModel]};
  304. return [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
  305. forConnection:self
  306. separator:@"%%"
  307. replacementDictionary:replacementDict];
  308. } else if ([relativePath isEqualToString:@"/style.css"]) {
  309. NSDictionary *replacementDict = @{@"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE", nil)};
  310. return [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
  311. forConnection:self
  312. separator:@"%%"
  313. replacementDictionary:replacementDict];
  314. }
  315. return [super httpResponseForMethod:method URI:path];
  316. }
  317. - (void)prepareForBodyWithSize:(UInt64)contentLength
  318. {
  319. // set up mime parser
  320. NSString* boundary = [request headerField:@"boundary"];
  321. _parser = [[MultipartFormDataParser alloc] initWithBoundary:boundary formEncoding:NSUTF8StringEncoding];
  322. _parser.delegate = self;
  323. APLog(@"expecting file of size %lli kB", contentLength / 1024);
  324. _contentLength = contentLength;
  325. }
  326. - (void)processBodyData:(NSData *)postDataChunk
  327. {
  328. /* append data to the parser. It will invoke callbacks to let us handle
  329. * parsed data. */
  330. [_parser appendData:postDataChunk];
  331. _receivedContent += postDataChunk.length;
  332. APLog(@"received %lli kB (%lli %%)", _receivedContent / 1024, ((_receivedContent * 100) / _contentLength));
  333. }
  334. //-----------------------------------------------------------------
  335. #pragma mark multipart form data parser delegate
  336. - (void)processStartOfPartWithHeader:(MultipartMessageHeader*) header
  337. {
  338. /* in this sample, we are not interested in parts, other then file parts.
  339. * check content disposition to find out filename */
  340. MultipartMessageHeaderField* disposition = (header.fields)[@"Content-Disposition"];
  341. NSString* filename = [(disposition.params)[@"filename"] lastPathComponent];
  342. if ((nil == filename) || [filename isEqualToString: @""]) {
  343. // it's either not a file part, or
  344. // an empty form sent. we won't handle it.
  345. return;
  346. }
  347. // create the path where to store the media temporarily
  348. NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  349. NSString* uploadDirPath = [searchPaths[0] stringByAppendingPathComponent:@"Upload"];
  350. NSFileManager *fileManager = [NSFileManager defaultManager];
  351. BOOL isDir = YES;
  352. if (![fileManager fileExistsAtPath:uploadDirPath isDirectory:&isDir ]) {
  353. [fileManager createDirectoryAtPath:uploadDirPath withIntermediateDirectories:YES attributes:nil error:nil];
  354. }
  355. _filepath = [uploadDirPath stringByAppendingPathComponent: filename];
  356. APLog(@"Saving file to %@", _filepath);
  357. if (![fileManager createDirectoryAtPath:uploadDirPath withIntermediateDirectories:true attributes:nil error:nil])
  358. APLog(@"Could not create directory at path: %@", _filepath);
  359. if (![fileManager createFileAtPath:_filepath contents:nil attributes:nil])
  360. APLog(@"Could not create file at path: %@", _filepath);
  361. _storeFile = [NSFileHandle fileHandleForWritingAtPath:_filepath];
  362. [(VLCAppDelegate*)[UIApplication sharedApplication].delegate networkActivityStarted];
  363. [(VLCAppDelegate*)[UIApplication sharedApplication].delegate disableIdleTimer];
  364. }
  365. - (void)processContent:(NSData*)data WithHeader:(MultipartMessageHeader*) header
  366. {
  367. // here we just write the output from parser to the file.
  368. if (_storeFile) {
  369. @try {
  370. [_storeFile writeData:data];
  371. }
  372. @catch (NSException *exception) {
  373. APLog(@"File to write further data because storage is full.");
  374. [_storeFile closeFile];
  375. _storeFile = nil;
  376. /* don't block */
  377. [self performSelector:@selector(stop) withObject:nil afterDelay:0.1];
  378. }
  379. }
  380. }
  381. - (void)processEndOfPartWithHeader:(MultipartMessageHeader*)header
  382. {
  383. // as the file part is over, we close the file.
  384. APLog(@"closing file");
  385. [_storeFile closeFile];
  386. _storeFile = nil;
  387. }
  388. - (BOOL)shouldDie
  389. {
  390. if (_filepath) {
  391. if (_filepath.length > 0)
  392. [[(VLCAppDelegate*)[UIApplication sharedApplication].delegate uploadController] moveFileFrom:_filepath];
  393. }
  394. return [super shouldDie];
  395. }
  396. @end