// // P4NetworkOperation.m // Perforce // // Created by Adam Czubernat on 08/01/2014. // Copyright (c) 2014 Perforce Software, Inc. All rights reserved. // #import "P4NetworkOperation.h" @interface P4NetworkOperation () { // Internal state BOOL started, executing, finished, cancelled; void (^receive)(P4NetworkOperation *operation); void (^completion)(P4NetworkOperation *operation); NSString *MIMEType; NSDictionary *HTTPHeaders; NSInteger HTTPStatus; } - (void)cancelOperation; - (void)completeOperation; @end @implementation P4NetworkOperation @synthesize success, error; #pragma mark - Overrides - (void)start { started = YES; if ([self isCancelled]) { [self cancelOperation]; return; } [self willChangeValueForKey:@"isExecuting"]; executing = YES; [self didChangeValueForKey:@"isExecuting"]; // Create connection connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; data = [[NSMutableData alloc] init]; // Force runloop for events passing [connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; [connection start]; } - (void)cancel { [super cancel]; [self cancelOperation]; } - (BOOL)isConcurrent { return YES; } - (BOOL)isExecuting { return executing; } - (BOOL)isFinished { return finished; } - (void)dealloc { PSLogm(@""); } #pragma mark - Public + (P4NetworkOperation *)operation { return [[P4NetworkOperation alloc] init]; } + (P4NetworkOperation *)operationWithRequest:(NSURLRequest *)urlRequest receive:(P4NetworkBlock_t)receive completion:(P4NetworkBlock_t)completion { P4NetworkOperation *operation = [[P4NetworkOperation alloc] init]; [operation setRequest:urlRequest receive:receive completion:completion]; return operation; } + (P4NetworkOperation *)operationWithUrl:(NSString *)url HTTPMethod:(NSString *)method HTTPHeaders:(NSDictionary *)headers HTTPBody:(NSData *)body receive:(P4NetworkBlock_t)receive completion:(P4NetworkBlock_t)completion { NSMutableURLRequest *request; request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60.0f]; [request setHTTPShouldHandleCookies:YES]; [request setHTTPMethod:method]; [request setHTTPBody:body]; // Add headers to request for (NSString *field in headers) [request setValue:[headers objectForKey:field] forHTTPHeaderField:field]; // Add length header [request setValue:[NSString stringWithFormat:@"%ld", [body length]] forHTTPHeaderField:@"Content-Length"]; P4NetworkOperation *operation = [[P4NetworkOperation alloc] init]; [operation setRequest:request receive:receive completion:completion]; return operation; } - (NSData *)data { return data; } - (NSString *)MIMEType { return MIMEType; } - (NSDictionary *)HTTPHeaders { return HTTPHeaders; } - (NSInteger)HTTPStatus { return HTTPStatus; } - (NSDate *)HTTPDate { static NSDateFormatter *formatter; if (!formatter) { formatter = [[NSDateFormatter alloc] init]; NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; [formatter setLocale:locale]; [formatter setDateFormat:@"EEE, d MMM y HH:mm:ss ZZZ"]; } NSString *stringDate = [HTTPHeaders objectForKey:@"Date"]; if (!stringDate) return nil; return [formatter dateFromString:stringDate]; } - (NSURLResponse *)HTTPResponse { return HTTPResponse; } - (CGFloat)progress { return progress; } - (long)completed { return completed; } - (long)length { return length; } - (NSURLRequest *)request { return request; } - (void)setRequest:(NSURLRequest *)urlRequest receive:(P4NetworkBlock_t)onReceive completion:(P4NetworkBlock_t)onCompletion { // Don't create new request if connection is already running if (executing) return; // Copy passed request request = [urlRequest copy]; // Copy passed blocks receive = [onReceive copy]; // Copy blocks from stack to heap, stack completion = [onCompletion copy]; // otherwise would be drained when calling // function returns. // Clear variables connection = nil; success = NO; error = nil; data = nil; MIMEType = nil; HTTPHeaders = nil; HTTPResponse = nil; HTTPStatus = 0; progress = 0.0f; completed = 0; length = 0; } #pragma mark - Private - (void)cancelOperation { @synchronized(self) { if (cancelled) return; cancelled = YES; } [connection cancel]; connection = nil; // Imitate error situation [self connection:nil didFailWithError: [NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]]; } - (void)completeOperation { if (started) [self willChangeValueForKey:@"isFinished"]; [self willChangeValueForKey:@"isExecuting"]; executing = NO; finished = YES; [self didChangeValueForKey:@"isExecuting"]; if (started) [self didChangeValueForKey:@"isFinished"]; if (completion) completion(self); } #pragma mark - NSURLConnection delegate - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { length = [response expectedContentLength]; completed = 0; progress = 0.0f; HTTPResponse = (NSHTTPURLResponse *)response; MIMEType = [HTTPResponse MIMEType]; HTTPHeaders = [HTTPResponse allHeaderFields]; HTTPStatus = [HTTPResponse statusCode]; if (receive) receive(self); } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)receivedData { // if (outputFile) // data = receivedData; // Store new chunk of data // else [(NSMutableData *)data appendData:receivedData]; // Append new data // Add received bytecount into completedLength completed += receivedData.length; // Compute progress if (length == NSURLResponseUnknownLength) progress = 0.0; else progress = completed / (CGFloat) length; if (receive) receive(self); } - (void)connection:(NSURLConnection *)failedConnection didFailWithError:(NSError *)failError { connection = nil; success = NO; if (failError) error = failError; [self completeOperation]; } - (void)connectionDidFinishLoading:(NSURLConnection *)finishedConnection { connection = nil; success = YES; if ((HTTPStatus >= 400 && HTTPStatus < 500) || // Client error (HTTPStatus >= 500 && HTTPStatus < 600)) { // Server error if (!error) error = [NSError errorWithDomain:NSCocoaErrorDomain code:HTTPStatus userInfo:@{ NSLocalizedDescriptionKey : [NSHTTPURLResponse localizedStringForStatusCode:HTTPStatus] }]; success = NO; } [self completeOperation]; } @end