mirror of
https://github.com/gnustep/libs-base.git
synced 2025-04-23 00:41:02 +00:00
Merge pull request #204 from Fokka-Engineering/nsurlsession-additions
Implement NSURLSessionDownloadTask and NSURLSessionDownloadDelegate
This commit is contained in:
commit
ed8be18af9
6 changed files with 314 additions and 13 deletions
|
@ -24,6 +24,7 @@
|
|||
@class NSURLResponse;
|
||||
@class NSURLSessionConfiguration;
|
||||
@class NSURLSessionDataTask;
|
||||
@class NSURLSessionDownloadTask;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -110,6 +111,12 @@ GS_EXPORT_CLASS
|
|||
/* Creates a data task to retrieve the contents of the given URL. */
|
||||
- (NSURLSessionDataTask*) dataTaskWithURL: (NSURL*)url;
|
||||
|
||||
/* Creates a download task with the given request. */
|
||||
- (NSURLSessionDownloadTask *) downloadTaskWithRequest: (NSURLRequest *)request;
|
||||
|
||||
/* Creates a download task to download the contents of the given URL. */
|
||||
- (NSURLSessionDownloadTask *) downloadTaskWithURL: (NSURL *)url;
|
||||
|
||||
@end
|
||||
|
||||
typedef NS_ENUM(NSUInteger, NSURLSessionTaskState) {
|
||||
|
@ -430,6 +437,37 @@ didReceiveChallenge: (NSURLAuthenticationChallenge*)challenge
|
|||
|
||||
@end
|
||||
|
||||
@protocol NSURLSessionDownloadDelegate <NSURLSessionTaskDelegate>
|
||||
|
||||
/* Sent when a download task that has completed a download. The delegate should
|
||||
* copy or move the file at the given location to a new location as it will be
|
||||
* removed when the delegate message returns. URLSession:task:didCompleteWithError: will
|
||||
* still be called.
|
||||
*/
|
||||
- (void) URLSession: (NSURLSession *)session
|
||||
downloadTask: (NSURLSessionDownloadTask *)downloadTask
|
||||
didFinishDownloadingToURL: (NSURL *)location;
|
||||
|
||||
@optional
|
||||
/* Sent periodically to notify the delegate of download progress. */
|
||||
- (void) URLSession: (NSURLSession *)session
|
||||
downloadTask: (NSURLSessionDownloadTask *)downloadTask
|
||||
didWriteData: (int64_t)bytesWritten
|
||||
totalBytesWritten: (int64_t)totalBytesWritten
|
||||
totalBytesExpectedToWrite: (int64_t)totalBytesExpectedToWrite;
|
||||
|
||||
/* Sent when a download has been resumed. If a download failed with an
|
||||
* error, the -userInfo dictionary of the error will contain an
|
||||
* NSURLSessionDownloadTaskResumeData key, whose value is the resume
|
||||
* data.
|
||||
*/
|
||||
- (void) URLSession: (NSURLSession *)session
|
||||
downloadTask: (NSURLSessionDownloadTask *)downloadTask
|
||||
didResumeAtOffset: (int64_t)fileOffset
|
||||
expectedTotalBytes: (int64_t)expectedTotalBytes;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#import "GSURLPrivate.h"
|
||||
#import "GSNativeProtocol.h"
|
||||
#import "GSTransferState.h"
|
||||
#import "GSURLSessionTaskBody.h"
|
||||
|
@ -9,6 +10,7 @@
|
|||
#import "Foundation/NSURL.h"
|
||||
#import "Foundation/NSURLError.h"
|
||||
#import "Foundation/NSURLSession.h"
|
||||
#import "Foundation/NSFileHandle.h"
|
||||
|
||||
|
||||
static BOOL isEasyHandlePaused(GSNativeProtocolInternalState state)
|
||||
|
@ -97,6 +99,8 @@ static BOOL isEasyHandleAddedToMultiHandle(GSNativeProtocolInternalState state)
|
|||
|
||||
- (void) setError: (NSError*)error;
|
||||
|
||||
- (void) setCountOfBytesReceived: (int64_t)count;
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSURLSessionTask (Internal)
|
||||
|
@ -138,6 +142,11 @@ static BOOL isEasyHandleAddedToMultiHandle(GSNativeProtocolInternalState state)
|
|||
ASSIGN(_error, error);
|
||||
}
|
||||
|
||||
- (void) setCountOfBytesReceived: (int64_t)count
|
||||
{
|
||||
_countOfBytesReceived = count;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GSCompletionAction
|
||||
|
@ -493,24 +502,24 @@ static BOOL isEasyHandleAddedToMultiHandle(GSNativeProtocolInternalState state)
|
|||
|
||||
- (void) notifyDelegateAboutReceivedData: (NSData*)data
|
||||
{
|
||||
NSURLSessionTask *task;
|
||||
id<NSURLSessionDelegate> delegate;
|
||||
NSURLSession *session;
|
||||
NSURLSessionTask *task;
|
||||
id<NSURLSessionDelegate> delegate;
|
||||
|
||||
task = [self task];
|
||||
|
||||
NSAssert(nil != task, @"Cannot notify");
|
||||
|
||||
delegate = [[task session] delegate];
|
||||
session = [task session];
|
||||
NSAssert(nil != session, @"Missing session");
|
||||
|
||||
delegate = [session delegate];
|
||||
if (nil != delegate
|
||||
&& [task isKindOfClass: [NSURLSessionDataTask class]]
|
||||
&& [delegate respondsToSelector: @selector(URLSession:dataTask:didReceiveData:)])
|
||||
{
|
||||
id<NSURLSessionDataDelegate> dataDelegate;
|
||||
NSURLSessionDataTask *dataTask;
|
||||
NSURLSession *session;
|
||||
|
||||
session = [task session];
|
||||
NSAssert(nil != session, @"Missing session");
|
||||
|
||||
dataDelegate = (id<NSURLSessionDataDelegate>)delegate;
|
||||
dataTask = (NSURLSessionDataTask*)task;
|
||||
[[session delegateQueue] addOperationWithBlock:
|
||||
|
@ -520,6 +529,40 @@ static BOOL isEasyHandleAddedToMultiHandle(GSNativeProtocolInternalState state)
|
|||
didReceiveData: data];
|
||||
}];
|
||||
}
|
||||
/* Don't check whether delegate respondsToSelector.
|
||||
* This delegate is optional. */
|
||||
if (nil != delegate
|
||||
&& [task isKindOfClass: [NSURLSessionDownloadTask class]])
|
||||
{
|
||||
id<NSURLSessionDownloadDelegate> downloadDelegate;
|
||||
NSURLSessionDownloadTask *downloadTask;
|
||||
GSDataDrain *dataDrain;
|
||||
NSFileHandle *fileHandle;
|
||||
|
||||
downloadDelegate = (id<NSURLSessionDownloadDelegate>)delegate;
|
||||
downloadTask = (NSURLSessionDownloadTask*)task;
|
||||
dataDrain = [_transferState bodyDataDrain];
|
||||
|
||||
/* Write to file. GSDataDrain opens the fileHandle. */
|
||||
fileHandle = [dataDrain fileHandle];
|
||||
[fileHandle seekToEndOfFile];
|
||||
[fileHandle writeData: data];
|
||||
|
||||
if ([delegate respondsToSelector: @selector
|
||||
(URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)])
|
||||
{
|
||||
/* Calculate received data length */
|
||||
[task setCountOfBytesReceived: (int64_t)[data length] + [task countOfBytesReceived]];
|
||||
[[session delegateQueue] addOperationWithBlock:
|
||||
^{
|
||||
[downloadDelegate URLSession: session
|
||||
downloadTask: downloadTask
|
||||
didWriteData: (int64_t)[data length]
|
||||
totalBytesWritten: [task countOfBytesReceived]
|
||||
totalBytesExpectedToWrite: [task countOfBytesExpectedToReceive]];
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) notifyDelegateAboutUploadedDataCount: (int64_t)count
|
||||
|
@ -673,9 +716,10 @@ static BOOL isEasyHandleAddedToMultiHandle(GSNativeProtocolInternalState state)
|
|||
|
||||
- (void) completeTask
|
||||
{
|
||||
NSURLSessionTask *task;
|
||||
GSDataDrain *bodyDataDrain;
|
||||
id<NSURLProtocolClient> client;
|
||||
NSURLSessionTask *task;
|
||||
GSDataDrain *bodyDataDrain;
|
||||
id<NSURLProtocolClient> client;
|
||||
id<NSURLSessionDelegate> delegate;
|
||||
|
||||
NSAssert(_internalState == GSNativeProtocolInternalStateTransferCompleted,
|
||||
@"Trying to complete the task, but its transfer isn't complete.");
|
||||
|
@ -683,6 +727,7 @@ static BOOL isEasyHandleAddedToMultiHandle(GSNativeProtocolInternalState state)
|
|||
task = [self task];
|
||||
[task setResponse: [_transferState response]];
|
||||
client = [self client];
|
||||
delegate = [[task session] delegate];
|
||||
|
||||
// We don't want a timeout to be triggered after this. The timeout timer
|
||||
// needs to be cancelled.
|
||||
|
@ -712,6 +757,16 @@ static BOOL isEasyHandleAddedToMultiHandle(GSNativeProtocolInternalState state)
|
|||
[self setInternalState: GSNativeProtocolInternalStateTaskCompleted];
|
||||
}
|
||||
|
||||
// Add temporary file URL to NSURLRequest properties
|
||||
// and close the fileHandle
|
||||
if (nil != delegate
|
||||
&& [task isKindOfClass: [NSURLSessionDownloadTask class]])
|
||||
{
|
||||
[[bodyDataDrain fileHandle] closeFile];
|
||||
[[self request] _setProperty: [bodyDataDrain fileURL]
|
||||
forKey: @"tempFileURL"];
|
||||
}
|
||||
|
||||
if ([client respondsToSelector: @selector(URLProtocolDidFinishLoading:)])
|
||||
{
|
||||
[client URLProtocolDidFinishLoading: self];
|
||||
|
|
|
@ -10,6 +10,9 @@
|
|||
#import "Foundation/NSURLError.h"
|
||||
#import "Foundation/NSURLResponse.h"
|
||||
#import "Foundation/NSURLSession.h"
|
||||
#import "Foundation/NSUUID.h"
|
||||
#import "Foundation/NSFileManager.h"
|
||||
#import "Foundation/NSPathUtilities.h"
|
||||
|
||||
#define GS_DELIMITERS_CR 0x0d
|
||||
#define GS_DELIMITERS_LR 0x0a
|
||||
|
@ -277,6 +280,21 @@
|
|||
|
||||
- (NSURL*) fileURL
|
||||
{
|
||||
/* Generate a random fileURL if fileURL is not initialized.
|
||||
* It would be nice to have this implemented in an initializer. */
|
||||
if (!_fileURL)
|
||||
{
|
||||
NSUUID *randomUUID;
|
||||
NSString *fileName;
|
||||
NSURL *tempURL;
|
||||
|
||||
randomUUID = [NSUUID UUID];
|
||||
fileName = [[randomUUID UUIDString] stringByAppendingPathExtension: @"tmp"];
|
||||
tempURL = [NSURL fileURLWithPath: NSTemporaryDirectory()];
|
||||
|
||||
_fileURL = [NSURL fileURLWithPath: fileName relativeToURL: tempURL];
|
||||
}
|
||||
|
||||
return _fileURL;
|
||||
}
|
||||
|
||||
|
@ -287,6 +305,17 @@
|
|||
|
||||
- (NSFileHandle*) fileHandle
|
||||
{
|
||||
/* Create temporary file and open a fileHandle for writing. */
|
||||
if (!_fileHandle)
|
||||
{
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
[fileManager createFileAtPath: [[self fileURL] relativePath]
|
||||
contents: nil
|
||||
attributes: nil];
|
||||
|
||||
_fileHandle = [NSFileHandle fileHandleForWritingToURL: [self fileURL] error: NULL];
|
||||
}
|
||||
|
||||
return _fileHandle;
|
||||
}
|
||||
|
||||
|
|
|
@ -284,6 +284,34 @@ static int nextSessionIdentifier()
|
|||
return [self dataTaskWithRequest: request];
|
||||
}
|
||||
|
||||
- (NSURLSessionDownloadTask *) downloadTaskWithRequest: (NSURLRequest *)request
|
||||
{
|
||||
NSURLSessionDownloadTask *task;
|
||||
|
||||
if (_invalidated)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
task = [[NSURLSessionDownloadTask alloc] initWithSession: self
|
||||
request: request
|
||||
taskIdentifier: _nextTaskIdentifier++];
|
||||
|
||||
[self addTask: task];
|
||||
|
||||
return AUTORELEASE(task);
|
||||
}
|
||||
|
||||
- (NSURLSessionDownloadTask *) downloadTaskWithURL: (NSURL *)url
|
||||
{
|
||||
NSMutableURLRequest *request;
|
||||
|
||||
request = [NSMutableURLRequest requestWithURL: url];
|
||||
[request setHTTPMethod: @"GET"];
|
||||
|
||||
return [self downloadTaskWithRequest: request];
|
||||
}
|
||||
|
||||
- (void) addTask: (NSURLSessionTask*)task
|
||||
{
|
||||
[_taskRegistry addTask: task];
|
||||
|
@ -584,6 +612,27 @@ static int nextSessionIdentifier()
|
|||
|
||||
if (nil != delegate)
|
||||
{
|
||||
// Send delegate with temporary fileURL
|
||||
if ([task isKindOfClass: [NSURLSessionDownloadTask class]]
|
||||
&& [delegate respondsToSelector: @selector(URLSession:downloadTask:didFinishDownloadingToURL:)])
|
||||
{
|
||||
id<NSURLSessionDownloadDelegate> downloadDelegate;
|
||||
NSURLSessionDownloadTask *downloadTask;
|
||||
NSURL *fileURL;
|
||||
|
||||
downloadDelegate = (id<NSURLSessionDownloadDelegate>)delegate;
|
||||
downloadTask = (NSURLSessionDownloadTask *)task;
|
||||
fileURL = [NSURLProtocol propertyForKey: @"tempFileURL"
|
||||
inRequest: [protocol request]];
|
||||
|
||||
[delegateQueue addOperationWithBlock:
|
||||
^{
|
||||
[downloadDelegate URLSession: session
|
||||
downloadTask: downloadTask
|
||||
didFinishDownloadingToURL: fileURL];
|
||||
}];
|
||||
}
|
||||
|
||||
[delegateQueue addOperationWithBlock:
|
||||
^{
|
||||
if (NSURLSessionTaskStateCompleted == [task state])
|
||||
|
@ -632,7 +681,7 @@ static int nextSessionIdentifier()
|
|||
|
||||
@implementation NSURLSessionTask
|
||||
{
|
||||
NSURLSession *_session; /* not retained */
|
||||
NSURLSession *_session;
|
||||
NSLock *_protocolLock;
|
||||
NSURLSessionTaskProtocolState _protocolState;
|
||||
NSURLProtocol *_protocol;
|
||||
|
@ -653,6 +702,12 @@ static int nextSessionIdentifier()
|
|||
NSData *data;
|
||||
NSInputStream *stream;
|
||||
|
||||
/*
|
||||
* Only retain the session once the -resume method is called
|
||||
* and release the session as the last thing done once the
|
||||
* task has completed. This avoids a retain loop causing
|
||||
* session and tasks to be leaked.
|
||||
*/
|
||||
_session = session;
|
||||
ASSIGN(_originalRequest, request);
|
||||
ASSIGN(_currentRequest, request);
|
||||
|
@ -857,6 +912,12 @@ static int nextSessionIdentifier()
|
|||
|
||||
- (void) resume
|
||||
{
|
||||
/*
|
||||
* Properly retain the session to keep a reference
|
||||
* to the task. This ensures correct API behaviour.
|
||||
*/
|
||||
RETAIN(_session);
|
||||
|
||||
dispatch_sync(_workQueue,
|
||||
^{
|
||||
if (NSURLSessionTaskStateCanceling == _state
|
||||
|
@ -1070,6 +1131,11 @@ static int nextSessionIdentifier()
|
|||
[_protocolLock lock];
|
||||
_protocolState = NSURLSessionTaskProtocolStateInvalidated;
|
||||
DESTROY(_protocol);
|
||||
/*
|
||||
* Release session at the end of the task
|
||||
* and not when -dealloc is called.
|
||||
*/
|
||||
DESTROY(_session);
|
||||
[_protocolLock unlock];
|
||||
}
|
||||
|
||||
|
@ -1083,6 +1149,10 @@ static int nextSessionIdentifier()
|
|||
|
||||
@end
|
||||
|
||||
@implementation NSURLSessionDownloadTask
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSURLSessionConfiguration
|
||||
|
||||
static NSURLSessionConfiguration *def = nil;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
NSData *_taskData;
|
||||
NSString *_taskText;
|
||||
NSError *_taskError;
|
||||
NSURL *_taskLocation;
|
||||
}
|
||||
- (BOOL) finished;
|
||||
- (void) reset;
|
||||
|
@ -48,6 +49,7 @@
|
|||
DESTROY(_taskData);
|
||||
DESTROY(_taskError);
|
||||
DESTROY(_taskText);
|
||||
DESTROY(_taskLocation);
|
||||
_finished = NO;
|
||||
}
|
||||
|
||||
|
@ -68,6 +70,11 @@
|
|||
return _taskText;
|
||||
}
|
||||
|
||||
- (NSURL*) taskLocation
|
||||
{
|
||||
return _taskLocation;
|
||||
}
|
||||
|
||||
- (void) URLSession: (NSURLSession*)session
|
||||
dataTask: (NSURLSessionDataTask*)dataTask
|
||||
didReceiveResponse: (NSURLResponse*)response
|
||||
|
@ -95,6 +102,14 @@
|
|||
RELEASE(text);
|
||||
}
|
||||
|
||||
/* NSURLSessionDownloadDelegate */
|
||||
- (void) URLSession: (NSURLSession *)session
|
||||
downloadTask: (NSURLSessionDownloadTask *)downloadTask
|
||||
didFinishDownloadingToURL: (NSURL *)location
|
||||
{
|
||||
ASSIGN(_taskLocation, location);
|
||||
}
|
||||
|
||||
- (void) URLSession: (NSURLSession*)session
|
||||
task: (NSURLSessionTask*)task
|
||||
didCompleteWithError: (NSError*)error
|
||||
|
@ -104,7 +119,7 @@ didCompleteWithError: (NSError*)error
|
|||
|
||||
if (error == nil)
|
||||
{
|
||||
NSLog(@"Download is Succesfull");
|
||||
NSLog(@"Download is Successful");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
94
Tests/base/NSURLSession/test03.m
Normal file
94
Tests/base/NSURLSession/test03.m
Normal file
|
@ -0,0 +1,94 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import "Testing.h"
|
||||
#import "ObjectTesting.h"
|
||||
#import "../NSURLConnection/Helpers/TestWebServer.h"
|
||||
|
||||
#if GS_HAVE_NSURLSESSION
|
||||
#import "delegate.g"
|
||||
#endif
|
||||
|
||||
int main()
|
||||
{
|
||||
START_SET("NSURLSession test03")
|
||||
|
||||
#if !GS_HAVE_NSURLSESSION
|
||||
SKIP("library built without NSURLSession support")
|
||||
#else
|
||||
NSFileManager *fm;
|
||||
NSBundle *bundle;
|
||||
NSString *helperPath;
|
||||
|
||||
// load the test suite's classes
|
||||
fm = [NSFileManager defaultManager];
|
||||
helperPath = [[fm currentDirectoryPath] stringByAppendingPathComponent:
|
||||
@"../NSURLConnection/Helpers/TestConnection.bundle"];
|
||||
bundle = [NSBundle bundleWithPath: helperPath];
|
||||
NSCAssert([bundle load], NSInternalInconsistencyException);
|
||||
|
||||
TestWebServer *server;
|
||||
Class c;
|
||||
BOOL debug = YES;
|
||||
|
||||
// create a shared TestWebServer instance for performance
|
||||
c = [bundle principalClass];
|
||||
server = [[c testWebServerClass] new];
|
||||
NSCAssert(server != nil, NSInternalInconsistencyException);
|
||||
[server setDebug: debug];
|
||||
[server start: nil]; // localhost:1234 HTTP
|
||||
|
||||
|
||||
NSURLSessionConfiguration *configuration;
|
||||
NSURLSession *defaultSession;
|
||||
NSURLSessionDownloadTask *downloadTask;
|
||||
NSMutableURLRequest *urlRequest;
|
||||
NSURL *url;
|
||||
NSOperationQueue *mainQueue;
|
||||
MyDelegate *object;
|
||||
NSString *content;
|
||||
|
||||
configuration = [[NSURLSessionConfiguration alloc] init];
|
||||
|
||||
object = AUTORELEASE([MyDelegate new]);
|
||||
mainQueue = [NSOperationQueue mainQueue];
|
||||
defaultSession = [NSURLSession sessionWithConfiguration: configuration
|
||||
delegate: object
|
||||
delegateQueue: mainQueue];
|
||||
RELEASE(configuration);
|
||||
url = [NSURL URLWithString: @"http://localhost:1234/index"];
|
||||
urlRequest = [NSMutableURLRequest requestWithURL: url];
|
||||
if ([urlRequest respondsToSelector: @selector(setDebug:)])
|
||||
{
|
||||
[urlRequest setDebug: YES];
|
||||
}
|
||||
|
||||
downloadTask = [defaultSession downloadTaskWithRequest: urlRequest];
|
||||
[downloadTask resume];
|
||||
|
||||
NSDate *limit = [NSDate dateWithTimeIntervalSinceNow: 60.0];
|
||||
while ([object finished] == NO
|
||||
&& [limit timeIntervalSinceNow] > 0.0)
|
||||
{
|
||||
ENTER_POOL
|
||||
NSDate *when = [NSDate dateWithTimeIntervalSinceNow: 0.1];
|
||||
|
||||
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
|
||||
beforeDate: when];
|
||||
LEAVE_POOL
|
||||
}
|
||||
|
||||
PASS(YES == [object finished], "request completed")
|
||||
PASS_EQUAL([object taskError], nil, "request did not error")
|
||||
|
||||
/* Get content from file */
|
||||
content = [NSString stringWithContentsOfFile: [[object taskLocation] path]
|
||||
encoding: NSUTF8StringEncoding
|
||||
error:nil];
|
||||
|
||||
|
||||
NSString *expect = @"Please give login and password";
|
||||
PASS_EQUAL(content, expect, "request returned text")
|
||||
|
||||
#endif
|
||||
END_SET("NSURLSession test03")
|
||||
return 0;
|
||||
}
|
Loading…
Reference in a new issue