VLCHTTPConnection.m 31.4 KB
Newer Older
1 2 3 4
/*****************************************************************************
 * VLCHTTPConnection.m
 * VLC for iOS
 *****************************************************************************
5
 * Copyright (c) 2013-2015 VideoLAN. All rights reserved.
6 7 8
 * $Id$
 *
 * Authors: Felix Paul Kühne <fkuehne # videolan.org>
Felix Paul Kühne's avatar
Felix Paul Kühne committed
9 10
 *          Pierre Sagaspe <pierre.sagaspe # me.com>
 *          Carola Nitz <caro # videolan.org>
11 12 13 14
 *          Jean-Baptiste Kempf <jb # videolan.org>
 *
 * Refer to the COPYING file of the official project for license.
 *****************************************************************************/
15

Felix Paul Kühne's avatar
Felix Paul Kühne committed
16
#import "VLCActivityManager.h"
17 18 19 20 21 22
#import "VLCHTTPConnection.h"
#import "MultipartFormDataParser.h"
#import "HTTPMessage.h"
#import "HTTPDataResponse.h"
#import "HTTPFileResponse.h"
#import "MultipartMessageHeaderField.h"
23
#import "HTTPDynamicFileResponse.h"
24
#import "NSString+SupportedMedia.h"
25
#import "UIDevice+VLC.h"
Felix Paul Kühne's avatar
Felix Paul Kühne committed
26
#import "VLCHTTPUploaderController.h"
27

28 29 30 31
#if TARGET_OS_IOS
#import "VLCThumbnailsCache.h"
#endif

32 33
@interface VLCHTTPConnection()
{
34 35 36
    MultipartFormDataParser *_parser;
    NSFileHandle *_storeFile;
    NSString *_filepath;
37 38
    UInt64 _contentLength;
    UInt64 _receivedContent;
39 40 41
#if TARGET_OS_TV
    BOOL _playbackStarted;
#endif
42 43 44 45 46 47 48 49
}
@end

@implementation VLCHTTPConnection

