Skip to content

Commit

Permalink
FIX: Download task exist file overwrite and cleanup if fail.
Browse files Browse the repository at this point in the history
Change-Id: I70a5a852492894c4b9db01f1b46050e0097e7428
  • Loading branch information
lancy committed Nov 3, 2016
1 parent ba46931 commit 277de20
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 6 deletions.
2 changes: 2 additions & 0 deletions Docs/README_cn.md
Expand Up @@ -70,6 +70,7 @@ YTKNetwork 的主要作者是:
* [maojj][maojjGithub]
* [veecci][veecciGithub]
* [tangqiaoboy][tangqiaoboyGithub]
* [skyline75489][skyline75489Github]

## 感谢

Expand All @@ -92,3 +93,4 @@ YTKNetwork 被许可在 MIT 协议下使用。查阅 LICENSE 文件来获得更
[lancyGithub]:https://github.com/lancy
[maojjGithub]:https://github.com/maojj
[veecciGithub]:https://github.com/veecci
[skyline75489Github]:https://github.com/skyline75489
2 changes: 1 addition & 1 deletion Framework/Info.plist
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.0</string>
<string>2.0.3</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
Expand Down
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -76,6 +76,7 @@ YTKNetwork is based on AFNetworking. You can find more detail about version comp
* [maojj][maojjGithub]
* [veecci][veecciGithub]
* [tangqiaoboy][tangqiaoboyGithub]
* [skyline75489][skyline75489Github]

## Acknowledgements

Expand All @@ -101,3 +102,4 @@ YTKNetwork is available under the MIT license. See the LICENSE file for more inf
[lancyGithub]:https://github.com/lancy
[maojjGithub]:https://github.com/maojj
[veecciGithub]:https://github.com/veecci
[skyline75489Github]:https://github.com/skyline75489
2 changes: 1 addition & 1 deletion YTKNetwork.podspec
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|

s.name = "YTKNetwork"
s.version = "2.0.2"
s.version = "2.0.3"
s.summary = "YTKNetwork is a high level request util based on AFNetworking."
s.homepage = "https://github.com/yuantiku/YTKNetwork"
s.license = "MIT"
Expand Down
10 changes: 6 additions & 4 deletions YTKNetwork/YTKBaseRequest.h
Expand Up @@ -159,7 +159,7 @@ typedef void(^YTKRequestCompletionBlock)(__kindof YTKBaseRequest *request);
/// `YTKResponseSerializerType`. Note this value can be nil if request failed.
///
/// @discussion If `resumableDownloadPath` and DownloadTask is using, this value will
/// be the path to which file is successfully saved (NSURL).
/// be the path to which file is successfully saved (NSURL), or nil if request failed.
@property (nonatomic, strong, readonly, nullable) id responseObject;

/// If you use `YTKResponseSerializerTypeJSON`, this is a convenience (and sematic) getter
Expand Down Expand Up @@ -211,9 +211,11 @@ typedef void(^YTKRequestCompletionBlock)(__kindof YTKBaseRequest *request);

/// This value is used to perform resumable download request. Default is nil.
///
/// @discussion NSURLSessionDownloadTask is used when this value is not nil. If request succeed, file will
/// be saved to this path automatically. For this to work, server must support `Range` and response
/// with proper `Last-Modified` and/or `Etag`. See `NSURLSessionDownloadTask` for more detail.
/// @discussion NSURLSessionDownloadTask is used when this value is not nil.
/// The exist file at the path will be removed before the request starts. If request succeed, file will
/// be saved to this path automatically, otherwise the response will be saved to `responseData`
/// and `responseString`. For this to work, server must support `Range` and response with
/// proper `Last-Modified` and/or `Etag`. See `NSURLSessionDownloadTask` for more detail.
@property (nonatomic, strong, nullable) NSString *resumableDownloadPath;

/// You can use this block to track the download progress. See also `resumableDownloadPath`.
Expand Down
21 changes: 21 additions & 0 deletions YTKNetwork/YTKNetworkAgent.m
Expand Up @@ -371,11 +371,24 @@ - (void)requestDidFailWithRequest:(YTKBaseRequest *)request error:(NSError *)err
YTKLog(@"Request %@ failed, status code = %ld, error = %@",
NSStringFromClass([request class]), (long)request.responseStatusCode, error.localizedDescription);

