Browse Source

Add new URL::downloadToFile method

tags/2021-05-28
hogliux 8 years ago
parent
commit
caa65e8ff1
9 changed files with 452 additions and 5 deletions
  1. +17
    -0
      modules/juce_core/native/juce_android_Network.cpp
  2. +10
    -0
      modules/juce_core/native/juce_curl_Network.cpp
  3. +13
    -0
      modules/juce_core/native/juce_linux_Network.cpp
  4. +220
    -0
      modules/juce_core/native/juce_mac_Network.mm
  5. +15
    -5
      modules/juce_core/native/juce_win32_Network.cpp
  6. +98
    -0
      modules/juce_core/network/juce_URL.cpp
  7. +75
    -0
      modules/juce_core/network/juce_URL.h
  8. +1
    -0
      modules/juce_core/network/juce_WebInputStream.cpp
  9. +3
    -0
      modules/juce_core/network/juce_WebInputStream.h

+ 17
- 0
modules/juce_core/native/juce_android_Network.cpp View File

@@ -80,9 +80,17 @@ public:
{} {}
~Pimpl() ~Pimpl()
{
cancel();
}
void cancel()
{ {
if (stream != 0) if (stream != 0)
{
stream.callVoidMethod (HTTPStream.release); stream.callVoidMethod (HTTPStream.release);
stream.clear();
}
} }
bool connect (WebInputStream::Listener* listener) bool connect (WebInputStream::Listener* listener)
@@ -154,7 +162,11 @@ public:
responseHeaders.set (key, previousValue.isEmpty() ? value : (previousValue + "," + value)); responseHeaders.set (key, previousValue.isEmpty() ? value : (previousValue + "," + value));
} }
return true;
} }
return false;
} }
//============================================================================== //==============================================================================
@@ -219,3 +231,8 @@ private:
GlobalRef stream; GlobalRef stream;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
}; };
URL::DownloadTask* URL::downloadToFile (const File& targetLocation, String extraHeaders, DownloadTask::Listener* listener)
{
return URL::DownloadTask::createFallbackDownloader (*this, targetLocation, extraHeaders, listener);
}

+ 10
- 0
modules/juce_core/native/juce_curl_Network.cpp View File

@@ -135,6 +135,11 @@ public:
} }
} }
void cancel()
{
cleanup();
}
//============================================================================== //==============================================================================
bool setOptions () bool setOptions ()
{ {
@@ -512,3 +517,8 @@ public:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
}; };
URL::DownloadTask* URL::downloadToFile (const File& targetLocation, String extraHeaders, DownloadTask::Listener* listener)
{
return URL::DownloadTask::createFallbackDownloader (*this, targetLocation, extraHeaders, listener);
}

+ 13
- 0
modules/juce_core/native/juce_linux_Network.cpp View File

@@ -138,6 +138,14 @@ public:
return (statusCode != 0); return (statusCode != 0);
} }
void cancel()
{
statusCode = -1;
finished = true;
closeSocket();
}
//==============================================================================w //==============================================================================w
bool isError() const { return socketHandle < 0; } bool isError() const { return socketHandle < 0; }
bool isExhausted() { return finished; } bool isExhausted() { return finished; }
@@ -556,4 +564,9 @@ private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
}; };
URL::DownloadTask* URL::downloadToFile (const File& targetLocation, String extraHeaders, DownloadTask::Listener* listener)
{
return URL::DownloadTask::createFallbackDownloader (*this, targetLocation, extraHeaders, listener);
}
#endif #endif

+ 220
- 0
modules/juce_core/native/juce_mac_Network.mm View File

