Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
//
// HCPFileDownloader.m
//
// Created by Nikolay Demyankov on 11.08.15.
//
#import "HCPFileDownloader.h"
#import "HCPManifestFile.h"
#import "NSData+HCPMD5.h"
#import "NSError+HCPExtension.h"
@interface HCPFileDownloader()<NSURLSessionDownloadDelegate> {
NSArray *_filesList;
NSURL *_contentURL;
NSURL *_folderURL;
NSDictionary *_headers;
NSURLSession *_session;
HCPFileDownloadCompletionBlock _complitionHandler;
NSUInteger _downloadCounter;
}
@end
static NSUInteger const TIMEOUT = 300;
@implementation HCPFileDownloader
#pragma mark Public API
- (instancetype)initWithFiles:(NSArray *)filesList srcDirURL:(NSURL *)contentURL dstDirURL:(NSURL *)folderURL requestHeaders:(NSDictionary *)headers {
self = [super init];
if (self) {
_filesList = filesList;
_contentURL = contentURL;
_folderURL = folderURL;
_headers = headers;
}
return self;
}
- (NSURLSession *)sessionWithHeaders:(NSDictionary *)headers {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
configuration.timeoutIntervalForRequest = TIMEOUT;
configuration.timeoutIntervalForResource = TIMEOUT;
if (headers) {
[configuration setHTTPAdditionalHeaders:headers];
}
return [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
}
- (void)startDownloadWithCompletionBlock:(HCPFileDownloadCompletionBlock)block {
_complitionHandler = block;
_downloadCounter = 0;
_session = [self sessionWithHeaders:_headers];
[self launchDownloadTaskForFile:_filesList[0]];
}
#pragma mark NSURLSessionTaskDelegate delegate
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error {
if (error && _complitionHandler) {
_complitionHandler(error);
_session = nil;
}
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
NSError *error = nil;
if (![self moveLoadedFile:location forFile:_filesList[_downloadCounter] toFolder:_folderURL error:&error]) {
[_session invalidateAndCancel];
_session = nil;
_complitionHandler(error);
return;
}
_downloadCounter++;
if (_downloadCounter >= _filesList.count) {
[_session finishTasksAndInvalidate];
_session = nil;
_complitionHandler(nil);
return;
}
[self launchDownloadTaskForFile:_filesList[_downloadCounter]];
}
- (void)launchDownloadTaskForFile:(HCPManifestFile *)file {
NSURL *url = [_contentURL URLByAppendingPathComponent:file.name];
NSLog(@"Starting file download: %@", url.absoluteString);
[[_session downloadTaskWithURL:url] resume];
}
#pragma Private API
/**
* Check if loaded file is corrupted.
*
* @param file file's url on the local storage
* @param checksum supposed checksum of the data
*
* @return <code>YES</code> if file is corrupted; <code>NO</code> if file is valid
*/
- (BOOL)isFileCorrupted:(NSURL *)file checksum:(NSString *)checksum {
NSString *dataHash = [[NSData dataWithContentsOfURL:file] md5];
if ([dataHash isEqualToString:checksum]) {
return NO;
}
NSLog(@"Hash %@ doesn't match the checksum %@", dataHash, checksum);
return YES;
}
/**
* Move loaded file from the tmp folder to the download folder.
*
* @param loadedFile loaded file url in the tmp folder
* @param forFile what file we loaded according to the manifest
* @param folderURL folder, where to move it
* @param error error entry; <code>nil</code> - if saved successfully;
*
* @return <code>YES</code> - if data is saved; <code>NO</code> - otherwise
*/
- (BOOL)moveLoadedFile:(NSURL *)loadedFile forFile:(HCPManifestFile *)file toFolder:(NSURL *)folderURL error:(NSError **)error {
if ([self isFileCorrupted:loadedFile checksum:file.md5Hash]) {
NSString *errorMsg = [NSString stringWithFormat:@"File %@ is corrupted", file.name];
*error = [NSError errorWithCode:kHCPFailedToDownloadUpdateFilesErrorCode description:errorMsg];
return NO;
}
NSURL *filePath = [folderURL URLByAppendingPathComponent:file.name];
NSFileManager *fileManager = [NSFileManager defaultManager];
// remove old version of the file
if ([fileManager fileExistsAtPath:filePath.path]) {
[fileManager removeItemAtURL:filePath error:nil];
}
// create storage directories
[fileManager createDirectoryAtPath:[filePath.path stringByDeletingLastPathComponent]
withIntermediateDirectories:YES
attributes:nil
error:nil];
// write data
return [fileManager moveItemAtURL:loadedFile toURL:filePath error: error];
}
@end