Skip to content

Commit 277de20

Browse files
committed
FIX: Download task exist file overwrite and cleanup if fail.
Change-Id: I70a5a852492894c4b9db01f1b46050e0097e7428
1 parent ba46931 commit 277de20

File tree

7 files changed

+67
-6
lines changed

7 files changed

+67
-6
lines changed

Docs/README_cn.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ YTKNetwork 的主要作者是:
7070
* [maojj][maojjGithub]
7171
* [veecci][veecciGithub]
7272
* [tangqiaoboy][tangqiaoboyGithub]
73+
* [skyline75489][skyline75489Github]
7374

7475
## 感谢
7576

@@ -92,3 +93,4 @@ YTKNetwork 被许可在 MIT 协议下使用。查阅 LICENSE 文件来获得更
9293
[lancyGithub]:https://github.com/lancy
9394
[maojjGithub]:https://github.com/maojj
9495
[veecciGithub]:https://github.com/veecci
96+
[skyline75489Github]:https://github.com/skyline75489

Framework/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<key>CFBundlePackageType</key>
1616
<string>FMWK</string>
1717
<key>CFBundleShortVersionString</key>
18-
<string>2.0</string>
18+
<string>2.0.3</string>
1919
<key>CFBundleSignature</key>
2020
<string>????</string>
2121
<key>CFBundleVersion</key>

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ YTKNetwork is based on AFNetworking. You can find more detail about version comp
7676
* [maojj][maojjGithub]
7777
* [veecci][veecciGithub]
7878
* [tangqiaoboy][tangqiaoboyGithub]
79+
* [skyline75489][skyline75489Github]
7980

8081
## Acknowledgements
8182

@@ -101,3 +102,4 @@ YTKNetwork is available under the MIT license. See the LICENSE file for more inf
101102
[lancyGithub]:https://github.com/lancy
102103
[maojjGithub]:https://github.com/maojj
103104
[veecciGithub]:https://github.com/veecci
105+
[skyline75489Github]:https://github.com/skyline75489

YTKNetwork.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Pod::Spec.new do |s|
22

33
s.name = "YTKNetwork"
4-
s.version = "2.0.2"
4+
s.version = "2.0.3"
55
s.summary = "YTKNetwork is a high level request util based on AFNetworking."
66
s.homepage = "https://github.com/yuantiku/YTKNetwork"
77
s.license = "MIT"

YTKNetwork/YTKBaseRequest.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ typedef void(^YTKRequestCompletionBlock)(__kindof YTKBaseRequest *request);
159159
/// `YTKResponseSerializerType`. Note this value can be nil if request failed.
160160
///
161161
/// @discussion If `resumableDownloadPath` and DownloadTask is using, this value will
162-
/// be the path to which file is successfully saved (NSURL).
162+
/// be the path to which file is successfully saved (NSURL), or nil if request failed.
163163
@property (nonatomic, strong, readonly, nullable) id responseObject;
164164

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

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

219221
/// You can use this block to track the download progress. See also `resumableDownloadPath`.

YTKNetwork/YTKNetworkAgent.m

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,11 +371,24 @@ - (void)requestDidFailWithRequest:(YTKBaseRequest *)request error:(NSError *)err
371371
YTKLog(@"Request %@ failed, status code = %ld, error = %@",
372372
NSStringFromClass([request class]), (long)request.responseStatusCode, error.localizedDescription);
373373

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

380+
// Load response from file and clean up if download task failed.
381+
if ([request.responseObject isKindOfClass:[NSURL class]]) {
382+
NSURL *url = request.responseObject;
383+
if (url.isFileURL && [[NSFileManager defaultManager] fileExistsAtPath:url.path]) {
384+
request.responseData = [NSData dataWithContentsOfURL:url];
385+
request.responseString = [[NSString alloc] initWithData:request.responseData encoding:[YTKNetworkUtils stringEncodingWithRequest:request]];
386+
387+
[[NSFileManager defaultManager] removeItemAtURL:url error:nil];
388+
}
389+
request.responseObject = nil;
390+
}
391+
379392
@autoreleasepool {
380393
[request requestFailedPreprocessor];
381394
}
@@ -462,6 +475,14 @@ - (NSURLSessionDownloadTask *)downloadTaskWithDownloadPath:(NSString *)downloadP
462475
downloadTargetPath = downloadPath;
463476
}
464477

478+
// AFN use `moveItemAtURL` to move downloaded file to target path,
479+
// this method aborts the move attempt if a file already exist at the path.
480+
// So we remove the exist file before we start the download task.
481+
// https://github.com/AFNetworking/AFNetworking/issues/3775
482+
if ([[NSFileManager defaultManager] fileExistsAtPath:downloadTargetPath]) {
483+
[[NSFileManager defaultManager] removeItemAtPath:downloadTargetPath error:nil];
484+
}
485+
465486
BOOL resumeDataFileExists = [[NSFileManager defaultManager] fileExistsAtPath:[self incompleteDownloadTempPathForDownloadPath:downloadPath].path];
466487
NSData *data = [NSData dataWithContentsOfURL:[self incompleteDownloadTempPathForDownloadPath:downloadPath]];
467488
BOOL resumeDataIsValid = [YTKNetworkUtils validateResumeData:data];

YTKNetworkTests/Test Cases/YTKResumableDownloadTests.m

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#import "AFNetworking.h"
1313

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

1617
@interface YTKResumableDownloadTests : YTKTestCase
1718

@@ -97,5 +98,38 @@ - (void)testResumableDownloadWithDirectoryPath {
9798
[self expectSuccess:req2];
9899
}
99100

101+
- (void)testResumableDownloadOverwriteAndCleanup {
102+
[[YTKNetworkAgent sharedAgent] resetURLSessionManager];
103+
[[YTKNetworkAgent sharedAgent] manager].responseSerializer.acceptableContentTypes = nil;
104+
NSString *path = [[self saveBasePath] stringByAppendingPathComponent:@"downloaded.bin"];
105+
106+
// Create a exist file
107+
NSString *testString = @"TEST";
108+
NSData *stringData = [testString dataUsingEncoding:NSUTF8StringEncoding];
109+
[stringData writeToFile:path atomically:YES];
110+
111+
// Download req file
112+
YTKDownloadRequest *req = [[YTKDownloadRequest alloc] initWithTimeout:self.networkTimeout requestUrl:kTestDownloadURL];
113+
req.resumableDownloadPath = path;
114+
req.resumableDownloadProgressBlock = ^(NSProgress *progress) {
115+
XCTAssertTrue(progress.completedUnitCount > 0);
116+
NSLog(@"Downloading: %lld / %lld", progress.completedUnitCount, progress.totalUnitCount);
117+
};
118+
119+
[self expectSuccess:req];
120+
XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:path]);
121+
XCTAssertTrue(([[NSFileManager defaultManager] contentsAtPath:path].length > stringData.length));
122+
123+
YTKDownloadRequest *req2 = [[YTKDownloadRequest alloc] initWithTimeout:self.networkTimeout requestUrl:kTestFailDownloadURL];
124+
req2.resumableDownloadPath = path;
125+
req2.resumableDownloadProgressBlock = ^(NSProgress *progress) {
126+
XCTAssertTrue(progress.completedUnitCount > 0);
127+
NSLog(@"Downloading: %lld / %lld", progress.completedUnitCount, progress.totalUnitCount);
128+
};
129+
130+
[self expectFailure:req2];
131+
XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:path]);
132+
XCTAssertTrue(req2.responseData.length > 0 && req2.responseString.length > 0);
133+
}
100134

101135
@end

0 commit comments

Comments
 (0)