@@ -150,6 +150,12 @@ public:
[data release]; [data release];
} }
void cancel()
{
signalThreadShouldExit();
stopThread (10000);
}
bool start (WebInputStream& inputStream, WebInputStream::Listener* listener) bool start (WebInputStream& inputStream, WebInputStream::Listener* listener)
{ {
startThread(); startThread();
@@ -369,6 +375,200 @@ private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (URLConnectionState) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (URLConnectionState)
}; };
//==============================================================================
#if JUCE_IOS
struct BackgroundDownloadTask : public URL::DownloadTask
{
BackgroundDownloadTask (const URL& urlToUse,
const File& targetLocationToUse,
String extraHeadersToUse,
URL::DownloadTask::Listener* listenerToUse)
: targetLocation (targetLocationToUse), listener (listenerToUse),
delegate (nullptr), session (nullptr), downloadTask (nullptr),
connectFinished (false), calledComplete (0)
{
downloaded = -1;
static DelegateClass cls;
delegate = [cls.createInstance() init];
DelegateClass::setState (delegate, this);
String uniqueIdentifier = String (urlToUse.toString (true).hashCode64()) + String (Random().nextInt64());
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:juceStringToNS (urlToUse.toString (true))]];
StringArray headerLines;
headerLines.addLines (extraHeadersToUse);
headerLines.removeEmptyStrings (true);
for (int i = 0; i < headerLines.size(); ++i)
{
String key = headerLines[i].upToFirstOccurrenceOf (":", false, false).trim();
String value = headerLines[i].fromFirstOccurrenceOf (":", false, false).trim();
if (key.isNotEmpty() && value.isNotEmpty())
[request addValue: juceStringToNS (value) forHTTPHeaderField: juceStringToNS (key)];
}
session =
[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:juceStringToNS (uniqueIdentifier)]
delegate:delegate
delegateQueue:nullptr];
if (session != nullptr)
downloadTask = [session downloadTaskWithRequest:request];
[request release];
}
~BackgroundDownloadTask()
{
[session release];
[delegate release];
}
bool initOK()
{
return (downloadTask != nullptr);
}
bool connect()
{
[downloadTask resume];
while (downloaded == -1 && finished == false)
Thread::sleep (1);
connectFinished = true;
return ! error;
}
//==============================================================================
File targetLocation;
URL::DownloadTask::Listener* listener;
NSObject<NSURLSessionDelegate>* delegate;
NSURLSession* session;
NSURLSessionDownloadTask* downloadTask;
bool connectFinished;
Atomic<int> calledComplete;
void didWriteData (int64 totalBytesWritten, int64 totalBytesExpectedToWrite)
{
downloaded = totalBytesWritten;
if (contentLength == -1)
contentLength = totalBytesExpectedToWrite;
if (connectFinished && error == false && finished == false && listener != nullptr)
listener->progress (this, totalBytesWritten, contentLength);
}
void didFinishDownloadingToURL (NSURL* location)
{
NSFileManager* fileManager = [[NSFileManager alloc] init];
error = ([fileManager moveItemAtURL:location
toURL:[NSURL fileURLWithPath:juceStringToNS (targetLocation.getFullPathName())]
error:nullptr] == NO);
httpCode = 200;
finished = true;
if (listener != nullptr && calledComplete.exchange (1) == 0)
{
if (contentLength > 0 && downloaded < contentLength)
{
downloaded = contentLength;
listener->progress (this, downloaded, contentLength);
}
listener->didComplete (this, !error);
}
}
void didCompleteWithError (NSError* nsError)
{
if (calledComplete.exchange (1) == 0)
{
httpCode = -1;
if (nsError != nullptr)
{
// see https://developer.apple.com/reference/foundation/nsurlsessiondownloadtask?language=objc
switch ([nsError code])
{
case NSURLErrorUserAuthenticationRequired:
httpCode = 401;
break;
case NSURLErrorNoPermissionsToReadFile:
httpCode = 403;
break;
case NSURLErrorFileDoesNotExist:
httpCode = 404;
break;
default:
httpCode = 500;
}
}
error = true;
finished = true;
if (listener != nullptr)
listener->didComplete (this, ! error);
}
}
//==============================================================================
struct DelegateClass : public ObjCClass<NSObject<NSURLSessionDelegate> >
{
DelegateClass() : ObjCClass<NSObject<NSURLSessionDelegate> > ("JUCE_URLDelegate_")
{
addIvar<BackgroundDownloadTask*> ("state");
addMethod (@selector (URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:),
didWriteData, "v@:@@qqq");
addMethod (@selector (URLSession:downloadTask:didFinishDownloadingToURL:),
didFinishDownloadingToURL, "v@:@@@");
addMethod (@selector (URLSession:task:didCompleteWithError:),
didCompleteWithError, "v@:@@@");
registerClass();
}
static void setState (id self, BackgroundDownloadTask* state) { object_setInstanceVariable (self, "state", state); }
static BackgroundDownloadTask* getState (id self) { return getIvar<BackgroundDownloadTask*> (self, "state"); }
private:
static void didWriteData (id self, SEL, NSURLSession*, NSURLSessionDownloadTask*, int64_t, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite)
{
if (auto state = getState (self)) state->didWriteData (totalBytesWritten, totalBytesExpectedToWrite);
}
static void didFinishDownloadingToURL (id self, SEL, NSURLSession*, NSURLSessionDownloadTask*, NSURL* location)
{
if (auto state = getState (self)) state->didFinishDownloadingToURL (location);
}
static void didCompleteWithError (id self, SEL, NSURLSession*, NSURLSessionTask*, NSError* nsError)
{
if (auto state = getState (self)) state->didCompleteWithError (nsError);
}
};
};
URL::DownloadTask* URL::downloadToFile (const File& targetLocation, String extraHeaders, DownloadTask::Listener* listener)
{
ScopedPointer<BackgroundDownloadTask> downloadTask = new BackgroundDownloadTask (*this, targetLocation, extraHeaders, listener);
if (downloadTask->initOK() && downloadTask->connect())
return downloadTask.release();
return nullptr;
}
#else
URL::DownloadTask* URL::downloadToFile (const File& targetLocation, String extraHeaders, DownloadTask::Listener* listener)
{
return URL::DownloadTask::createFallbackDownloader (*this, targetLocation, extraHeaders, listener);
}
#endif
//============================================================================== //==============================================================================
#else #else
@@ -440,6 +640,12 @@ public:
stopThread (10000); stopThread (10000);
} }
void cancel()
{
hasFinished = hasFailed = true;
stop();
}
int read (char* dest, int numBytes) int read (char* dest, int numBytes)
{ {
int numDone = 0; int numDone = 0;
@@ -615,6 +821,11 @@ private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (URLConnectionState) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (URLConnectionState)
}; };
URL::DownloadTask* URL::downloadToFile (const File& targetLocation, String extraHeaders, DownloadTask::Listener* listener)
{
return URL::DownloadTask::createFallbackDownloader (*this, targetLocation, extraHeaders, listener);
}
#pragma clang diagnostic pop #pragma clang diagnostic pop
#endif #endif
@@ -728,6 +939,15 @@ public:
return true; return true;
} }
void cancel()
{
if (finished || isError())
return;
if (connection != nullptr)
connection->cancel();
}
int statusCode; int statusCode;
private: private:


