mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-31 03:00:05 +00:00
Added URL methods to convert between local file urls (including Android content:// URLs) and JUCE's File class
This commit is contained in:
parent
5aec5ce984
commit
ae9ec7c6e5
5 changed files with 598 additions and 99 deletions
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
jbyteArray postDataArray = 0;
|
||||
|
||||
if (postData.getSize() > 0)
|
||||
if (isContentURL)
|
||||
{
|
||||
postDataArray = env->NewByteArray (static_cast<jsize> (postData.getSize()));
|
||||
env->SetByteArrayRegion (postDataArray, 0, static_cast<jsize> (postData.getSize()), (const jbyte*) postData.getData());
|
||||
}
|
||||
|
||||
LocalRef<jobject> responseHeaderBuffer (env->NewObject (StringBuffer, StringBuffer.constructor));
|
||||
|
||||
// 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);
|
||||
|
||||
jintArray statusCodeArray = env->NewIntArray (1);
|
||||
jassert (statusCodeArray != 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 (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)
|
||||
{
|
||||
StringArray headerLines;
|
||||
auto inputStream = AndroidContentUriResolver::getInputStreamForContentUri (url);
|
||||
|
||||
if (inputStream != nullptr)
|
||||
{
|
||||
LocalRef<jstring> headersString ((jstring) env->CallObjectMethod (responseHeaderBuffer.get(),
|
||||
StringBuffer.toString));
|
||||
headerLines.addLines (juceString (env, headersString));
|
||||
stream = GlobalRef (inputStream);
|
||||
statusCode = 200;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
String address = url.toString (! isPost);
|
||||
|
||||
if (! address.contains ("://"))
|
||||
address = "http://" + address;
|
||||
|
||||
MemoryBlock postData;
|
||||
if (isPost)
|
||||
WebInputStream::createHeadersAndPostData (url, headers, postData);
|
||||
|
||||
jbyteArray postDataArray = 0;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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]);
|
||||
LocalRef<jobject> responseHeaderBuffer (env->NewObject (StringBuffer, StringBuffer.constructor));
|
||||
|
||||
responseHeaders.set (key, previousValue.isEmpty() ? value : (previousValue + "," + value));
|
||||
// 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);
|
||||
|
||||
jintArray statusCodeArray = env->NewIntArray (1);
|
||||
jassert (statusCodeArray != 0);
|
||||
|
||||
{
|
||||
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())));
|
||||
}
|
||||
|
||||
return true;
|
||||
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)
|
||||
{
|
||||
StringArray headerLines;
|
||||
|
||||
{
|
||||
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 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 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); }
|
||||
//==============================================================================
|
||||
bool setPosition (int64 wantedPos)
|
||||
{
|
||||
if (isContentURL)
|
||||
{
|
||||
if (wantedPos < readPosition)
|
||||
return false;
|
||||
|
||||
auto bytesToSkip = wantedPos - readPosition;
|
||||
|
||||
if (bytesToSkip == 0)
|
||||
return true;
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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…
Add table
Add a link
Reference in a new issue