Browse Source

Added URL methods to convert between local file urls (including Android content:// URLs) and JUCE's File class

tags/2021-05-28
hogliux 7 years ago
parent
commit
ae9ec7c6e5
5 changed files with 590 additions and 91 deletions
  1. +322
    -0
      modules/juce_core/native/juce_android_Files.cpp
  2. +8
    -0
      modules/juce_core/native/juce_android_JNIHelpers.h
  3. +152
    -66
      modules/juce_core/native/juce_android_Network.cpp
  4. +73
    -12
      modules/juce_core/network/juce_URL.cpp
  5. +35
    -13
      modules/juce_core/network/juce_URL.h

+ 322
- 0
modules/juce_core/native/juce_android_Files.cpp View File

@@ -32,6 +32,328 @@ namespace juce
DECLARE_JNI_CLASS (MediaScannerConnection, "android/media/MediaScannerConnection");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
METHOD (query, "query", "(Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;") \
METHOD (openInputStream, "openInputStream", "(Landroid/net/Uri;)Ljava/io/InputStream;") \
DECLARE_JNI_CLASS (ContentResolver, "android/content/ContentResolver");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
METHOD (moveToFirst, "moveToFirst", "()Z") \
METHOD (getColumnIndex, "getColumnIndex", "(Ljava/lang/String;)I") \
METHOD (getString, "getString", "(I)Ljava/lang/String;") \
METHOD (close, "close", "()V") \
DECLARE_JNI_CLASS (AndroidCursor, "android/database/Cursor");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
STATICMETHOD (getExternalStorageDirectory, "getExternalStorageDirectory", "()Ljava/io/File;") \
STATICMETHOD (getExternalStoragePublicDirectory, "getExternalStoragePublicDirectory", "(Ljava/lang/String;)Ljava/io/File;") \
DECLARE_JNI_CLASS (AndroidEnvironment, "android/os/Environment");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
METHOD (getAbsolutePath, "getAbsolutePath", "()Ljava/lang/String;") \
DECLARE_JNI_CLASS (AndroidFile, "java/io/File");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
STATICMETHOD (withAppendedId, "withAppendedId", "(Landroid/net/Uri;J)Landroid/net/Uri;") \
DECLARE_JNI_CLASS (ContentUris, "android/content/ContentUris");
#undef JNI_CLASS_MEMBERS
//==============================================================================
struct AndroidContentUriResolver
{
public:
static LocalRef<jobject> getInputStreamForContentUri (const URL& url)
{
// only use this method for content URIs
jassert (url.getScheme() == "content");
auto* env = getEnv();
LocalRef<jobject> contentResolver (android.activity.callObjectMethod (JuceAppActivity.getContentResolver));
if (contentResolver)
return LocalRef<jobject> ((env->CallObjectMethod (contentResolver.get(), ContentResolver.openInputStream, urlToUri (url).get())));
return LocalRef<jobject>();
}
static File getLocalFileFromContentUri (const URL& url)
{
// only use this method for content URIs
jassert (url.getScheme() == "content");
auto authority = url.getDomain();
auto documentId = URL::removeEscapeChars (url.getSubPath().fromFirstOccurrenceOf ("/", false, false));
auto tokens = StringArray::fromTokens (documentId, ":", "");
if (authority == "com.android.externalstorage.documents")
{
auto storageId = tokens[0];
auto subpath = tokens[1];
auto storagePath = getStorageDevicePath (storageId);
if (storagePath != File())
return storagePath.getChildFile (subpath);
}
else if (authority == "com.android.providers.downloads.documents")
{
auto type = tokens[0];
auto downloadId = tokens[1];
if (type.equalsIgnoreCase ("raw"))
{
return File (downloadId);
}
else if (type.equalsIgnoreCase ("downloads"))
{
auto subDownloadPath = url.getSubPath().fromFirstOccurrenceOf ("tree/downloads", false, false);
return File (getWellKnownFolder ("Download").getFullPathName() + "/" + subDownloadPath);
}
else
{
return getLocalFileFromContentUri (URL ("content://downloads/public_downloads/" + documentId));
}
}
else if (authority == "com.android.providers.media.documents" && documentId.isNotEmpty())
{
auto type = tokens[0];
auto mediaId = tokens[1];
if (type == "image")
type = "images";
return getCursorDataColumn (URL (String ("content://media/external/") + type + "/media"),
"_id=?", StringArray {mediaId});
}
return getCursorDataColumn (url);
}
private:
//==============================================================================
static String getCursorDataColumn (const URL& url, const String& selection = {},
const StringArray& selectionArgs = {})
{
auto uri = urlToUri (url);
auto* env = getEnv();
LocalRef<jobject> contentResolver (android.activity.callObjectMethod (JuceAppActivity.getContentResolver));
if (contentResolver)
{
LocalRef<jstring> columnName (javaString ("_data"));
LocalRef<jobjectArray> projection (env->NewObjectArray (1, JavaString, columnName.get()));
LocalRef<jobjectArray> args;
if (selection.isNotEmpty())
{
args = LocalRef<jobjectArray> (env->NewObjectArray (selectionArgs.size(), JavaString, javaString("").get()));
for (int i = 0; i < selectionArgs.size(); ++i)
env->SetObjectArrayElement (args.get(), i, javaString (selectionArgs[i]).get());
}
LocalRef<jstring> jSelection (selection.isNotEmpty() ? javaString (selection) : LocalRef<jstring>());
LocalRef<jobject> cursor (env->CallObjectMethod (contentResolver.get(), ContentResolver.query,
uri.get(), projection.get(), jSelection.get(),
args.get(), nullptr));
if (cursor)
{
if (env->CallBooleanMethod (cursor.get(), AndroidCursor.moveToFirst) != 0)
{
auto columnIndex = env->CallIntMethod (cursor.get(), AndroidCursor.getColumnIndex, columnName.get());
if (columnIndex >= 0)
{
LocalRef<jstring> value ((jstring) env->CallObjectMethod (cursor.get(), AndroidCursor.getString, columnIndex));
if (value)
return juceString (value.get());
}
}
env->CallVoidMethod (cursor.get(), AndroidCursor.close);
}
}
return {};
}
//==============================================================================
static File getWellKnownFolder (const String& folderId)
{
auto* env = getEnv();
LocalRef<jobject> downloadFolder (env->CallStaticObjectMethod (AndroidEnvironment,
AndroidEnvironment.getExternalStoragePublicDirectory,
javaString (folderId).get()));
return (downloadFolder ? juceFile (downloadFolder) : File());
}
//==============================================================================
static File getStorageDevicePath (const String& storageId)
{
// check for the primary alias
if (storageId == "primary")
return getPrimaryStorageDirectory();
auto storageDevices = getSecondaryStorageDirectories();
for (auto storageDevice : storageDevices)
if (getStorageIdForMountPoint (storageDevice) == storageId)
return storageDevice;
return {};
}
static File getPrimaryStorageDirectory()
{
auto* env = getEnv();
return juceFile (LocalRef<jobject> (env->CallStaticObjectMethod (AndroidEnvironment, AndroidEnvironment.getExternalStorageDirectory)));
}
static Array<File> getSecondaryStorageDirectories()
{
Array<File> results;
if (getSDKVersion() >= 19)
{
auto* env = getEnv();
static jmethodID m = (env->GetMethodID (JuceAppActivity, "getExternalFilesDirs",
"(Ljava/lang/String;)[Ljava/io/File;"));
if (m == 0)
return {};
auto paths = convertFileArray (LocalRef<jobject> (android.activity.callObjectMethod (m, nullptr)));
for (auto path : paths)
results.add (getMountPointForFile (path));
}
else
{
// on older SDKs other external storages are located "next" to the primary
// storage mount point
auto mountFolder = getMountPointForFile (getPrimaryStorageDirectory())
.getParentDirectory();
// don't include every folder. Only folders which are actually mountpoints
juce_statStruct info;
if (! juce_stat (mountFolder.getFullPathName(), info))
return {};
auto rootFsDevice = info.st_dev;
DirectoryIterator iter (mountFolder, false, "*", File::findDirectories);
while (iter.next())
{
auto candidate = iter.getFile();
if (juce_stat (candidate.getFullPathName(), info)
&& info.st_dev != rootFsDevice)
results.add (candidate);
}
}
return results;
}
//==============================================================================
static String getStorageIdForMountPoint (const File& mountpoint)
{
// currently this seems to work fine, but something
// more intelligent may be needed in the future
return mountpoint.getFileName();
}
static File getMountPointForFile (const File& file)
{
juce_statStruct info;
if (juce_stat (file.getFullPathName(), info))
{
auto dev = info.st_dev;
File mountPoint = file;
for (;;)
{
auto parent = mountPoint.getParentDirectory();
if (parent == mountPoint)
break;
juce_stat (parent.getFullPathName(), info);
if (info.st_dev != dev)
break;
mountPoint = parent;
}
return mountPoint;
}
return {};
}
//==============================================================================
static Array<File> convertFileArray (LocalRef<jobject> obj)
{
auto* env = getEnv();
int n = (int) env->GetArrayLength ((jobjectArray) obj.get());
Array<File> files;
for (int i = 0; i < n; ++i)
files.add (juceFile (LocalRef<jobject> (env->GetObjectArrayElement ((jobjectArray) obj.get(),
(jsize) i))));
return files;
}
static File juceFile (LocalRef<jobject> obj)
{
auto* env = getEnv();
if (env->IsInstanceOf (obj.get(), AndroidFile) != 0)
return File (safeString (LocalRef<jobject> (env->CallObjectMethod (obj.get(),
AndroidFile.getAbsolutePath))));
return {};
}
//==============================================================================
static int getSDKVersion()
{
static int sdkVersion
= getEnv()->CallStaticIntMethod (JuceAppActivity,
JuceAppActivity.getAndroidSDKVersion);
return sdkVersion;
}
static LocalRef<jobject> urlToUri (const URL& url)
{
return LocalRef<jobject> (getEnv()->CallStaticObjectMethod (Uri, Uri.parse, javaString (url.toString (true)).get()));
}
static String safeString (LocalRef<jobject> str)
{
if (str)
return juceString ((jstring) str.get());
return {};
}
};
//==============================================================================
class MediaScannerConnectionClient : public AndroidInterfaceImplementer
{


+ 8
- 0
modules/juce_core/native/juce_android_JNIHelpers.h View File

@@ -324,6 +324,7 @@ extern AndroidSystem android;
METHOD (startIntentSenderForResult, "startIntentSenderForResult", "(Landroid/content/IntentSender;ILandroid/content/Intent;III)V") \
METHOD (moveTaskToBack, "moveTaskToBack", "(Z)Z") \
METHOD (startActivity, "startActivity", "(Landroid/content/Intent;)V") \
METHOD (getContentResolver, "getContentResolver", "()Landroid/content/ContentResolver;") \
DECLARE_JNI_CLASS (JuceAppActivity, JUCE_ANDROID_ACTIVITY_CLASSPATH);
#undef JNI_CLASS_MEMBERS
@@ -563,6 +564,13 @@ DECLARE_JNI_CLASS (JavaSet, "java/util/Set");
DECLARE_JNI_CLASS (JavaString, "java/lang/String");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
STATICMETHOD (parse, "parse", "(Ljava/lang/String;)Landroid/net/Uri;") \
METHOD (getAuthority, "getAuthority", "()Ljava/lang/String;") \
DECLARE_JNI_CLASS (Uri, "android/net/Uri");
#undef JNI_CLASS_MEMBERS
//==============================================================================
class AndroidInterfaceImplementer;


+ 152
- 66
modules/juce_core/native/juce_android_Network.cpp View File

@@ -43,6 +43,13 @@ DECLARE_JNI_CLASS (StringBuffer, "java/lang/StringBuffer");
DECLARE_JNI_CLASS (HTTPStream, JUCE_ANDROID_ACTIVITY_CLASSPATH "$HTTPStream");
#undef JNI_CLASS_MEMBERS
//==============================================================================
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
METHOD (close, "close", "()V") \
METHOD (read, "read", "([BII)I") \
DECLARE_JNI_CLASS (AndroidInputStream, "java/io/InputStream");
#undef JNI_CLASS_MEMBERS
//==============================================================================
void MACAddress::findAllAddresses (Array<MACAddress>& /*result*/)
@@ -60,12 +67,43 @@ JUCE_API bool JUCE_CALLTYPE Process::openEmailWithAttachments (const String& /*t
return false;
}
//==============================================================================
bool URL::isLocalFile() const
{
if (getScheme() == "file")
return true;
auto file = AndroidContentUriResolver::getLocalFileFromContentUri (*this);
return (file != File());
}
File URL::getLocalFile() const
{
if (getScheme() == "content")
{
auto path = AndroidContentUriResolver::getLocalFileFromContentUri (*this);
// This URL does not refer to a local file
// Call URL::isLocalFile to first check if the URL
// refers to a local file.
jassert (path != File());
return path;
}
return fileFromFileSchemeURL (*this);
}
//==============================================================================
class WebInputStream::Pimpl
{
public:
enum { contentStreamCacheSize = 1024 };
Pimpl (WebInputStream&, const URL& urlToCopy, bool shouldBePost)
: url (urlToCopy), isPost (shouldBePost),
: url (urlToCopy),
isContentURL (urlToCopy.getScheme() == "content"),
isPost (shouldBePost),
httpRequest (isPost ? "POST" : "GET")
{}
@@ -76,6 +114,12 @@ public:
void cancel()
{
if (isContentURL)
{
stream.callVoidMethod (AndroidInputStream.close);
return;
}
const ScopedLock lock (createStreamLock);
if (stream != 0)
@@ -89,83 +133,98 @@ public:
bool connect (WebInputStream::Listener* /*listener*/)
{
String address = url.toString (! isPost);
auto* env = getEnv();
if (! address.contains ("://"))
address = "http://" + address;
MemoryBlock postData;
if (isPost)
WebInputStream::createHeadersAndPostData (url, headers, postData);
JNIEnv* env = getEnv();
if (isContentURL)
{
auto inputStream = AndroidContentUriResolver::getInputStreamForContentUri (url);
jbyteArray postDataArray = 0;
if (inputStream != nullptr)
{
stream = GlobalRef (inputStream);
statusCode = 200;
if (postData.getSize() > 0)
{
postDataArray = env->NewByteArray (static_cast<jsize> (postData.getSize()));
env->SetByteArrayRegion (postDataArray, 0, static_cast<jsize> (postData.getSize()), (const jbyte*) postData.getData());
return true;
}
}
else
{
String address = url.toString (! isPost);
LocalRef<jobject> responseHeaderBuffer (env->NewObject (StringBuffer, StringBuffer.constructor));
if (! address.contains ("://"))
address = "http://" + address;
// Annoyingly, the android HTTP functions will choke on this call if you try to do it on the message
// thread. You'll need to move your networking code to a background thread to keep it happy..
jassert (Thread::getCurrentThread() != nullptr);
MemoryBlock postData;
if (isPost)
WebInputStream::createHeadersAndPostData (url, headers, postData);
jintArray statusCodeArray = env->NewIntArray (1);
jassert (statusCodeArray != 0);
jbyteArray postDataArray = 0;
{
const ScopedLock lock (createStreamLock);
if (! hasBeenCancelled)
stream = GlobalRef (env->CallStaticObjectMethod (JuceAppActivity,
JuceAppActivity.createHTTPStream,
javaString (address).get(),
(jboolean) isPost,
postDataArray,
javaString (headers).get(),
(jint) timeOutMs,
statusCodeArray,
responseHeaderBuffer.get(),
(jint) numRedirectsToFollow,
javaString (httpRequest).get()));
}
if (postData.getSize() > 0)
{
postDataArray = env->NewByteArray (static_cast<jsize> (postData.getSize()));
env->SetByteArrayRegion (postDataArray, 0, static_cast<jsize> (postData.getSize()), (const jbyte*) postData.getData());
}
if (stream != 0 && ! stream.callBooleanMethod (HTTPStream.connect))
stream.clear();
LocalRef<jobject> responseHeaderBuffer (env->NewObject (StringBuffer, StringBuffer.constructor));
jint* const statusCodeElements = env->GetIntArrayElements (statusCodeArray, 0);
statusCode = statusCodeElements[0];
env->ReleaseIntArrayElements (statusCodeArray, statusCodeElements, 0);
env->DeleteLocalRef (statusCodeArray);
// Annoyingly, the android HTTP functions will choke on this call if you try to do it on the message
// thread. You'll need to move your networking code to a background thread to keep it happy..
jassert (Thread::getCurrentThread() != nullptr);
if (postDataArray != 0)
env->DeleteLocalRef (postDataArray);
if (stream != 0)
{
StringArray headerLines;
jintArray statusCodeArray = env->NewIntArray (1);
jassert (statusCodeArray != 0);
{
LocalRef<jstring> headersString ((jstring) env->CallObjectMethod (responseHeaderBuffer.get(),
StringBuffer.toString));
headerLines.addLines (juceString (env, headersString));
const ScopedLock lock (createStreamLock);
if (! hasBeenCancelled)
stream = GlobalRef (LocalRef<jobject> (env->CallStaticObjectMethod (JuceAppActivity,
JuceAppActivity.createHTTPStream,
javaString (address).get(),
(jboolean) isPost,
postDataArray,
javaString (headers).get(),
(jint) timeOutMs,
statusCodeArray,
responseHeaderBuffer.get(),
(jint) numRedirectsToFollow,
javaString (httpRequest).get())));
}
for (int i = 0; i < headerLines.size(); ++i)
if (stream != 0 && ! stream.callBooleanMethod (HTTPStream.connect))
stream.clear();
jint* const statusCodeElements = env->GetIntArrayElements (statusCodeArray, 0);
statusCode = statusCodeElements[0];
env->ReleaseIntArrayElements (statusCodeArray, statusCodeElements, 0);
env->DeleteLocalRef (statusCodeArray);
if (postDataArray != 0)
env->DeleteLocalRef (postDataArray);
if (stream != 0)
{
const String& header = headerLines[i];
const String key (header.upToFirstOccurrenceOf (": ", false, false));
const String value (header.fromFirstOccurrenceOf (": ", false, false));
const String previousValue (responseHeaders[key]);
StringArray headerLines;
responseHeaders.set (key, previousValue.isEmpty() ? value : (previousValue + "," + value));
}
{
LocalRef<jstring> headersString ((jstring) env->CallObjectMethod (responseHeaderBuffer.get(),
StringBuffer.toString));
headerLines.addLines (juceString (env, headersString));
}
for (int i = 0; i < headerLines.size(); ++i)
{
const String& header = headerLines[i];
const String key (header.upToFirstOccurrenceOf (": ", false, false));
const String value (header.fromFirstOccurrenceOf (": ", false, false));
const String previousValue (responseHeaders[key]);
responseHeaders.set (key, previousValue.isEmpty() ? value : (previousValue + "," + value));
}
return true;
return true;
}
}
return false;
@@ -193,11 +252,30 @@ public:
//==============================================================================
bool isError() const { return stream == nullptr; }
bool isExhausted() { return (isContentURL ? eofStreamReached : stream != nullptr && stream.callBooleanMethod (HTTPStream.isExhausted)); }
int64 getTotalLength() { return (isContentURL ? -1 : (stream != nullptr ? stream.callLongMethod (HTTPStream.getTotalLength) : 0)); }
int64 getPosition() { return (isContentURL ? readPosition : (stream != nullptr ? stream.callLongMethod (HTTPStream.getPosition) : 0)); }
//==============================================================================
bool setPosition (int64 wantedPos)
{
if (isContentURL)
{
if (wantedPos < readPosition)
return false;
auto bytesToSkip = wantedPos - readPosition;
if (bytesToSkip == 0)
return true;
bool isExhausted() { return stream != nullptr && stream.callBooleanMethod (HTTPStream.isExhausted); }
int64 getTotalLength() { return stream != nullptr ? stream.callLongMethod (HTTPStream.getTotalLength) : 0; }
int64 getPosition() { return stream != nullptr ? stream.callLongMethod (HTTPStream.getPosition) : 0; }
bool setPosition (int64 wantedPos) { return stream != nullptr && stream.callBooleanMethod (HTTPStream.setPosition, (jlong) wantedPos); }
HeapBlock<char> buffer (bytesToSkip);
return (read (buffer.getData(), (int) bytesToSkip) > 0);
}
return stream != nullptr && stream.callBooleanMethod (HTTPStream.setPosition, (jlong) wantedPos);
}
int read (void* buffer, int bytesToRead)
{
@@ -212,12 +290,19 @@ public:
jbyteArray javaArray = env->NewByteArray (bytesToRead);
int numBytes = stream.callIntMethod (HTTPStream.read, javaArray, (jint) bytesToRead);
auto numBytes = (isContentURL ? stream.callIntMethod (AndroidInputStream.read, javaArray, 0, (jint) bytesToRead)
: stream.callIntMethod (HTTPStream.read, javaArray, (jint) bytesToRead));
if (numBytes > 0)
env->GetByteArrayRegion (javaArray, 0, numBytes, static_cast<jbyte*> (buffer));
env->DeleteLocalRef (javaArray);
readPosition += jmax (0, numBytes);
if (numBytes == -1)
eofStreamReached = true;
return numBytes;
}
@@ -226,12 +311,13 @@ public:
private:
const URL url;
bool isPost;
bool isContentURL, isPost, eofStreamReached = false;
int numRedirectsToFollow = 5, timeOutMs = 0;
String httpRequest, headers;
StringPairArray responseHeaders;
CriticalSection createStreamLock;
bool hasBeenCancelled = false;
int readPosition = 0;
GlobalRef stream;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)


+ 73
- 12
modules/juce_core/network/juce_URL.cpp View File

@@ -137,6 +137,28 @@ URL::DownloadTask::~DownloadTask() {}
URL::URL() noexcept {}
URL::URL (const String& u) : url (u)
{
init();
}
URL::URL (File localFile)
{
while (! localFile.isRoot())
{
url = "/" + addEscapeChars (localFile.getFileName(), false) + url;
localFile = localFile.getParentDirectory();
}
url = addEscapeChars (localFile.getFileName (), false) + url;
if (! url.startsWithChar (L'/'))
url = "/" + url;
url = "file://" + url;
jassert (isWellFormed());
}
void URL::init()
{
int i = url.indexOfChar ('?');
@@ -320,6 +342,40 @@ String URL::getScheme() const
return url.substring (0, URLHelpers::findEndOfScheme (url) - 1);
}
#ifndef JUCE_ANDROID
bool URL::isLocalFile() const
{
return (getScheme() == "file");
}
File URL::getLocalFile() const
{
return fileFromFileSchemeURL (*this);
}
#endif
File URL::fileFromFileSchemeURL (const URL& fileURL)
{
if (! fileURL.isLocalFile())
{
jassertfalse;
return {};
}
auto path = removeEscapeChars (fileURL.getDomain());
#ifndef JUCE_WINDOWS
path = File::getSeparatorString() + path;
#endif
auto urlElements = StringArray::fromTokens (fileURL.getSubPath(), "/", "");
for (auto urlElement : urlElements)
path += File::getSeparatorString() + removeEscapeChars (urlElement);
return path;
}
int URL::getPort() const
{
auto colonPos = url.indexOfChar (URLHelpers::findStartOfNetLocation (url), ':');
@@ -438,16 +494,19 @@ bool URL::isProbablyAnEmailAddress (const String& possibleEmailAddress)
}
//==============================================================================
WebInputStream* URL::createInputStream (const bool usePostCommand,
OpenStreamProgressCallback* const progressCallback,
void* const progressCallbackContext,
String headers,
const int timeOutMs,
StringPairArray* const responseHeaders,
int* statusCode,
const int numRedirectsToFollow,
String httpRequestCmd) const
{
InputStream* URL::createInputStream (const bool usePostCommand,
OpenStreamProgressCallback* const progressCallback,
void* const progressCallbackContext,
String headers,
const int timeOutMs,
StringPairArray* const responseHeaders,
int* statusCode,
const int numRedirectsToFollow,
String httpRequestCmd) const
{
if (isLocalFile())
return getLocalFile().createInputStream();
ScopedPointer<WebInputStream> wi (new WebInputStream (*this, usePostCommand));
struct ProgressCallbackCaller : WebInputStream::Listener
@@ -501,7 +560,8 @@ WebInputStream* URL::createInputStream (const bool usePostCommand,
//==============================================================================
bool URL::readEntireBinaryStream (MemoryBlock& destData, bool usePostCommand) const
{
const ScopedPointer<InputStream> in (createInputStream (usePostCommand));
const ScopedPointer<InputStream> in (isLocalFile() ? getLocalFile().createInputStream()
: static_cast<InputStream*> (createInputStream (usePostCommand)));
if (in != nullptr)
{
@@ -514,7 +574,8 @@ bool URL::readEntireBinaryStream (MemoryBlock& destData, bool usePostCommand) co
String URL::readEntireTextStream (bool usePostCommand) const
{
const ScopedPointer<InputStream> in (createInputStream (usePostCommand));
const ScopedPointer<InputStream> in (isLocalFile() ? getLocalFile().createInputStream()
: static_cast<InputStream*> (createInputStream (usePostCommand)));
if (in != nullptr)
return in->readEntireStreamAsString();


+ 35
- 13
modules/juce_core/network/juce_URL.h View File

@@ -53,6 +53,9 @@ public:
URL (URL&&);
URL& operator= (URL&&);
/** Creates URL referring to a local file on your disk using the file:// scheme. */
explicit URL (File);
/** Destructor. */
~URL();
@@ -94,6 +97,20 @@ public:
*/
String getScheme() const;
/** Returns true if this URL refers to a local file. */
bool isLocalFile() const;
/** Returns the file path of the local file to which this URL refers to.
If the URL does not represent a local file URL (i.e. the URL's scheme is not 'file')
then this method will assert.
This method also supports converting Android's content:// URLs to
local file paths.
@see isLocalFile
*/
File getLocalFile() const;
/** Attempts to read a port number from the URL.
@returns the port number, or 0 if none is explicitly specified.
*/
@@ -260,16 +277,18 @@ public:
/** Attempts to open a stream that can read from this URL.
This method is a convenience wrapper for creating a new WebInputStream and setting some
commonly used options. The returned WebInputStream will have already been connected and
reading can start instantly.
Note that this method will block until the first byte of data has been received or an
error has occurred.
Note that on some platforms (Android, for example) it's not permitted to do any network
action from the message thread, so you must only call it from a background thread.
Unless the URL represents a local file, this method returns an instance of a
WebInputStream. You can use dynamic_cast to cast the return value to a WebInputStream
which allows you more fine-grained control of the transfer process.
If the URL represents a local file, then this method simply returns a FileInputStream.
@param doPostLikeRequest if true, the parameters added to this class will be transferred
via the HTTP headers which is typical for POST requests. Otherwise
the parameters will be added to the URL address. Additionally,
@@ -298,15 +317,15 @@ public:
@returns an input stream that the caller must delete, or a null pointer if there was an
error trying to open it.
*/
WebInputStream* createInputStream (bool doPostLikeRequest,
OpenStreamProgressCallback* progressCallback = nullptr,
void* progressCallbackContext = nullptr,
String extraHeaders = String(),
int connectionTimeOutMs = 0,
StringPairArray* responseHeaders = nullptr,
int* statusCode = nullptr,
int numRedirectsToFollow = 5,
String httpRequestCmd = String()) const;
InputStream* createInputStream (bool doPostLikeRequest,
OpenStreamProgressCallback* progressCallback = nullptr,
void* progressCallbackContext = nullptr,
String extraHeaders = String(),
int connectionTimeOutMs = 0,
StringPairArray* responseHeaders = nullptr,
int* statusCode = nullptr,
int numRedirectsToFollow = 5,
String httpRequestCmd = String()) const;
//==============================================================================
/** Represents a download task.
@@ -487,6 +506,8 @@ private:
MemoryBlock postData;
StringArray parameterNames, parameterValues;
static File fileFromFileSchemeURL (const URL&);
struct Upload : public ReferenceCountedObject
{
Upload (const String&, const String&, const String&, const File&, MemoryBlock*);
@@ -501,6 +522,7 @@ private:
ReferenceCountedArray<Upload> filesToUpload;
URL (const String&, int);
void init();
void addParameter (const String&, const String&);
void createHeadersAndPostData (String&, MemoryBlock&) const;
URL withUpload (Upload*) const;


Loading…
Cancel
Save