+ 15
- 5
modules/juce_core/native/juce_win32_Network.cpp View File

@@ -185,6 +185,11 @@ public:
return (int) bytesRead; return (int) bytesRead;
} }
void cancel()
{
close();
}
bool setPosition (int64 wantedPos) bool setPosition (int64 wantedPos)
{ {
if (isError()) if (isError())
@@ -231,11 +236,11 @@ private:
void close() void close()
{ {
if (request != 0)
{
InternetCloseHandle (request);
request = 0;
}
HINTERNET requestCopy = request;
request = 0;
if (requestCopy != 0)
InternetCloseHandle (requestCopy);
if (connection != 0) if (connection != 0)
{ {
@@ -548,3 +553,8 @@ bool JUCE_CALLTYPE Process::openEmailWithAttachments (const String& targetEmailA
return mapiSendMail (0, 0, &message, MAPI_DIALOG | MAPI_LOGON_UI, 0) == SUCCESS_SUCCESS; return mapiSendMail (0, 0, &message, MAPI_DIALOG | MAPI_LOGON_UI, 0) == SUCCESS_SUCCESS;
} }
URL::DownloadTask* URL::downloadToFile (const File& targetLocation, String extraHeaders, DownloadTask::Listener* listener)
{
return URL::DownloadTask::createFallbackDownloader (*this, targetLocation, extraHeaders, listener);
}

+ 98
- 0
modules/juce_core/network/juce_URL.cpp View File

@@ -26,6 +26,104 @@
============================================================================== ==============================================================================
*/ */
//==============================================================================
struct FallbackDownloadTask : public URL::DownloadTask,
public Thread
{
FallbackDownloadTask (FileOutputStream* outputStreamToUse,
size_t bufferSizeToUse,
WebInputStream* streamToUse,
URL::DownloadTask::Listener* listenerToUse)
: Thread ("DownloadTask thread"),
fileStream (outputStreamToUse),
bufferSize (bufferSizeToUse),
buffer (bufferSize),
stream (streamToUse),
listener (listenerToUse)
{
contentLength = stream->getTotalLength();
httpCode = stream->getStatusCode();
startThread ();
}
~FallbackDownloadTask()
{
signalThreadShouldExit();
stream->cancel();
waitForThreadToExit (-1);
}
//==============================================================================
void run() override
{
while (! stream->isExhausted() && ! stream->isError() && ! threadShouldExit())
{
if (listener != nullptr)
listener->progress (this, downloaded, contentLength);
const int max = jmin ((int) bufferSize, contentLength < 0 ? std::numeric_limits<int>::max()
: static_cast<int> (contentLength - downloaded));
const int actual = stream->read (buffer.getData(), max);
if (threadShouldExit() || stream->isError())
break;
if (! fileStream->write (buffer.getData(), static_cast<size_t> (actual)))
{
error = true;
break;
}
downloaded += actual;
}
if (threadShouldExit() || (stream != nullptr && stream->isError()))
error = true;
finished = true;
if (listener != nullptr && ! threadShouldExit())
listener->finished (this, ! error);
}
//==============================================================================
ScopedPointer<FileOutputStream> fileStream;
size_t bufferSize;
HeapBlock<char> buffer;
ScopedPointer<WebInputStream> stream;
URL::DownloadTask::Listener* listener;
};
void URL::DownloadTask::Listener::progress (DownloadTask*, int64, int64) {}
URL::DownloadTask::Listener::~Listener() {}
//==============================================================================
URL::DownloadTask* URL::DownloadTask::createFallbackDownloader (const URL& urlToUse,
const File& targetFileToUse,
const String& extraHeadersToUse,
Listener* listenerToUse)
{
const size_t bufferSize = 0x8000;
targetFileToUse.deleteFile();
if (ScopedPointer<FileOutputStream> outputStream = targetFileToUse.createOutputStream (bufferSize))
{
ScopedPointer<WebInputStream> stream = new WebInputStream (urlToUse, false);
stream->withExtraHeaders (extraHeadersToUse);
if (stream->connect (nullptr))
return new FallbackDownloadTask (outputStream.release(), bufferSize, stream.release(), listenerToUse);
}
return nullptr;
}
URL::DownloadTask::DownloadTask() : contentLength (-1), downloaded (0), finished (false), error (false), httpCode (-1) {}
URL::DownloadTask::~DownloadTask() {}
//==============================================================================
URL::URL() URL::URL()
{ {
} }


+ 75
- 0
modules/juce_core/network/juce_URL.h View File

@@ -322,6 +322,81 @@ public:
int numRedirectsToFollow = 5, int numRedirectsToFollow = 5,
String httpRequestCmd = String()) const; String httpRequestCmd = String()) const;
//==============================================================================
/** Represents a download task.
Returned by downloadToFile to allow querying and controling the download task.
*/
class DownloadTask
{
public:
struct Listener
{
virtual ~Listener();
/** Called when the download has finished. Be aware that this callback may
come on an arbitrary thread. */
virtual void finished (DownloadTask* task, bool success) = 0;
/** Called periodically by the OS to indicate download progress.
Beware that this callback may come on an arbitrary thread.
*/
virtual void progress (DownloadTask* task, int64 bytesDownloaded, int64 totalLength);
};
/** Releases the resources of the download task, unregisters the listener
and cancels the download if necessary. */
virtual ~DownloadTask();
/** Returns the total length of the download task. This may return -1 if the length
was not returned by the server. */
inline int64 getTotalLength() const { return contentLength; }
/** Returns the number of bytes that have been downloaded so far. */
inline int64 getLengthDownloaded() const { return downloaded; }
/** Returns true if the download finished or there was an error. */
inline bool isFinished() const { return finished; }
/** Returns the status code of the server's response. This will only be valid
after the download has finished.
@see isFinished
*/
inline int statusCode() const { return httpCode; }
/** Returns true if there was an error. */
inline bool hadError() const { return error; }
protected:
int64 contentLength, downloaded;
bool finished, error;
int httpCode;
DownloadTask ();
private:
friend class URL;
static DownloadTask* createFallbackDownloader (const URL&, const File&, const String&, Listener*);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DownloadTask)
};
/** Download the URL to a file.
This method attempts to download the URL to a given file location.
Using this method to download files on mobile is less flexible but more reliable
than using createInputStream or WebInputStreams as it will attempt to download the file
using a native OS background network task. Such tasks automatically deal with
network re-connections and continuing your download while your app is suspended but are
limited to simple GET requests.
*/
DownloadTask* downloadToFile (const File& targetLocation,
String extraHeaders = String(),
DownloadTask::Listener* listener = nullptr);
//============================================================================== //==============================================================================
/** Tries to download the entire contents of this URL into a binary data block. /** Tries to download the entire contents of this URL into a binary data block.


+ 1
- 0
modules/juce_core/network/juce_WebInputStream.cpp View File

@@ -42,6 +42,7 @@ WebInputStream& WebInputStream::withNumRedirectsToFollow (int num) {
StringPairArray WebInputStream::getRequestHeaders() const { return pimpl->getRequestHeaders(); } StringPairArray WebInputStream::getRequestHeaders() const { return pimpl->getRequestHeaders(); }
StringPairArray WebInputStream::getResponseHeaders() { connect (nullptr); return pimpl->getResponseHeaders(); } StringPairArray WebInputStream::getResponseHeaders() { connect (nullptr); return pimpl->getResponseHeaders(); }
bool WebInputStream::isError() const { return pimpl->isError(); } bool WebInputStream::isError() const { return pimpl->isError(); }
void WebInputStream::cancel() { pimpl->cancel(); }
bool WebInputStream::isExhausted() { return pimpl->isExhausted(); } bool WebInputStream::isExhausted() { return pimpl->isExhausted(); }
int64 WebInputStream::getPosition() { return pimpl->getPosition(); } int64 WebInputStream::getPosition() { return pimpl->getPosition(); }
int64 WebInputStream::getTotalLength() { connect (nullptr); return pimpl->getTotalLength(); } int64 WebInputStream::getTotalLength() { connect (nullptr); return pimpl->getTotalLength(); }


+ 3
- 0
modules/juce_core/network/juce_WebInputStream.h View File

@@ -146,6 +146,9 @@ class JUCE_API WebInputStream : public InputStream
/** Returns true if there was an error during the connection attempt */ /** Returns true if there was an error during the connection attempt */
bool isError() const; bool isError() const;
/** Will cancel a blocking read. */
void cancel();
//============================================================================== //==============================================================================
/** Returns the total number of bytes available for reading in this stream. /** Returns the total number of bytes available for reading in this stream.


Loading…
Cancel
Save