- (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path
{
    // Add support for POST
Felix Paul Kühne's avatar
Felix Paul Kühne committed
50
    if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.json"])
51
        return YES;
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76

    return [super supportsMethod:method atPath:path];
}

- (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path
{
    // Inform HTTP server that we expect a body to accompany a POST request
    if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.json"]) {
        // here we need to make sure, boundary is set in header
        NSString* contentType = [request headerField:@"Content-Type"];
        NSUInteger paramsSeparator = [contentType rangeOfString:@";"].location;
        if (NSNotFound == paramsSeparator)
            return NO;

        if (paramsSeparator >= contentType.length - 1)
            return NO;

        NSString* type = [contentType substringToIndex:paramsSeparator];
        if (![type isEqualToString:@"multipart/form-data"]) {
            // we expect multipart/form-data content type
            return NO;
        }

        // enumerate all params in content-type, and find boundary there
        NSArray* params = [[contentType substringFromIndex:paramsSeparator + 1] componentsSeparatedByString:@";"];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
77 78 79
        NSUInteger count = params.count;
        for (NSUInteger i = 0; i < count; i++) {
            NSString *param = params[i];
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
            paramsSeparator = [param rangeOfString:@"="].location;
            if ((NSNotFound == paramsSeparator) || paramsSeparator >= param.length - 1)
                continue;

            NSString* paramName = [param substringWithRange:NSMakeRange(1, paramsSeparator-1)];
            NSString* paramValue = [param substringFromIndex:paramsSeparator+1];

            if ([paramName isEqualToString: @"boundary"])
                // let's separate the boundary from content-type, to make it more handy to handle
                [request setHeaderField:@"boundary" value:paramValue];
        }
        // check if boundary specified
        if (nil == [request headerField:@"boundary"])
            return NO;

        return YES;
    }
    return [super expectsRequestBodyFromMethod:method atPath:path];
}

- (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
102
    if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.json"])
103
        return [[HTTPDataResponse alloc] initWithData:[@"\"OK\"" dataUsingEncoding:NSUTF8StringEncoding]];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
104

105
#if TARGET_OS_IOS
106 107
    if ([path hasPrefix:@"/download/"]) {
        NSString *filePath = [[path stringByReplacingOccurrencesOfString:@"/download/" withString:@""]stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
108 109 110
        HTTPFileResponse *fileResponse = [[HTTPFileResponse alloc] initWithFilePath:filePath forConnection:self];
        fileResponse.contentType = @"application/octet-stream";
        return fileResponse;
111
    }
112 113 114 115 116
    if ([path hasPrefix:@"/thumbnail"]) {
        NSString *filePath = [[path stringByReplacingOccurrencesOfString:@"/thumbnail/" withString:@""]stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        filePath = [filePath stringByReplacingOccurrencesOfString:@".png" withString:@""];

        NSManagedObjectContext *moc = [[MLMediaLibrary sharedMediaLibrary] managedObjectContext];
117 118 119
        if (moc) {
            NSPersistentStoreCoordinator *psc = [moc persistentStoreCoordinator];
            if (psc) {
120 121 122 123 124 125 126
                NSManagedObject *mo = nil;
                @try {
                    mo = [moc existingObjectWithID:[psc managedObjectIDForURIRepresentation:[NSURL URLWithString:filePath]] error:nil];
                }@catch (NSException *exeption) {
                    // somebody gave us a malformed or stale URIRepresentation
                }

Felix Paul Kühne's avatar
Felix Paul Kühne committed
127 128 129 130 131 132 133 134 135 136 137 138
                NSData *theData;
                NSString *contentType;

                /* devices category 3 and faster include HW accelerated JPEG encoding
                 * so we can make our transfers faster by using waaay smaller images */
                if ([[UIDevice currentDevice] speedCategory] < 3) {
                    theData = UIImagePNGRepresentation([VLCThumbnailsCache thumbnailForManagedObject:mo]);
                    contentType = @"image/png";
                } else {
                    theData = UIImageJPEGRepresentation([VLCThumbnailsCache thumbnailForManagedObject:mo], .9);
                    contentType = @"image/jpg";
                }
139 140 141

                if (theData) {
                    HTTPDataResponse *dataResponse = [[HTTPDataResponse alloc] initWithData:theData];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
142
                    dataResponse.contentType = contentType;
143 144 145
                    return dataResponse;
                }
            }
146
        }
147
    }
148 149 150
    NSString *filePath = [self filePathForURI:path];
    NSString *documentRoot = [config documentRoot];
    NSString *relativePath = [filePath substringFromIndex:[documentRoot length]];
151
    BOOL shouldReturnLibVLCXML = [relativePath isEqualToString:@"/libMediaVLC.xml"];
152

153
    if ([relativePath isEqualToString:@"/index.html"] || shouldReturnLibVLCXML) {
154 155 156
        NSMutableArray *allMedia = [[NSMutableArray alloc] init];

        /* add all albums */
157 158
        NSArray *allAlbums = [MLAlbum allAlbums];
        for (MLAlbum *album in allAlbums) {
159 160
            if (album.name.length > 0 && album.tracks.count > 1)
                [allMedia addObject:album];
161
        }
162 163

        /* add all shows */
164 165
        NSArray *allShows = [MLShow allShows];
        for (MLShow *show in allShows) {
166 167
            if (show.name.length > 0 && show.episodes.count > 1)
                [allMedia addObject:show];
168
        }
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190

        /* add all folders*/
        NSArray *allFolders = [MLLabel allLabels];
        for (MLLabel *folder in allFolders)
            [allMedia addObject:folder];

        /* add all remaining files */
        NSArray *allFiles = [MLFile allFiles];
        for (MLFile *file in allFiles) {
            if (file.labels.count > 0) continue;

            if (!file.isShowEpisode && !file.isAlbumTrack)
                [allMedia addObject:file];
            else if (file.isShowEpisode) {
                if (file.showEpisode.show.episodes.count < 2)
                    [allMedia addObject:file];
            } else if (file.isAlbumTrack) {
                if (file.albumTrack.album.tracks.count < 2)
                    [allMedia addObject:file];
            }
        }

Felix Paul Kühne's avatar
Felix Paul Kühne committed
191 192 193
        NSUInteger mediaCount = allMedia.count;
        NSMutableArray *mediaInHtml = [[NSMutableArray alloc] initWithCapacity:mediaCount];
        NSMutableArray *mediaInXml = [[NSMutableArray alloc] initWithCapacity:mediaCount];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
194
        NSString *hostName = [[VLCHTTPUploaderController sharedInstance] hostname];
195
        NSString *duration;
196 197

        for (NSManagedObject *mo in allMedia) {
198
            if ([mo isKindOfClass:[MLFile class]]) {
199 200
                MLFile *file = (MLFile *)mo;
                duration = [[VLCTime timeWithNumber:file.duration] stringValue];
201 202
                [mediaInHtml addObject:[NSString stringWithFormat:
                                        @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
203
                                        <a href=\"download/%@\" class=\"inner\"> \
204 205 206
                                        <div class=\"down icon\"></div> \
                                        <div class=\"infos\"> \
                                        <span class=\"first-line\">%@</span> \
207
                                        <span class=\"second-line\">%@ - %0.2f MB</span> \
208 209 210
                                        </div> \
                                        </a> \
                                        </div>",
211
                                        file.objectID.URIRepresentation,
212
                                        [file.url.path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
213 214
                                        file.title,
                                        duration, (float)(file.fileSizeInBytes / 1e6)]];
215
                if (shouldReturnLibVLCXML) {
216
                    NSString *pathSub = [self _checkIfSubtitleWasFound:file.path];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
217
                    if (pathSub)
218
                        pathSub = [NSString stringWithFormat:@"http://%@/download/%@", hostName, pathSub];
219
                    [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]];
220
                }
221
            }
222
            else if ([mo isKindOfClass:[MLShow class]]) {
223 224
                MLShow *show = (MLShow *)mo;
                NSArray *episodes = [show sortedEpisodes];
225 226
                [mediaInHtml addObject:[NSString stringWithFormat:
                                        @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
227
                                        <a href=\"#\" class=\"inner folder\"> \
228 229 230
                                        <div class=\"open icon\"></div> \
                                        <div class=\"infos\"> \
                                        <span class=\"first-line\">%@</span> \
Felix Paul Kühne's avatar
Felix Paul Kühne committed
231
                                        <span class=\"second-line\">%lu items</span> \
232 233 234 235
                                        </div> \
                                        </a> \
                                        <div class=\"content\">",
                                        mo.objectID.URIRepresentation,
236
                                        show.name,
Felix Paul Kühne's avatar
Felix Paul Kühne committed
237
                                        (unsigned long)[episodes count]]];
238
                for (MLShowEpisode *showEp in episodes) {
Felix Paul Kühne's avatar
Felix Paul Kühne committed
239 240
                    MLFile *anyFileFromEpisode = (MLFile *)[[showEp files] anyObject];
                    duration = [[VLCTime timeWithNumber:[anyFileFromEpisode duration]] stringValue];
241 242
                    [mediaInHtml addObject:[NSString stringWithFormat:
                                            @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
243
                                            <a href=\"download/%@\" class=\"inner\"> \
244 245 246
                                            <div class=\"down icon\"></div> \
                                            <div class=\"infos\"> \
                                            <span class=\"first-line\">S%@E%@ - %@</span> \
247
                                            <span class=\"second-line\">%@ - %0.2f MB</span> \
248 249 250 251
                                            </div> \
                                            </a> \
                                            </div>",
                                            showEp.objectID.URIRepresentation,
252
                                            [anyFileFromEpisode.url.path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
253 254
                                            showEp.seasonNumber,
                                            showEp.episodeNumber,
255
                                            showEp.name,
Felix Paul Kühne's avatar
Felix Paul Kühne committed
256
                                            duration, (float)([anyFileFromEpisode fileSizeInBytes] / 1e6)]];
257
                    if (shouldReturnLibVLCXML) {
258
                        NSString *pathSub = [self _checkIfSubtitleWasFound:[anyFileFromEpisode path]];
259 260
                        if (![pathSub isEqualToString:@""])
                            pathSub = [NSString stringWithFormat:@"http://%@/download/%@", hostName, pathSub];
261
                        [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]];
262
                    }
263
                }
264
                [mediaInHtml addObject:@"</div></div>"];
265
            } else if ([mo isKindOfClass:[MLLabel class]]) {
266 267
                MLLabel *label = (MLLabel *)mo;
                NSArray *folderItems = [label sortedFolderItems];
268 269
                [mediaInHtml addObject:[NSString stringWithFormat:
                                        @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
270
                                        <a href=\"#\" class=\"inner folder\"> \
271 272 273
                                        <div class=\"open icon\"></div> \
                                        <div class=\"infos\"> \
                                        <span class=\"first-line\">%@</span> \
Felix Paul Kühne's avatar
Felix Paul Kühne committed
274
                                        <span class=\"second-line\">%lu items</span> \
275 276 277
                                        </div> \
                                        </a> \
                                        <div class=\"content\">",
278 279 280
                                        label.objectID.URIRepresentation,
                                        label.name,
                                        (unsigned long)folderItems.count]];
281
                for (MLFile *file in folderItems) {
282
                    duration = [[VLCTime timeWithNumber:[file duration]] stringValue];
283 284
                    [mediaInHtml addObject:[NSString stringWithFormat:
                                            @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
285
                                            <a href=\"download/%@\" class=\"inner\"> \
286 287 288
                                            <div class=\"down icon\"></div> \
                                            <div class=\"infos\"> \
                                            <span class=\"first-line\">%@</span> \
289
                                            <span class=\"second-line\">%@ - %0.2f MB</span> \
290 291 292 293
                                            </div> \
                                            </a> \
                                            </div>",
                                            file.objectID.URIRepresentation,
294
                                            [file.url.path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
295
                                            file.title,
296
                                            duration, (float)(file.fileSizeInBytes / 1e6)]];
297
                    if (shouldReturnLibVLCXML) {
298
                        NSString *pathSub = [self _checkIfSubtitleWasFound:file.path];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
299
                        if (pathSub)
300
                            pathSub = [NSString stringWithFormat:@"http://%@/download/%@", hostName, pathSub];
301
                        [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]];
302
                    }
303
                }
304
                [mediaInHtml addObject:@"</div></div>"];
305
            } else if ([mo isKindOfClass:[MLAlbum class]]) {
306 307
                MLAlbum *album = (MLAlbum *)mo;
                NSArray *albumTracks = [album sortedTracks];
308 309
                [mediaInHtml addObject:[NSString stringWithFormat:
                                        @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
310
                                        <a href=\"#\" class=\"inner folder\"> \
311 312 313
                                        <div class=\"open icon\"></div> \
                                        <div class=\"infos\"> \
                                        <span class=\"first-line\">%@</span> \
Felix Paul Kühne's avatar
Felix Paul Kühne committed
314
                                        <span class=\"second-line\">%lu items</span> \
315 316 317
                                        </div> \
                                        </a> \
                                        <div class=\"content\">",
318 319 320
                                        album.objectID.URIRepresentation,
                                        album.name,
                                        (unsigned long)albumTracks.count]];
321
                for (MLAlbumTrack *track in albumTracks) {
322
                    MLFile *anyFileFromTrack = [track anyFileFromTrack];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
323
                    duration = [[VLCTime timeWithNumber:[anyFileFromTrack duration]] stringValue];
324 325
                    [mediaInHtml addObject:[NSString stringWithFormat:
                                            @"<div style=\"background-image:url('thumbnail/%@.png')\"> \
326
                                            <a href=\"download/%@\" class=\"inner\"> \
327 328 329
                                            <div class=\"down icon\"></div> \
                                            <div class=\"infos\"> \
                                            <span class=\"first-line\">%@</span> \
330
                                            <span class=\"second-line\">%@ - %0.2f MB</span> \
331 332 333 334
                                            </div> \
                                            </a> \
                                            </div>",
                                            track.objectID.URIRepresentation,
335
                                            [anyFileFromTrack.url.path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
336
                                            track.title,
Felix Paul Kühne's avatar
Felix Paul Kühne committed
337
                                            duration, (float)([anyFileFromTrack fileSizeInBytes] / 1e6)]];
338
                    if (shouldReturnLibVLCXML)
339
                        [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]]];
340
                }
341
                [mediaInHtml addObject:@"</div></div>"];
342
            }
343
        }
344

345

Felix Paul Kühne's avatar
Felix Paul Kühne committed
346 347
        UIDevice *currentDevice = [UIDevice currentDevice];
        NSString *deviceModel = [currentDevice model];
348
        NSDictionary *replacementDict;
349
        HTTPDynamicFileResponse *fileResponse;
350

351
        if (shouldReturnLibVLCXML) {
352 353
            replacementDict = @{@"FILES" : [mediaInXml componentsJoinedByString:@" "],
                                @"NB_FILE" : [NSString stringWithFormat:@"%li", (unsigned long)mediaInXml.count],
Felix Paul Kühne's avatar
Felix Paul Kühne committed
354
                                @"LIB_TITLE" : [currentDevice name]};
355 356 357 358 359 360 361

            fileResponse = [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
                                                               forConnection:self
                                                                   separator:@"%%"
                                                       replacementDictionary:replacementDict];
            fileResponse.contentType = @"application/xml";
        } else {
362 363 364 365 366 367
            replacementDict = @{@"FILES" : [mediaInHtml componentsJoinedByString:@" "],
                                @"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE", nil),
                                @"WEBINTF_DROPFILES" : NSLocalizedString(@"WEBINTF_DROPFILES", nil),
                                @"WEBINTF_DROPFILES_LONG" : [NSString stringWithFormat:NSLocalizedString(@"WEBINTF_DROPFILES_LONG", nil), deviceModel],
                                @"WEBINTF_DOWNLOADFILES" : NSLocalizedString(@"WEBINTF_DOWNLOADFILES", nil),
                                @"WEBINTF_DOWNLOADFILES_LONG" : [NSString stringWithFormat: NSLocalizedString(@"WEBINTF_DOWNLOADFILES_LONG", nil), deviceModel]};
368 369 370 371 372 373
            fileResponse = [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
                                                               forConnection:self
                                                                   separator:@"%%"
                                                       replacementDictionary:replacementDict];
            fileResponse.contentType = @"text/html";
        }
374 375 376 377 378 379
#else
        UIDevice *currentDevice = [UIDevice currentDevice];
        NSString *deviceModel = [currentDevice model];
        NSString *filePath = [self filePathForURI:path];
        NSString *documentRoot = [config documentRoot];
        NSString *relativePath = [filePath substringFromIndex:[documentRoot length]];
380
        NSDictionary *replacementDict = @{@"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE_ATV", nil),
381
                                          @"WEBINTF_DROPFILES" : NSLocalizedString(@"WEBINTF_DROPFILES", nil),
382
                                          @"WEBINTF_DROPFILES_LONG" : [NSString stringWithFormat:NSLocalizedString(@"WEBINTF_DROPFILES_LONG_ATV", nil), deviceModel]};
383 384

        HTTPDynamicFileResponse *fileResponse;
385 386 387 388 389 390
        if ([relativePath isEqualToString:@"/index.html"]) {
            fileResponse = [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
                                                               forConnection:self
                                                                   separator:@"%%"
                                                       replacementDictionary:replacementDict];
            fileResponse.contentType = @"text/html";
391
#endif
392

393
        return fileResponse;
394
    } else if ([relativePath isEqualToString:@"/style.css"]) {
395
#if TARGET_OS_IOS
396
        NSDictionary *replacementDict = @{@"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE", nil)};
397 398 399
#else
        NSDictionary *replacementDict = @{@"WEBINTF_TITLE" : NSLocalizedString(@"WEBINTF_TITLE_ATV", nil)};
#endif
400 401 402 403 404 405
        HTTPDynamicFileResponse *fileResponse = [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
                                                                                    forConnection:self
                                                                                        separator:@"%%"
                                                                            replacementDictionary:replacementDict];
        fileResponse.contentType = @"text/css";
        return fileResponse;
406
    }
407

408 409 410 411 412 413 414 415 416 417
    return [super httpResponseForMethod:method URI:path];
}

