| @@ -101,10 +101,10 @@ bool Process::openEmailWithAttachments (const String& targetEmailAddress, | |||||
| class URLConnectionState : public Thread | class URLConnectionState : public Thread | ||||
| { | { | ||||
| public: | public: | ||||
| URLConnectionState (NSObject* owner_, NSURLRequest* req) | |||||
| URLConnectionState (NSURLRequest* req) | |||||
| : Thread ("http connection"), | : Thread ("http connection"), | ||||
| contentLength (-1), | contentLength (-1), | ||||
| owner (owner_), | |||||
| delegate (nil), | |||||
| request ([req retain]), | request ([req retain]), | ||||
| connection (nil), | connection (nil), | ||||
| data ([[NSMutableData data] retain]), | data ([[NSMutableData data] retain]), | ||||
| @@ -113,15 +113,19 @@ public: | |||||
| hasFailed (false), | hasFailed (false), | ||||
| hasFinished (false) | hasFinished (false) | ||||
| { | { | ||||
| static DelegateClass cls; | |||||
| delegate = [cls.createInstance() init]; | |||||
| DelegateClass::setState (delegate, this); | |||||
| } | } | ||||
| ~URLConnectionState() | ~URLConnectionState() | ||||
| { | { | ||||
| stopThread (10000); | |||||
| stop(); | |||||
| [connection release]; | [connection release]; | ||||
| [data release]; | [data release]; | ||||
| [request release]; | [request release]; | ||||
| [headers release]; | [headers release]; | ||||
| [delegate release]; | |||||
| } | } | ||||
| bool start (URL::OpenStreamProgressCallback* callback, void* context) | bool start (URL::OpenStreamProgressCallback* callback, void* context) | ||||
| @@ -216,12 +220,12 @@ public: | |||||
| void run() | void run() | ||||
| { | { | ||||
| NSUInteger oldRetainCount = [owner retainCount]; | |||||
| NSUInteger oldRetainCount = [delegate retainCount]; | |||||
| connection = [[NSURLConnection alloc] initWithRequest: request | connection = [[NSURLConnection alloc] initWithRequest: request | ||||
| delegate: owner]; | |||||
| delegate: delegate]; | |||||
| if (oldRetainCount == [owner retainCount]) | |||||
| [owner retain]; // newer SDK should already retain this, but there were problems in older versions.. | |||||
| if (oldRetainCount == [delegate retainCount]) | |||||
| [delegate retain]; // newer SDK should already retain this, but there were problems in older versions.. | |||||
| if (connection != nil) | if (connection != nil) | ||||
| { | { | ||||
| @@ -235,7 +239,7 @@ public: | |||||
| int64 contentLength; | int64 contentLength; | ||||
| CriticalSection dataLock; | CriticalSection dataLock; | ||||
| NSObject* owner; | |||||
| NSObject* delegate; | |||||
| NSURLRequest* request; | NSURLRequest* request; | ||||
| NSURLConnection* connection; | NSURLConnection* connection; | ||||
| NSMutableData* data; | NSMutableData* data; | ||||
| @@ -243,64 +247,50 @@ public: | |||||
| bool initialised, hasFailed, hasFinished; | bool initialised, hasFailed, hasFinished; | ||||
| private: | private: | ||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (URLConnectionState); | |||||
| }; | |||||
| //============================================================================== | |||||
| struct URLConnectionDelegateClass : public ObjCClass<NSObject> | |||||
| { | |||||
| URLConnectionDelegateClass() : ObjCClass ("JUCEAppDelegate_") | |||||
| //============================================================================== | |||||
| struct DelegateClass : public ObjCClass<NSObject> | |||||
| { | { | ||||
| addIvar <URLConnectionState*> ("state"); | |||||
| addMethod (@selector (dealloc), dealloc, "v@:"); | |||||
| addMethod (@selector (connection:didReceiveResponse:), didReceiveResponse, "v@:@@"); | |||||
| addMethod (@selector (connection:didFailWithError:), didFailWithError, "v@:@@"); | |||||
| addMethod (@selector (connection:didReceiveData:), didReceiveData, "v@:@@"); | |||||
| addMethod (@selector (connectionDidFinishLoading:), connectionDidFinishLoading, "v@:@"); | |||||
| DelegateClass() : ObjCClass ("JUCEAppDelegate_") | |||||
| { | |||||
| addIvar <URLConnectionState*> ("state"); | |||||
| registerClass(); | |||||
| } | |||||
| addMethod (@selector (connection:didReceiveResponse:), didReceiveResponse, "v@:@@"); | |||||
| addMethod (@selector (connection:didFailWithError:), didFailWithError, "v@:@@"); | |||||
| addMethod (@selector (connection:didReceiveData:), didReceiveData, "v@:@@"); | |||||
| addMethod (@selector (connectionDidFinishLoading:), connectionDidFinishLoading, "v@:@"); | |||||
| static void setState (id self, URLConnectionState* state) | |||||
| { | |||||
| object_setInstanceVariable (self, "state", state); | |||||
| } | |||||
| registerClass(); | |||||
| } | |||||
| static URLConnectionState* getState (id self) | |||||
| { | |||||
| return getIvar<URLConnectionState*> (self, "state"); | |||||
| } | |||||
| static void setState (id self, URLConnectionState* state) { object_setInstanceVariable (self, "state", state); } | |||||
| static URLConnectionState* getState (id self) { return getIvar<URLConnectionState*> (self, "state"); } | |||||
| private: | |||||
| static void dealloc (id self, SEL sel) | |||||
| { | |||||
| getState (self)->stop(); | |||||
| delete getState (self); | |||||
| sendSuperclassMessage (self, @selector (dealloc)); | |||||
| } | |||||
| private: | |||||
| static void didReceiveResponse (id self, SEL, NSURLConnection*, NSURLResponse* response) | |||||
| { | |||||
| getState (self)->didReceiveResponse (response); | |||||
| } | |||||
| static void didReceiveResponse (id self, SEL, NSURLConnection*, NSURLResponse* response) | |||||
| { | |||||
| getState (self)->didReceiveResponse (response); | |||||
| } | |||||
| static void didFailWithError (id self, SEL, NSURLConnection*, NSError* error) | |||||
| { | |||||
| getState (self)->didFailWithError (error); | |||||
| } | |||||
| static void didFailWithError (id self, SEL, NSURLConnection*, NSError* error) | |||||
| { | |||||
| getState (self)->didFailWithError (error); | |||||
| } | |||||
| static void didReceiveData (id self, SEL, NSURLConnection*, NSData* newData) | |||||
| { | |||||
| getState (self)->didReceiveData (newData); | |||||
| } | |||||
| static void didReceiveData (id self, SEL, NSURLConnection*, NSData* newData) | |||||
| { | |||||
| getState (self)->didReceiveData (newData); | |||||
| } | |||||
| static void connectionDidFinishLoading (id self, SEL, NSURLConnection*) | |||||
| { | |||||
| getState (self)->finishedLoading(); | |||||
| } | |||||
| }; | |||||
| static void connectionDidFinishLoading (id self, SEL, NSURLConnection*) | |||||
| { | |||||
| getState (self)->finishedLoading(); | |||||
| } | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (URLConnectionState); | |||||
| }; | }; | ||||
| //============================================================================== | //============================================================================== | ||||
| class WebInputStream : public InputStream | class WebInputStream : public InputStream | ||||
| { | { | ||||
| @@ -308,37 +298,29 @@ public: | |||||
| WebInputStream (const String& address_, bool isPost_, const MemoryBlock& postData_, | WebInputStream (const String& address_, bool isPost_, const MemoryBlock& postData_, | ||||
| URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, | URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, | ||||
| const String& headers_, int timeOutMs_, StringPairArray* responseHeaders) | const String& headers_, int timeOutMs_, StringPairArray* responseHeaders) | ||||
| : connection (nil), | |||||
| address (address_), headers (headers_), postData (postData_), position (0), | |||||
| : address (address_), headers (headers_), postData (postData_), position (0), | |||||
| finished (false), isPost (isPost_), timeOutMs (timeOutMs_) | finished (false), isPost (isPost_), timeOutMs (timeOutMs_) | ||||
| { | { | ||||
| JUCE_AUTORELEASEPOOL | JUCE_AUTORELEASEPOOL | ||||
| connection = createConnection (progressCallback, progressCallbackContext); | |||||
| createConnection (progressCallback, progressCallbackContext); | |||||
| if (responseHeaders != nullptr && connection != nil) | |||||
| if (responseHeaders != nullptr && connection != nullptr) | |||||
| { | { | ||||
| URLConnectionState* const state = URLConnectionDelegateClass::getState (connection); | |||||
| if (state->headers != nil) | |||||
| if (connection->headers != nil) | |||||
| { | { | ||||
| NSEnumerator* enumerator = [state->headers keyEnumerator]; | |||||
| NSEnumerator* enumerator = [connection->headers keyEnumerator]; | |||||
| NSString* key; | NSString* key; | ||||
| while ((key = [enumerator nextObject]) != nil) | while ((key = [enumerator nextObject]) != nil) | ||||
| responseHeaders->set (nsStringToJuce (key), | responseHeaders->set (nsStringToJuce (key), | ||||
| nsStringToJuce ((NSString*) [state->headers objectForKey: key])); | |||||
| nsStringToJuce ((NSString*) [connection->headers objectForKey: key])); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| ~WebInputStream() | |||||
| { | |||||
| close(); | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| bool isError() const { return connection == nil; } | |||||
| int64 getTotalLength() { return connection == nil ? -1 : URLConnectionDelegateClass::getState (connection)->contentLength; } | |||||
| bool isError() const { return connection == nullptr; } | |||||
| int64 getTotalLength() { return connection == nullptr ? -1 : connection->contentLength; } | |||||
| bool isExhausted() { return finished; } | bool isExhausted() { return finished; } | ||||
| int64 getPosition() { return position; } | int64 getPosition() { return position; } | ||||
| @@ -347,23 +329,17 @@ public: | |||||
| jassert (buffer != nullptr && bytesToRead >= 0); | jassert (buffer != nullptr && bytesToRead >= 0); | ||||
| if (finished || isError()) | if (finished || isError()) | ||||
| { | |||||
| return 0; | return 0; | ||||
| } | |||||
| else | |||||
| { | |||||
| JUCE_AUTORELEASEPOOL | |||||
| URLConnectionState* const state = URLConnectionDelegateClass::getState (connection); | |||||
| JUCE_AUTORELEASEPOOL | |||||
| const int bytesRead = state->read (static_cast <char*> (buffer), bytesToRead); | |||||
| position += bytesRead; | |||||
| const int bytesRead = connection->read (static_cast <char*> (buffer), bytesToRead); | |||||
| position += bytesRead; | |||||
| if (bytesRead == 0) | |||||
| finished = true; | |||||
| if (bytesRead == 0) | |||||
| finished = true; | |||||
| return bytesRead; | |||||
| } | |||||
| return bytesRead; | |||||
| } | } | ||||
| bool setPosition (int64 wantedPos) | bool setPosition (int64 wantedPos) | ||||
| @@ -374,9 +350,9 @@ public: | |||||
| if (wantedPos < position) | if (wantedPos < position) | ||||
| { | { | ||||
| close(); | |||||
| connection = nullptr; | |||||
| position = 0; | position = 0; | ||||
| connection = createConnection (0, 0); | |||||
| createConnection (0, 0); | |||||
| } | } | ||||
| skipNextBytes (wantedPos - position); | skipNextBytes (wantedPos - position); | ||||
| @@ -385,9 +361,8 @@ public: | |||||
| return true; | return true; | ||||
| } | } | ||||
| //============================================================================== | |||||
| private: | private: | ||||
| NSObject* connection; | |||||
| ScopedPointer<URLConnectionState> connection; | |||||
| String address, headers; | String address, headers; | ||||
| MemoryBlock postData; | MemoryBlock postData; | ||||
| int64 position; | int64 position; | ||||
| @@ -395,57 +370,42 @@ private: | |||||
| const bool isPost; | const bool isPost; | ||||
| const int timeOutMs; | const int timeOutMs; | ||||
| void close() | |||||
| void createConnection (URL::OpenStreamProgressCallback* progressCallback, | |||||
| void* progressCallbackContext) | |||||
| { | { | ||||
| if (connection != nil) | |||||
| { | |||||
| URLConnectionDelegateClass::getState (connection)->stop(); | |||||
| [connection release]; | |||||
| connection = nil; | |||||
| } | |||||
| } | |||||
| jassert (connection == nullptr); | |||||
| NSObject* createConnection (URL::OpenStreamProgressCallback* progressCallback, | |||||
| void* progressCallbackContext) | |||||
| { | |||||
| NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: juceStringToNS (address)] | NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: juceStringToNS (address)] | ||||
| cachePolicy: NSURLRequestReloadIgnoringLocalCacheData | cachePolicy: NSURLRequestReloadIgnoringLocalCacheData | ||||
| timeoutInterval: timeOutMs <= 0 ? 60.0 : (timeOutMs / 1000.0)]; | timeoutInterval: timeOutMs <= 0 ? 60.0 : (timeOutMs / 1000.0)]; | ||||
| if (req == nil) | |||||
| return nil; | |||||
| [req setHTTPMethod: nsStringLiteral (isPost ? "POST" : "GET")]; | |||||
| //[req setCachePolicy: NSURLRequestReloadIgnoringLocalAndRemoteCacheData]; | |||||
| StringArray headerLines; | |||||
| headerLines.addLines (headers); | |||||
| headerLines.removeEmptyStrings (true); | |||||
| for (int i = 0; i < headerLines.size(); ++i) | |||||
| if (req != nil) | |||||
| { | { | ||||
| const String key (headerLines[i].upToFirstOccurrenceOf (":", false, false).trim()); | |||||
| const String value (headerLines[i].fromFirstOccurrenceOf (":", false, false).trim()); | |||||
| [req setHTTPMethod: nsStringLiteral (isPost ? "POST" : "GET")]; | |||||
| //[req setCachePolicy: NSURLRequestReloadIgnoringLocalAndRemoteCacheData]; | |||||
| if (key.isNotEmpty() && value.isNotEmpty()) | |||||
| [req addValue: juceStringToNS (value) forHTTPHeaderField: juceStringToNS (key)]; | |||||
| } | |||||
| StringArray headerLines; | |||||
| headerLines.addLines (headers); | |||||
| headerLines.removeEmptyStrings (true); | |||||
| if (isPost && postData.getSize() > 0) | |||||
| [req setHTTPBody: [NSData dataWithBytes: postData.getData() | |||||
| length: postData.getSize()]]; | |||||
| for (int i = 0; i < headerLines.size(); ++i) | |||||
| { | |||||
| const String key (headerLines[i].upToFirstOccurrenceOf (":", false, false).trim()); | |||||
| const String value (headerLines[i].fromFirstOccurrenceOf (":", false, false).trim()); | |||||
| static URLConnectionDelegateClass cls; | |||||
| NSObject* const s = [cls.createInstance() init]; | |||||
| if (key.isNotEmpty() && value.isNotEmpty()) | |||||
| [req addValue: juceStringToNS (value) forHTTPHeaderField: juceStringToNS (key)]; | |||||
| } | |||||
| URLConnectionState* state = new URLConnectionState (s, req); | |||||
| URLConnectionDelegateClass::setState (s, state); | |||||
| if (isPost && postData.getSize() > 0) | |||||
| [req setHTTPBody: [NSData dataWithBytes: postData.getData() | |||||
| length: postData.getSize()]]; | |||||
| if (state->start (progressCallback, progressCallbackContext)) | |||||
| return s; | |||||
| connection = new URLConnectionState (req); | |||||
| [s release]; | |||||
| return nil; | |||||
| if (! connection->start (progressCallback, progressCallbackContext)) | |||||
| connection = nullptr; | |||||
| } | |||||
| } | } | ||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream); | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream); | ||||