VLCHTTPFileDownloader.m 9.89 KB
Newer Older
1 2 3 4
/*****************************************************************************
 * VLCHTTPFileDownloader.m
 * VLC for iOS
 *****************************************************************************
5
 * Copyright (c) 2013-2018 VideoLAN. All rights reserved.
6 7 8 9 10 11 12
 * $Id$
 *
 * Authors: Felix Paul Kühne <fkuehne # videolan.org>
 *          Pierre Sagaspe <pierre.sagaspe # me.com>
 *
 * Refer to the COPYING file of the official project for license.
 *****************************************************************************/
13 14

#import "VLCHTTPFileDownloader.h"
15
#import "NSString+SupportedMedia.h"
16
#import "VLCActivityManager.h"
17
#import "UIDevice+VLC.h"
18
#import "VLCMediaFileDiscoverer.h"
19

20
@interface VLCHTTPFileDownloader () <NSURLSessionDelegate>
21 22
{
    NSString *_filePath;
23
    long long _expectedDownloadSize;
24
    NSUInteger _receivedDataSize;
25
    NSString *_fileName;
26
    NSURLSessionTask *_sessionTask;
27
    NSMutableURLRequest *_originalRequest;
28
    NSUInteger _statusCode;
29 30 31 32 33 34
}

@end

@implementation VLCHTTPFileDownloader

35 36 37 38 39
- (NSString *)userReadableDownloadName
{
    return _fileName;
}

40 41
- (void)downloadFileFromURL:(NSURL *)url
{
42
    [self downloadFileFromURL:url withFileName:nil];
43 44
}

45
- (void)downloadFileFromURL:(NSURL *)url withFileName:(NSString*)fileName
Pierre SAGASPE's avatar
Pierre SAGASPE committed
46
{
47
    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
48
    NSString *basePath = [searchPaths[0] stringByAppendingPathComponent:@"Upload"];
49 50 51
    if (fileName)
        _fileName = fileName;
    else
52
        _fileName = [url.lastPathComponent stringByRemovingPercentEncoding];
53 54 55 56 57

    if (_fileName.pathExtension.length == 0 || ![_fileName isSupportedFormat]) {
        _fileName = [_fileName stringByAppendingPathExtension:@"vlc"];
    }

58 59 60 61
    _filePath = [basePath stringByAppendingPathComponent:_fileName];
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:basePath])
        [fileManager createDirectoryAtPath:basePath withIntermediateDirectories:YES attributes:nil error:nil];
Pierre SAGASPE's avatar
Pierre SAGASPE committed
62 63
    _expectedDownloadSize = _receivedDataSize = 0;
    NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url];
64
    [theRequest addValue:[NSString stringWithFormat:@"Mozilla/5.0 (%@; CPU OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/%@ Mobile/11A465 Safari/9537.53 VLC for iOS/%@", UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? @"iPad" : @"iPhone", [[UIDevice currentDevice] systemVersion], [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]] forHTTPHeaderField:@"User-Agent"];
65
    _originalRequest = [theRequest mutableCopy];
66 67 68 69 70

    NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    _sessionTask = [urlSession dataTaskWithRequest:theRequest];
    [_sessionTask resume];
    if (!_sessionTask) {
Pierre SAGASPE's avatar
Pierre SAGASPE committed
71 72 73 74
        APLog(@"failed to establish connection");
        _downloadInProgress = NO;
    } else {
        _downloadInProgress = YES;
75 76 77
        VLCActivityManager *activityManager = [VLCActivityManager defaultManager];
        [activityManager networkActivityStarted];
        [activityManager disableIdleTimer];
Pierre SAGASPE's avatar
Pierre SAGASPE committed
78 79 80
    }
}

81 82 83 84 85 86 87 88 89 90 91 92
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse
{
    if (redirectResponse) {
        NSURL *URL = [request URL];

        NSFileManager *fileManager = [NSFileManager defaultManager];

        if ([fileManager fileExistsAtPath:_filePath])
            [fileManager removeItemAtPath:_filePath error:nil];

        NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
        NSString *basePath = [searchPaths[0] stringByAppendingPathComponent:@"Upload"];
93
        _fileName = [[URL lastPathComponent] stringByRemovingPercentEncoding];
94 95 96 97 98 99 100 101 102 103 104
        _filePath = [basePath stringByAppendingPathComponent:_fileName];
        if (![fileManager fileExistsAtPath:basePath])
            [fileManager createDirectoryAtPath:basePath withIntermediateDirectories:YES attributes:nil error:nil];

        NSMutableURLRequest *newRequest = [_originalRequest mutableCopy];
        [newRequest setURL:URL];
        return newRequest;
    } else
        return request;
}