- (void)prepareForBodyWithSize:(UInt64)contentLength
{
    // set up mime parser
    NSString* boundary = [request headerField:@"boundary"];
    _parser = [[MultipartFormDataParser alloc] initWithBoundary:boundary formEncoding:NSUTF8StringEncoding];
    _parser.delegate = self;

418 419
    APLog(@"expecting file of size %lli kB", contentLength / 1024);
    _contentLength = contentLength;
420 421 422 423 424 425 426
}

- (void)processBodyData:(NSData *)postDataChunk
{
    /* append data to the parser. It will invoke callbacks to let us handle
     * parsed data. */
    [_parser appendData:postDataChunk];
427

428
    _receivedContent += postDataChunk.length;
429

430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
    long long percentage = ((_receivedContent * 100) / _contentLength);
    APLog(@"received %lli kB (%lli %%)", _receivedContent / 1024, percentage);
#if TARGET_OS_TV
    if (!_playbackStarted) {
        if (percentage >= 10) {
            _playbackStarted = YES;

            APLog(@"Starting playback of %@", _filepath);
            VLCPlaybackController *vpc = [VLCPlaybackController sharedInstance];
            [vpc playURL:[NSURL fileURLWithPath:_filepath] successCallback:nil errorCallback:nil];

            VLCFullscreenMovieTVViewController *moviewVC = [VLCFullscreenMovieTVViewController fullscreenMovieTVViewController];
            [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:moviewVC
                                                                                         animated:YES
                                                                                       completion:nil];

        }
    }
#endif
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468
}

