diff --git a/Foundation/CPPredicate/_CPPredicate.j b/Foundation/CPPredicate/_CPPredicate.j index 201bcccd0..56bfcd643 100644 --- a/Foundation/CPPredicate/_CPPredicate.j +++ b/Foundation/CPPredicate/_CPPredicate.j @@ -605,11 +605,11 @@ } if ([self scanPredicateKeyword:@"TRUE"] || [self scanPredicateKeyword:@"YES"]) { - return [CPExpression expressionForConstantValue:[CPNumber numberWithBool:YES]]; + return [CPExpression expressionForConstantValue:YES]; } if ([self scanPredicateKeyword:@"FALSE"] || [self scanPredicateKeyword:@"NO"]) { - return [CPExpression expressionForConstantValue:[CPNumber numberWithBool:NO]]; + return [CPExpression expressionForConstantValue:NO]; } if ([self scanPredicateKeyword:@"SELF"]) { diff --git a/Foundation/CPURLConnection.j b/Foundation/CPURLConnection.j index b962d107f..cd195b04a 100644 --- a/Foundation/CPURLConnection.j +++ b/Foundation/CPURLConnection.j @@ -176,6 +176,7 @@ var CPURLConnectionDelegate = nil; (window.location.protocol === "file:" || window.location.protocol === "app:")); _HTTPRequest = new CFHTTPRequest(); + _HTTPRequest.setTimeout([aRequest timeoutInterval] * 1000); _HTTPRequest.setWithCredentials([aRequest withCredentials]); if (shouldStartImmediately) @@ -210,6 +211,7 @@ var CPURLConnectionDelegate = nil; _HTTPRequest.open([_request HTTPMethod], [[_request URL] absoluteString], YES); _HTTPRequest.onreadystatechange = function() { [self _readyStateDidChange]; }; + _HTTPRequest.ontimeout = function() { [self _didTimeout]; }; var fields = [_request allHTTPHeaderFields], key = nil, @@ -222,11 +224,16 @@ var CPURLConnectionDelegate = nil; } catch (anException) { - if ([_delegate respondsToSelector:@selector(connection:didFailWithError:)]) - [_delegate connection:self didFailWithError:anException]; + [self _sendDelegateDidFailWithError:anException]; } } +- (void)_sendDelegateDidFailWithError:(CPException)anException +{ + if ([_delegate respondsToSelector:@selector(connection:didFailWithError:)]) + [_delegate connection:self didFailWithError:anException]; +} + /* Cancels the current request. */ @@ -249,10 +256,22 @@ var CPURLConnectionDelegate = nil; return _isLocalFileConnection; } +/*! + @ignore +*/ +- (void)_didTimeout +{ + var exception = [CPException exceptionWithName:@"Timeout exception" + reason:"The request timed out." + userInfo:@{}]; + + [self _sendDelegateDidFailWithError:exception]; +} + /* @ignore */ - (void)_readyStateDidChange { - if (_HTTPRequest.readyState() === CFHTTPRequest.CompleteState) + if (_HTTPRequest.readyState() === CFHTTPRequest.CompleteState && !_HTTPRequest.isTimeoutRequest()) { var statusCode = _HTTPRequest.status(), URL = [_request URL]; diff --git a/Foundation/CPURLRequest.j b/Foundation/CPURLRequest.j index 8d6a39e3b..9c0ad1b46 100644 --- a/Foundation/CPURLRequest.j +++ b/Foundation/CPURLRequest.j @@ -25,6 +25,12 @@ @import "CPString.j" @import "CPURL.j" +@typedef CPURLRequestCachePolicy +CPURLRequestUseProtocolCachePolicy = 0; +CPURLRequestReloadIgnoringLocalCacheData = 1; +CPURLRequestReturnCacheDataElseLoad = 2; +CPURLRequestReturnCacheDataDontLoad = 3; + /*! @class CPURLRequest @ingroup foundation @@ -35,14 +41,16 @@ */ @implementation CPURLRequest : CPObject { - CPURL _URL @accessors(property=URL); + CPURL _URL @accessors(property=URL); // FIXME: this should be CPData - CPString _HTTPBody @accessors(property=HTTPBody); - CPString _HTTPMethod @accessors(property=HTTPMethod); - BOOL _withCredentials @accessors(property=withCredentials); + CPString _HTTPBody @accessors(property=HTTPBody); + CPString _HTTPMethod @accessors(property=HTTPMethod); + BOOL _withCredentials @accessors(property=withCredentials); - CPDictionary _HTTPHeaderFields @accessors(readonly, getter=allHTTPHeaderFields); + CPDictionary _HTTPHeaderFields @accessors(readonly, getter=allHTTPHeaderFields); + CPTimeInterval _timeoutInterval @accessors(readonly, getter=timeoutInterval); + CPURLRequestCachePolicy _cachePolicy @accessors(readonly, getter=cachePolicy); } /*! @@ -55,6 +63,18 @@ return [[CPURLRequest alloc] initWithURL:aURL]; } +/*! + Creates a request with a specified URL, cachePolicy and timeoutInterval + @param aURL the URL of the request + @param aCachePolicy the cache policy of the request + @param aTimeoutInterval the timeoutInterval of the request + @return a CPURLRequest +*/ ++ (id)requestWithURL:(CPURL)anURL cachePolicy:(CPURLRequestCachePolicy)aCachePolicy timeoutInterval:(CPTimeInterval)aTimeoutInterval +{ + return [[CPURLRequest alloc] initWithURL:anURL cachePolicy:aCachePolicy timeoutInterval:aTimeoutInterval]; +} + /*! Equal to `[receiver initWithURL:nil]`. */ @@ -63,6 +83,25 @@ return [self initWithURL:nil]; } +/*! + Initializes the request with a URL. This is the designated initializer. + + @param aURL the url to set + @param aCachePolicy the cache policy of the request + @param aTimeoutInterval the timeoutInterval of the request + @return the initialized CPURLRequest +*/ +- (id)initWithURL:(CPURL)anURL cachePolicy:(CPURLRequestCachePolicy)aCachePolicy timeoutInterval:(CPTimeInterval)aTimeoutInterval +{ + if (self = [self initWithURL:anURL]) + { + _cachePolicy = aCachePolicy; + _timeoutInterval = aTimeoutInterval; + } + + return self; +} + /*! Initializes the request with a URL. This is the designated initializer. @@ -71,9 +110,7 @@ */ - (id)initWithURL:(CPURL)aURL { - self = [super init]; - - if (self) + if (self = [super init]) { [self setURL:aURL]; @@ -81,9 +118,34 @@ _HTTPMethod = @"GET"; _HTTPHeaderFields = @{}; _withCredentials = NO; + _timeoutInterval = 60.0; + _cachePolicy = CPURLRequestUseProtocolCachePolicy; [self setValue:"Thu, 01 Jan 1970 00:00:00 GMT" forHTTPHeaderField:"If-Modified-Since"]; - [self setValue:"no-cache" forHTTPHeaderField:"Cache-Control"]; + + switch (_cachePolicy) + { + case CPURLRequestUseProtocolCachePolicy: + // TODO: implement everything about cache... + [self setValue:"no-cache" forHTTPHeaderField:"Cache-Control"]; + break; + + case CPURLRequestReturnCacheDataElseLoad: + [self setValue:"max-stale=31536000" forHTTPHeaderField:"Cache-Control"]; + break; + + case CPURLRequestReturnCacheDataDontLoad: + [self setValue:"only-if-cached" forHTTPHeaderField:"Cache-Control"]; + break; + + case CPURLRequestReloadIgnoringLocalCacheData: + [self setValue:"no-cache" forHTTPHeaderField:"Cache-Control"]; + break; + + default: + [self setValue:"no-cache" forHTTPHeaderField:"Cache-Control"]; + } + [self setValue:"XMLHttpRequest" forHTTPHeaderField:"X-Requested-With"]; } diff --git a/Objective-J/CFHTTPRequest.js b/Objective-J/CFHTTPRequest.js index 0d34667f2..eff9a49df 100644 --- a/Objective-J/CFHTTPRequest.js +++ b/Objective-J/CFHTTPRequest.js @@ -112,7 +112,13 @@ GLOBAL(CFHTTPRequest) = function() determineAndDispatchHTTPRequestEvents(self); }; + this._timeoutHandler = function() + { + dispatchTimeoutHTTPRequestEvents(self); + }; + this._nativeRequest.onreadystatechange = this._stateChangeHandler; + this._nativeRequest.ontimeout = this._timeoutHandler; if (CFHTTPRequest.AuthenticationDelegate !== nil) this._eventDispatcher.addEventListener("HTTP403", function() @@ -209,6 +215,16 @@ CFHTTPRequest.prototype.getResponseHeader = function(/*String*/ aHeader) return this._nativeRequest.getResponseHeader(aHeader); }; +CFHTTPRequest.prototype.setTimeout = function(/*int*/ aTimeout) +{ + this._nativeRequest.timeout = aTimeout; +}; + +CFHTTPRequest.prototype.getTimeout = function(/*int*/ aTimeout) +{ + return this._nativeRequest.timeout; +}; + CFHTTPRequest.prototype.getAllResponseHeaders = function() { return this._nativeRequest.getAllResponseHeaders(); @@ -235,7 +251,10 @@ CFHTTPRequest.prototype.send = function(/*Object*/ aBody) if (!this._isOpen) { delete this._nativeRequest.onreadystatechange; + delete this._nativeRequest.ontimeout; + this._nativeRequest.open(this._method, this._URL, this._async, this._user, this._password); + this._nativeRequest.ontimeout = this._timeoutHandler; this._nativeRequest.onreadystatechange = this._stateChangeHandler; } @@ -287,15 +306,25 @@ CFHTTPRequest.prototype.withCredentials = function() return this._nativeRequest.withCredentials; }; +CFHTTPRequest.prototype.isTimeoutRequest = function() +{ + // Can we consider that as a timeout ? + return !this.success() && !this._nativeRequest.response && !this._nativeRequest.responseText && !this._nativeRequest.responseType && !this._nativeRequest.responseURL && !this._nativeRequest.responseXML; +}; + +function dispatchTimeoutHTTPRequestEvents(/*CFHTTPRequest*/ aRequest) +{ + aRequest._eventDispatcher.dispatchEvent({ type:"timeout", request:aRequest}); +} + function determineAndDispatchHTTPRequestEvents(/*CFHTTPRequest*/ aRequest) { - var eventDispatcher = aRequest._eventDispatcher; + var eventDispatcher = aRequest._eventDispatcher, + nativeRequest = aRequest._nativeRequest, + readyStates = ["uninitialized", "loading", "loaded", "interactive", "complete"]; eventDispatcher.dispatchEvent({ type:"readystatechange", request:aRequest}); - var nativeRequest = aRequest._nativeRequest, - readyStates = ["uninitialized", "loading", "loaded", "interactive", "complete"]; - if (readyStates[aRequest.readyState()] === "complete") { var status = "HTTP" + aRequest.status(); diff --git a/Tests/Foundation/CPPredicateTest.j b/Tests/Foundation/CPPredicateTest.j index 114f440e9..670a13b5b 100644 --- a/Tests/Foundation/CPPredicateTest.j +++ b/Tests/Foundation/CPPredicateTest.j @@ -526,6 +526,57 @@ [self assertTrue:[result isEqual:data] message:"[1, 2, 3] filtered with '" + [ctpred description] + "' should return [1, 2, 3]"]; } +- (void)testConstantValueExpressionWithBooleanValueIsBoolean +{ + var right = [[CPPredicate predicateWithFormat:@"k = YES"] rightExpression]; + + [self assert:CPConstantValueExpressionType equals:[right expressionType]]; + [self assert:YES equals:[right constantValue]]; + [self assert:'boolean' equals:typeof [right constantValue]]; + + right = [[CPPredicate predicateWithFormat:@"k = NO"] rightExpression]; + + [self assert:CPConstantValueExpressionType equals:[right expressionType]]; + [self assert:NO equals:[right constantValue]]; + [self assert:'boolean' equals:typeof [right constantValue]]; +} + +- (void)testEvaluateBooleanConstantValueExpressionWithDifferentObjects +{ + var yesPredicate = [CPPredicate predicateWithFormat:@"self = YES"], + noPredicate = [CPPredicate predicateWithFormat:@"self = NO"]; + + var evalYesPredicate = function(object) { return [yesPredicate evaluateWithObject:object]; }, + evalNoPredicate = function(object) { return [noPredicate evaluateWithObject:object]; }; + + var fixtures = [ + { + message:@"booleans", + values: [YES, NO], + expected_yes: [YES, NO], + expected_no: [ NO, YES] + }, + { + message:@"numbers", + values: [ 0, 1, 2], + expected_yes: [ NO, YES, NO], + expected_no: [YES, NO, NO] + }, + { + message:@"strings", + values: [@"1", @"2", @"true", @"false", @"", @"YES", @"NO"], + expected_yes: [ NO , NO , NO , NO , NO, NO , NO ], + expected_no: [ NO , NO , NO , NO , NO, NO , NO ] + }]; + + for (var i = 0; i < [fixtures count]; i++) + { + var fixture = fixtures[i]; + [self assert:fixture.expected_yes equals:fixture.values.map(evalYesPredicate) message:fixture.message + " with YES-predicate"]; + [self assert:fixture.expected_no equals:fixture.values.map(evalNoPredicate) message:fixture.message + " with NO-predicate"]; + } +} + @end @implementation CPObject (PredicateTesting)