// Save incomplete download data.
NSData *incompleteDownloadData = error.userInfo[NSURLSessionDownloadTaskResumeData];
if (incompleteDownloadData) {
[incompleteDownloadData writeToURL:[self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath] atomically:YES];
}

// Load response from file and clean up if download task failed.
if ([request.responseObject isKindOfClass:[NSURL class]]) {
NSURL *url = request.responseObject;
if (url.isFileURL && [[NSFileManager defaultManager] fileExistsAtPath:url.path]) {
request.responseData = [NSData dataWithContentsOfURL:url];
request.responseString = [[NSString alloc] initWithData:request.responseData encoding:[YTKNetworkUtils stringEncodingWithRequest:request]];

[[NSFileManager defaultManager] removeItemAtURL:url error:nil];
}
request.responseObject = nil;
}

@autoreleasepool {
[request requestFailedPreprocessor];
}
Expand Down Expand Up @@ -462,6 +475,14 @@ - (NSURLSessionDownloadTask *)downloadTaskWithDownloadPath:(NSString *)downloadP
downloadTargetPath = downloadPath;
}

// AFN use `moveItemAtURL` to move downloaded file to target path,
// this method aborts the move attempt if a file already exist at the path.
// So we remove the exist file before we start the download task.
// https://github.com/AFNetworking/AFNetworking/issues/3775
if ([[NSFileManager defaultManager] fileExistsAtPath:downloadTargetPath]) {
[[NSFileManager defaultManager] removeItemAtPath:downloadTargetPath error:nil];
}

BOOL resumeDataFileExists = [[NSFileManager defaultManager] fileExistsAtPath:[self incompleteDownloadTempPathForDownloadPath:downloadPath].path];
NSData *data = [NSData dataWithContentsOfURL:[self incompleteDownloadTempPathForDownloadPath:downloadPath]];
BOOL resumeDataIsValid = [YTKNetworkUtils validateResumeData:data];
Expand Down
34 changes: 34 additions & 0 deletions YTKNetworkTests/Test Cases/YTKResumableDownloadTests.m
Expand Up @@ -12,6 +12,7 @@
#import "AFNetworking.h"

NSString *const kTestDownloadURL = @"https://qd.myapp.com/myapp/qqteam/AndroidQQ/mobileqq_android.apk";
NSString *const kTestFailDownloadURL = @"https://qd.myapp.com/myapp/qqteam/AndroidQQ/mobileqq_android.apkfail";

@interface YTKResumableDownloadTests : YTKTestCase

Expand Down Expand Up @@ -97,5 +98,38 @@ - (void)testResumableDownloadWithDirectoryPath {
[self expectSuccess:req2];
}

- (void)testResumableDownloadOverwriteAndCleanup {
[[YTKNetworkAgent sharedAgent] resetURLSessionManager];
[[YTKNetworkAgent sharedAgent] manager].responseSerializer.acceptableContentTypes = nil;
NSString *path = [[self saveBasePath] stringByAppendingPathComponent:@"downloaded.bin"];

// Create a exist file
NSString *testString = @"TEST";
NSData *stringData = [testString dataUsingEncoding:NSUTF8StringEncoding];
[stringData writeToFile:path atomically:YES];

// Download req file
YTKDownloadRequest *req = [[YTKDownloadRequest alloc] initWithTimeout:self.networkTimeout requestUrl:kTestDownloadURL];
req.resumableDownloadPath = path;
req.resumableDownloadProgressBlock = ^(NSProgress *progress) {
XCTAssertTrue(progress.completedUnitCount > 0);
NSLog(@"Downloading: %lld / %lld", progress.completedUnitCount, progress.totalUnitCount);
};

[self expectSuccess:req];
XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:path]);
XCTAssertTrue(([[NSFileManager defaultManager] contentsAtPath:path].length > stringData.length));

YTKDownloadRequest *req2 = [[YTKDownloadRequest alloc] initWithTimeout:self.networkTimeout requestUrl:kTestFailDownloadURL];
req2.resumableDownloadPath = path;
req2.resumableDownloadProgressBlock = ^(NSProgress *progress) {
XCTAssertTrue(progress.completedUnitCount > 0);
NSLog(@"Downloading: %lld / %lld", progress.completedUnitCount, progress.totalUnitCount);
};

[self expectFailure:req2];
XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:path]);
XCTAssertTrue(req2.responseData.length > 0 && req2.responseString.length > 0);
}

@end

0 comments on commit 277de20

Please sign in to comment.