//-----------------------------------------------------------------
#pragma mark multipart form data parser delegate


- (void)processStartOfPartWithHeader:(MultipartMessageHeader*) header
{
    /* in this sample, we are not interested in parts, other then file parts.
     * check content disposition to find out filename */

    MultipartMessageHeaderField* disposition = (header.fields)[@"Content-Disposition"];
    NSString* filename = [(disposition.params)[@"filename"] lastPathComponent];

    if ((nil == filename) || [filename isEqualToString: @""]) {
        // it's either not a file part, or
        // an empty form sent. we won't handle it.
        return;
    }

469
    // create the path where to store the media temporarily
470
    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
Felix Paul Kühne's avatar
Felix Paul Kühne committed
471
    NSString *uploadDirPath = [searchPaths[0] stringByAppendingPathComponent:@"Upload"];
472
    NSFileManager *fileManager = [NSFileManager defaultManager];
473 474

    BOOL isDir = YES;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
475
    if (![fileManager fileExistsAtPath:uploadDirPath isDirectory:&isDir])
476
        [fileManager createDirectoryAtPath:uploadDirPath withIntermediateDirectories:YES attributes:nil error:nil];
477

478
    _filepath = [uploadDirPath stringByAppendingPathComponent: filename];
479

480 481 482 483 484 485 486 487 488
    NSNumber *freeSpace = [[UIDevice currentDevice] freeDiskspace];
    if (_contentLength >= freeSpace.longLongValue) {
        /* avoid deadlock since we are on a background thread */
        [self performSelectorOnMainThread:@selector(notifyUserAboutEndOfFreeStorage:) withObject:filename waitUntilDone:NO];
        [self handleResourceNotFound];
        [self stop];
        return;
    }

489
    APLog(@"Saving file to %@", _filepath);
490
    if (![fileManager createDirectoryAtPath:uploadDirPath withIntermediateDirectories:true attributes:nil error:nil])
491
        APLog(@"Could not create directory at path: %@", _filepath);
492

493
    if (![fileManager createFileAtPath:_filepath contents:nil attributes:nil])
494
        APLog(@"Could not create file at path: %@", _filepath);
495

496
    _storeFile = [NSFileHandle fileHandleForWritingAtPath:_filepath];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
497 498 499 500

    VLCActivityManager *activityManager = [VLCActivityManager defaultManager];
    [activityManager networkActivityStarted];
    [activityManager disableIdleTimer];
501 502
}