105
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
106
{
107 108 109
    completionHandler(NSURLSessionResponseAllow);
    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
    _statusCode = [httpResponse statusCode];
110
    if (_statusCode == 200) {
111 112
        _expectedDownloadSize = [response expectedContentLength];
        APLog(@"expected download size: %lli", _expectedDownloadSize);
113 114 115 116 117 118
        if (![[response suggestedFilename] isSupportedFormat]) { //handle unsupported format
            VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"FILE_NOT_SUPPORTED", nil)
                                                              message:[NSString stringWithFormat:NSLocalizedString(@"FILE_NOT_SUPPORTED_LONG", nil), [response suggestedFilename]]
                                                    cancelButtonTitle:NSLocalizedString(@"BUTTON_OK", nil)
                                                    otherButtonTitles:nil];
            [alert show];
119
            [_sessionTask cancel];
120
            [self _downloadEnded];
121 122 123
            return;
        }
        if (_expectedDownloadSize  > [[UIDevice currentDevice] VLCFreeDiskSpace].longLongValue) { //handle too big a download
124 125
            VLCAlertView *alert = [[VLCAlertView alloc] initWithTitle:NSLocalizedString(@"DISK_FULL", nil)
                                                             message:[NSString stringWithFormat:NSLocalizedString(@"DISK_FULL_FORMAT", nil), _fileName, [[UIDevice currentDevice] model]]
126
                                                             delegate:self
127
                                                    cancelButtonTitle:NSLocalizedString(@"BUTTON_OK", nil)
128
                                                    otherButtonTitles:nil];
129
            [alert show];
130
            [_sessionTask cancel];
131 132
            [self _downloadEnded];
            return;
133
        }
134
        [self.delegate downloadStarted];
135
    } else {
136
        APLog(@"unhandled status code %lu", (unsigned long)_statusCode);
137
        if ([self.delegate respondsToSelector:@selector(downloadFailedWithErrorDescription:)])
138
            [self.delegate downloadFailedWithErrorDescription:[NSString stringWithFormat:NSLocalizedString(@"HTTP_DOWNLOAD_FAILED",nil), _statusCode]];
139 140 141
    }
}

142
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
143 144
{
    NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:_filePath];
145
    if (!fileHandle && _statusCode != 404) {
146 147 148 149 150 151
        // create file
        [[NSFileManager defaultManager] createFileAtPath:_filePath contents:nil attributes:nil];
        fileHandle = [NSFileHandle fileHandleForWritingAtPath:_filePath];

        if (!fileHandle) {
            APLog(@"file creation failed, no data was saved");
152
            if ([self.delegate respondsToSelector:@selector(downloadFailedWithErrorDescription:)])
153
                [self.delegate downloadFailedWithErrorDescription:NSLocalizedString(@"HTTP_FILE_CREATION_FAILED",nil)];
154 155 156 157 158 159 160 161
            return;
        }
    }

    @try {
        [fileHandle seekToEndOfFile];
        [fileHandle writeData:data];

162 163 164
        _receivedDataSize = _receivedDataSize + [data length];
        if ([self.delegate respondsToSelector:@selector(progressUpdatedTo:receivedDataSize:expectedDownloadSize:)])
            [self.delegate progressUpdatedTo: (float)_receivedDataSize / (float)_expectedDownloadSize receivedDataSize:_receivedDataSize expectedDownloadSize:_expectedDownloadSize];
165 166 167 168 169 170 171 172
    }
    @catch (NSException * e) {
        APLog(@"exception when writing to file %@", _filePath);
    }

    [fileHandle closeFile];
}

173
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
174
{
175 176 177 178 179 180 181 182 183 184 185 186
    if (error.code != -999) {
        if (error) {
            APLog(@"http file download failed (%li)", (long)error.code);
            if ([self.delegate respondsToSelector:@selector(downloadFailedWithErrorDescription:)])
                [self.delegate downloadFailedWithErrorDescription:error.description];
        } else {
            APLog(@"http file download complete");
        }
        [self _downloadEnded];
    } else {
        APLog(@"http file download canceled");
    }
187 188 189 190
}

- (void)cancelDownload
{
191
    [_sessionTask cancel];
192 193 194 195 196
    /* remove partially downloaded content */
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ([fileManager fileExistsAtPath:_filePath])
        [fileManager removeItemAtPath:_filePath error:nil];

197
    if ([self.delegate respondsToSelector:@selector(downloadFailedWithErrorDescription:)])
198
        [self.delegate downloadFailedWithErrorDescription:NSLocalizedString(@"HTTP_DOWNLOAD_CANCELLED",nil)];
199

200 201 202 203 204 205
    [self _downloadEnded];
}

- (void)_downloadEnded
{
    _downloadInProgress = NO;
206 207 208
    VLCActivityManager *activityManager = [VLCActivityManager defaultManager];
    [activityManager networkActivityStopped];
    [activityManager activateIdleTimer];
209

210 211 212 213 214 215 216 217
    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *libraryPath = searchPaths[0];

    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *finalFilePath = [libraryPath stringByAppendingPathComponent:_fileName];

    if ([fileManager fileExistsAtPath:_filePath]) {
        [fileManager moveItemAtPath:_filePath toPath:finalFilePath error:nil];
218
        [[VLCMediaFileDiscoverer sharedInstance] performSelectorOnMainThread:@selector(updateMediaList) withObject:nil waitUntilDone:NO];
219 220
    }

221
    [self.delegate downloadEnded];
222 223 224
}

@end