503 504
- (void)notifyUserAboutEndOfFreeStorage:(NSString *)filename
{
505
#if TARGET_OS_IOS
506 507 508 509 510 511 512 513
    VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"DISK_FULL", nil)
                                                      message:[NSString stringWithFormat:
                                                               NSLocalizedString(@"DISK_FULL_FORMAT", nil),
                                                               filename,
                                                               [[UIDevice currentDevice] model]]
                                                     delegate:self
                                            cancelButtonTitle:NSLocalizedString(@"BUTTON_OK", nil)
                                            otherButtonTitles:nil];
514
    [alert show];
515 516 517 518 519 520 521 522 523 524 525 526
#else
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"DISK_FULL", nil)
                                                                             message:[NSString stringWithFormat:
                                                                                      NSLocalizedString(@"DISK_FULL_FORMAT", nil),
                                                                                      filename,
                                                                                      [[UIDevice currentDevice] model]]
                                                                      preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_CANCEL", nil)
                                                        style:UIAlertActionStyleCancel
                                                      handler:nil]];
    [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
#endif
527 528
}

529 530 531
- (void)processContent:(NSData*)data WithHeader:(MultipartMessageHeader*) header
{
    // here we just write the output from parser to the file.
532 533 534 535 536 537 538 539 540 541 542 543 544
    if (_storeFile) {
        @try {
            [_storeFile writeData:data];
        }
        @catch (NSException *exception) {
            APLog(@"File to write further data because storage is full.");
            [_storeFile closeFile];
            _storeFile = nil;
            /* don't block */
            [self performSelector:@selector(stop) withObject:nil afterDelay:0.1];
        }
    }

545 546 547 548 549
}

- (void)processEndOfPartWithHeader:(MultipartMessageHeader*)header
{
    // as the file part is over, we close the file.
550
    APLog(@"closing file");
551 552 553 554
    [_storeFile closeFile];
    _storeFile = nil;
}

555
- (BOOL)shouldDie
556
{
557 558
    if (_filepath) {
        if (_filepath.length > 0)
Felix Paul Kühne's avatar
Felix Paul Kühne committed
559
            [[VLCHTTPUploaderController sharedInstance] moveFileFrom:_filepath];
560
    }
561
    return [super shouldDie];
562 563
}

564 565
#pragma mark subtitle

Felix Paul Kühne's avatar
Felix Paul Kühne committed
566
- (NSMutableArray *)_listOfSubtitles
567
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
568
    NSMutableArray *listOfSubtitles = [[NSMutableArray alloc] init];
569
    NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
570
    NSArray *allFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsDirectory error:nil];
571
    NSString *filePath;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
572 573 574
    NSUInteger count = allFiles.count;
    for (NSUInteger i = 0; i < count; i++) {
        filePath = [[NSString stringWithFormat:@"%@/%@", documentsDirectory, allFiles[i]] stringByReplacingOccurrencesOfString:@"file://"withString:@""];
575
        if ([filePath isSupportedSubtitleFormat])
Felix Paul Kühne's avatar
Felix Paul Kühne committed
576
            [listOfSubtitles addObject:filePath];
577
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
578
    return listOfSubtitles;
579 580
}

581
- (NSString *)_checkIfSubtitleWasFound:(NSString *)filePath
582
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
583
    NSString *subtitlePath;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
584 585 586
    NSString *fileSub;
    NSString *currentPath;

587
    NSString *fileName = [[filePath lastPathComponent] stringByDeletingPathExtension];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
588 589 590
    if (fileName == nil)
        return nil;

Felix Paul Kühne's avatar
Felix Paul Kühne committed
591 592
    NSMutableArray *listOfSubtitles = [self _listOfSubtitles];
    NSUInteger count = listOfSubtitles.count;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
593

Felix Paul Kühne's avatar
Felix Paul Kühne committed
594 595 596
    for (NSUInteger i = 0; i < count; i++) {
        currentPath = listOfSubtitles[i];
        fileSub = [NSString stringWithFormat:@"%@", currentPath];
597
        if ([fileSub rangeOfString:fileName].location != NSNotFound)
Felix Paul Kühne's avatar
Felix Paul Kühne committed
598
            subtitlePath = currentPath;
599 600 601 602
    }
    return subtitlePath;
}

603
@end