Browse Source

Update juce

Signed-off-by: falkTX <falktx@falktx.com>
tags/2021-03-15
falkTX 1 year ago
parent
commit
32de35c3cc
Signed by: falkTX <falktx@falktx.com> GPG Key ID: CDBAA37ABC74FBA0
13 changed files with 845 additions and 339 deletions
  1. +31
    -0
      libs/juce-current/patches/26_fix-x11-opengl-offset.patch
  2. +20
    -8
      libs/juce-current/source/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h
  3. +13
    -3
      libs/juce-current/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp
  4. +239
    -48
      libs/juce-current/source/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp
  5. +23
    -2
      libs/juce-current/source/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.h
  6. +14
    -11
      libs/juce-current/source/modules/juce_core/native/juce_curl_Network.cpp
  7. +20
    -14
      libs/juce-current/source/modules/juce_core/native/juce_linux_Network.cpp
  8. +9
    -7
      libs/juce-current/source/modules/juce_core/native/juce_mac_Network.mm
  9. +12
    -9
      libs/juce-current/source/modules/juce_core/native/juce_win32_Network.cpp
  10. +132
    -43
      libs/juce-current/source/modules/juce_core/network/juce_URL.cpp
  11. +239
    -132
      libs/juce-current/source/modules/juce_core/network/juce_URL.h
  12. +22
    -10
      libs/juce-current/source/modules/juce_core/network/juce_WebInputStream.cpp
  13. +71
    -52
      libs/juce-current/source/modules/juce_core/network/juce_WebInputStream.h

+ 31
- 0
libs/juce-current/patches/26_fix-x11-opengl-offset.patch View File

@@ -0,0 +1,31 @@
diff --git a/modules/juce_opengl/native/juce_OpenGL_linux_X11.h b/modules/juce_opengl/native/juce_OpenGL_linux_X11.h
index 203798b2f..aed0f3af9 100644
--- a/modules/juce_opengl/native/juce_OpenGL_linux_X11.h
+++ b/modules/juce_opengl/native/juce_OpenGL_linux_X11.h
@@ -110,7 +110,8 @@ public:
auto glBounds = component.getTopLevelComponent()
->getLocalArea (&component, component.getLocalBounds());
- glBounds = Desktop::getInstance().getDisplays().logicalToPhysical (glBounds);
+ if (JUCEApplicationBase::isStandaloneApp())
+ glBounds = Desktop::getInstance().getDisplays().logicalToPhysical (glBounds);
embeddedWindow = X11Symbols::getInstance()->xCreateWindow (display, windowH,
glBounds.getX(), glBounds.getY(),
@@ -227,8 +228,14 @@ public:
auto physicalBounds = Desktop::getInstance().getDisplays().logicalToPhysical (bounds);
XWindowSystemUtilities::ScopedXLock xLock;
- X11Symbols::getInstance()->xMoveResizeWindow (display, embeddedWindow,
- physicalBounds.getX(), physicalBounds.getY(),
+
+ if (JUCEApplicationBase::isStandaloneApp())
+ X11Symbols::getInstance()->xMoveResizeWindow (display, embeddedWindow,
+ physicalBounds.getX(), physicalBounds.getY(),
+ (unsigned int) jmax (1, physicalBounds.getWidth()),
+ (unsigned int) jmax (1, physicalBounds.getHeight()));
+ else
+ X11Symbols::getInstance()->xResizeWindow (display, embeddedWindow,
(unsigned int) jmax (1, physicalBounds.getWidth()),
(unsigned int) jmax (1, physicalBounds.getHeight()));
}

+ 20
- 8
libs/juce-current/source/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h View File

@@ -113,7 +113,7 @@ public:
startTimer (500);
}
virtual ~StandalonePluginHolder() override
~StandalonePluginHolder() override
{
stopTimer();
@@ -755,9 +755,9 @@ private:
notification.setBounds (r.removeFromTop (NotificationArea::height));
if (editor != nullptr)
editor->setBounds (editor->getLocalArea (this, r.toFloat())
.withPosition (r.getTopLeft().toFloat().transformedBy (editor->getTransform().inverted()))
.toNearestInt());
editor->setBoundsConstrained (editor->getLocalArea (this, r.toFloat())
.withPosition (r.getTopLeft().toFloat().transformedBy (editor->getTransform().inverted()))
.toNearestInt());
}
private:
@@ -819,10 +819,22 @@ private:
#else
if (editor != nullptr)
{
auto rect = getSizeToContainEditor();
setSize (rect.getWidth(),
rect.getHeight() + (shouldShowNotification ? NotificationArea::height : 0));
const int extraHeight = shouldShowNotification ? NotificationArea::height : 0;
const auto rect = getSizeToContainEditor();
if (auto* editorConstrainer = editor->getConstrainer())
{
const auto borders = owner.getContentComponentBorder();
const auto extraWindowWidth = borders.getLeftAndRight();
const auto extraWindowHeight = extraHeight + borders.getTopAndBottom();
owner.setResizeLimits (editorConstrainer->getMinimumWidth() + extraWindowWidth,
editorConstrainer->getMinimumHeight() + extraWindowHeight,
editorConstrainer->getMaximumWidth() + extraWindowWidth,
editorConstrainer->getMaximumHeight() + extraWindowHeight);
}
setSize (rect.getWidth(), rect.getHeight() + extraHeight);
}
#endif
}


+ 13
- 3
libs/juce-current/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp View File

@@ -127,10 +127,20 @@ void AudioProcessorEditor::attachResizableCornerComponent()
void AudioProcessorEditor::setBoundsConstrained (Rectangle<int> newBounds)
{
if (constrainer != nullptr)
constrainer->setBoundsForComponent (this, newBounds, false, false, false, false);
else
if (constrainer == nullptr)
{
setBounds (newBounds);
return;
}
auto currentBounds = getBounds();
constrainer->setBoundsForComponent (this,
newBounds,
newBounds.getY() != currentBounds.getY() && newBounds.getBottom() == currentBounds.getBottom(),
newBounds.getX() != currentBounds.getX() && newBounds.getRight() == currentBounds.getRight(),
newBounds.getY() == currentBounds.getY() && newBounds.getBottom() != currentBounds.getBottom(),
newBounds.getX() == currentBounds.getX() && newBounds.getRight() != currentBounds.getRight());
}
void AudioProcessorEditor::editorResized (bool wasResized)


+ 239
- 48
libs/juce-current/source/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp View File

@@ -26,6 +26,102 @@
namespace juce
{
template <typename Value>
struct ChannelInfo
{
ChannelInfo() = default;
ChannelInfo (Value** dataIn, int numChannelsIn)
: data (dataIn), numChannels (numChannelsIn) {}
Value** data = nullptr;
int numChannels = 0;
};
/** Sets up `channels` so that it contains channel pointers suitable for passing to
an AudioProcessor's processBlock.
On return, `channels` will hold `max (processorIns, processorOuts)` entries.
The first `processorIns` entries will point to buffers holding input data.
Any entries after the first `processorIns` entries will point to zeroed buffers.
In the case that the system only provides a single input channel, but the processor
has been initialised with multiple input channels, the system input will be copied
to all processor inputs.
In the case that the system provides no input channels, but the processor has
been initialise with multiple input channels, the processor's input channels will
all be zeroed.
@param ins the system inputs.
@param outs the system outputs.
@param numSamples the number of samples in the system buffers.
@param processorIns the number of input channels requested by the processor.
@param processorOuts the number of output channels requested by the processor.
@param tempBuffer temporary storage for inputs that don't have a corresponding output.
@param channels holds pointers to each of the processor's audio channels.
*/
static void initialiseIoBuffers (ChannelInfo<const float> ins,
ChannelInfo<float> outs,
const int numSamples,
int processorIns,
int processorOuts,
AudioBuffer<float>& tempBuffer,
std::vector<float*>& channels)
{
jassert ((int) channels.size() >= jmax (processorIns, processorOuts));
size_t totalNumChans = 0;
const auto numBytes = (size_t) numSamples * sizeof (float);
const auto prepareInputChannel = [&] (int index)
{
if (ins.numChannels == 0)
zeromem (channels[totalNumChans], numBytes);
else
memcpy (channels[totalNumChans], ins.data[index % ins.numChannels], numBytes);
};
if (processorIns > processorOuts)
{
// If there aren't enough output channels for the number of
// inputs, we need to use some temporary extra ones (can't
// use the input data in case it gets written to).
jassert (tempBuffer.getNumChannels() >= processorIns - processorOuts);
jassert (tempBuffer.getNumSamples() >= numSamples);
for (int i = 0; i < processorOuts; ++i)
{
channels[totalNumChans] = outs.data[i];
prepareInputChannel (i);
++totalNumChans;
}
for (auto i = processorOuts; i < processorIns; ++i)
{
channels[totalNumChans] = tempBuffer.getWritePointer (i - outs.numChannels);
prepareInputChannel (i);
++totalNumChans;
}
}
else
{
for (int i = 0; i < processorIns; ++i)
{
channels[totalNumChans] = outs.data[i];
prepareInputChannel (i);
++totalNumChans;
}
for (auto i = processorIns; i < processorOuts; ++i)
{
channels[totalNumChans] = outs.data[i];
zeromem (channels[totalNumChans], (size_t) numSamples * sizeof (float));
++totalNumChans;
}
}
}
//==============================================================================
AudioProcessorPlayer::AudioProcessorPlayer (bool doDoublePrecisionProcessing)
: isDoublePrecision (doDoublePrecisionProcessing)
{
@@ -37,28 +133,64 @@ AudioProcessorPlayer::~AudioProcessorPlayer()
}
//==============================================================================
AudioProcessorPlayer::NumChannels AudioProcessorPlayer::findMostSuitableLayout (const AudioProcessor& proc) const
{
std::vector<NumChannels> layouts { deviceChannels };
if (deviceChannels.ins == 0 || deviceChannels.ins == 1)
{
layouts.emplace_back (defaultProcessorChannels.ins, deviceChannels.outs);
layouts.emplace_back (deviceChannels.outs, deviceChannels.outs);
}
const auto it = std::find_if (layouts.begin(), layouts.end(), [&] (const NumChannels& chans)
{
return proc.checkBusesLayoutSupported (chans.toLayout());
});
return it != std::end (layouts) ? *it : layouts[0];
}
void AudioProcessorPlayer::resizeChannels()
{
const auto maxChannels = jmax (deviceChannels.ins,
deviceChannels.outs,
actualProcessorChannels.ins,
actualProcessorChannels.outs);
channels.resize ((size_t) maxChannels);
tempBuffer.setSize (maxChannels, blockSize);
}
void AudioProcessorPlayer::setProcessor (AudioProcessor* const processorToPlay)
{
if (processor != processorToPlay)
{
if (processorToPlay != nullptr && sampleRate > 0 && blockSize > 0)
{
processorToPlay->setPlayConfigDetails (numInputChans, numOutputChans, sampleRate, blockSize);
defaultProcessorChannels = NumChannels { processorToPlay->getBusesLayout() };
actualProcessorChannels = findMostSuitableLayout (*processorToPlay);
processorToPlay->setPlayConfigDetails (actualProcessorChannels.ins,
actualProcessorChannels.outs,
sampleRate,
blockSize);
bool supportsDouble = processorToPlay->supportsDoublePrecisionProcessing() && isDoublePrecision;
auto supportsDouble = processorToPlay->supportsDoublePrecisionProcessing() && isDoublePrecision;
processorToPlay->setProcessingPrecision (supportsDouble ? AudioProcessor::doublePrecision
: AudioProcessor::singlePrecision);
processorToPlay->prepareToPlay (sampleRate, blockSize);
}
AudioProcessor* oldOne;
AudioProcessor* oldOne = nullptr;
{
const ScopedLock sl (lock);
oldOne = isPrepared ? processor : nullptr;
processor = processorToPlay;
isPrepared = true;
resizeChannels();
}
if (oldOne != nullptr)
@@ -76,7 +208,7 @@ void AudioProcessorPlayer::setDoublePrecisionProcessing (bool doublePrecision)
{
processor->releaseResources();
bool supportsDouble = processor->supportsDoublePrecisionProcessing() && doublePrecision;
auto supportsDouble = processor->supportsDoublePrecisionProcessing() && doublePrecision;
processor->setProcessingPrecision (supportsDouble ? AudioProcessor::doublePrecision
: AudioProcessor::singlePrecision);
@@ -103,53 +235,26 @@ void AudioProcessorPlayer::audioDeviceIOCallback (const float** const inputChann
const int numOutputChannels,
const int numSamples)
{
// these should have been prepared by audioDeviceAboutToStart()...
// These should have been prepared by audioDeviceAboutToStart()...
jassert (sampleRate > 0 && blockSize > 0);
// The processor should be prepared to deal with the same number of output channels
// as our output device.
jassert (processor == nullptr || numOutputChannels == actualProcessorChannels.outs);
incomingMidi.clear();
messageCollector.removeNextBlockOfMessages (incomingMidi, numSamples);
int totalNumChans = 0;
if (numInputChannels > numOutputChannels)
{
// if there aren't enough output channels for the number of
// inputs, we need to create some temporary extra ones (can't
// use the input data in case it gets written to)
tempBuffer.setSize (numInputChannels - numOutputChannels, numSamples,
false, false, true);
initialiseIoBuffers ({ inputChannelData, numInputChannels },
{ outputChannelData, numOutputChannels },
numSamples,
actualProcessorChannels.ins,
actualProcessorChannels.outs,
tempBuffer,
channels);
for (int i = 0; i < numOutputChannels; ++i)
{
channels[totalNumChans] = outputChannelData[i];
memcpy (channels[totalNumChans], inputChannelData[i], (size_t) numSamples * sizeof (float));
++totalNumChans;
}
for (int i = numOutputChannels; i < numInputChannels; ++i)
{
channels[totalNumChans] = tempBuffer.getWritePointer (i - numOutputChannels);
memcpy (channels[totalNumChans], inputChannelData[i], (size_t) numSamples * sizeof (float));
++totalNumChans;
}
}
else
{
for (int i = 0; i < numInputChannels; ++i)
{
channels[totalNumChans] = outputChannelData[i];
memcpy (channels[totalNumChans], inputChannelData[i], (size_t) numSamples * sizeof (float));
++totalNumChans;
}
for (int i = numInputChannels; i < numOutputChannels; ++i)
{
channels[totalNumChans] = outputChannelData[i];
zeromem (channels[totalNumChans], (size_t) numSamples * sizeof (float));
++totalNumChans;
}
}
AudioBuffer<float> buffer (channels, totalNumChans, numSamples);
const auto totalNumChannels = jmax (actualProcessorChannels.ins, actualProcessorChannels.outs);
AudioBuffer<float> buffer (channels.data(), (int) totalNumChannels, numSamples);
{
const ScopedLock sl (lock);
@@ -205,11 +310,11 @@ void AudioProcessorPlayer::audioDeviceAboutToStart (AudioIODevice* const device)
sampleRate = newSampleRate;
blockSize = newBlockSize;
numInputChans = numChansIn;
numOutputChans = numChansOut;
deviceChannels = { numChansIn, numChansOut };
resizeChannels();
messageCollector.reset (sampleRate);
channels.calloc (jmax (numChansIn, numChansOut) + 2);
if (processor != nullptr)
{
@@ -240,4 +345,90 @@ void AudioProcessorPlayer::handleIncomingMidiMessage (MidiInput*, const MidiMess
messageCollector.addMessageToQueue (message);
}
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
struct AudioProcessorPlayerTests : public UnitTest
{
AudioProcessorPlayerTests()
: UnitTest ("AudioProcessorPlayer", UnitTestCategories::audio) {}
void runTest() override
{
struct Layout
{
int numIns, numOuts;
};
const Layout processorLayouts[] { Layout { 0, 0 },
Layout { 1, 1 },
Layout { 4, 4 },
Layout { 4, 8 },
Layout { 8, 4 } };
beginTest ("Buffers are prepared correctly for a variety of channel layouts");
{
for (const auto& layout : processorLayouts)
{
for (const auto numSystemInputs : { 0, 1, layout.numIns })
{
const int numSamples = 256;
const auto systemIns = getTestBuffer (numSystemInputs, numSamples);
auto systemOuts = getTestBuffer (layout.numOuts, numSamples);
AudioBuffer<float> tempBuffer (jmax (layout.numIns, layout.numOuts), numSamples);
std::vector<float*> channels ((size_t) jmax (layout.numIns, layout.numOuts), nullptr);
initialiseIoBuffers ({ systemIns.getArrayOfReadPointers(), systemIns.getNumChannels() },
{ systemOuts.getArrayOfWritePointers(), systemOuts.getNumChannels() },
numSamples,
layout.numIns,
layout.numOuts,
tempBuffer,
channels);
int channelIndex = 0;
for (const auto& channel : channels)
{
const auto value = [&]
{
// Any channels past the number of inputs should be silent.
if (layout.numIns <= channelIndex)
return 0.0f;
// If there's no input, all input channels should be silent.
if (numSystemInputs == 0) return 0.0f;
// If there's one input, all input channels should copy from that input.
if (numSystemInputs == 1) return 1.0f;
// Otherwise, each processor input should match the corresponding system input.
return (float) (channelIndex + 1);
}();
expect (FloatVectorOperations::findMinAndMax (channel, numSamples) == Range<float> (value, value));
channelIndex += 1;
}
}
}
}
}
static AudioBuffer<float> getTestBuffer (int numChannels, int numSamples)
{
AudioBuffer<float> result (numChannels, numSamples);
for (int i = 0; i < result.getNumChannels(); ++i)
FloatVectorOperations::fill (result.getWritePointer (i), (float) i + 1, result.getNumSamples());
return result;
}
};
static AudioProcessorPlayerTests audioProcessorPlayerTests;
#endif
} // namespace juce

+ 23
- 2
libs/juce-current/source/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.h View File

@@ -101,6 +101,27 @@ public:
void handleIncomingMidiMessage (MidiInput*, const MidiMessage&) override;
private:
struct NumChannels
{
NumChannels() = default;
NumChannels (int numIns, int numOuts) : ins (numIns), outs (numOuts) {}
explicit NumChannels (const AudioProcessor::BusesLayout& layout)
: ins (layout.getNumChannels (true, 0)), outs (layout.getNumChannels (false, 0)) {}
AudioProcessor::BusesLayout toLayout() const
{
return { { AudioChannelSet::canonicalChannelSet (ins) },
{ AudioChannelSet::canonicalChannelSet (outs) } };
}
int ins = 0, outs = 0;
};
//==============================================================================
NumChannels findMostSuitableLayout (const AudioProcessor&) const;
void resizeChannels();
//==============================================================================
AudioProcessor* processor = nullptr;
CriticalSection lock;
@@ -108,8 +129,8 @@ private:
int blockSize = 0;
bool isPrepared = false, isDoublePrecision = false;
int numInputChans = 0, numOutputChans = 0;
HeapBlock<float*> channels;
NumChannels deviceChannels, defaultProcessorChannels, actualProcessorChannels;
std::vector<float*> channels;
AudioBuffer<float> tempBuffer;
AudioBuffer<double> conversionBuffer;


+ 14
- 11
libs/juce-current/source/modules/juce_core/native/juce_curl_Network.cpp View File

@@ -110,9 +110,12 @@ private:
class WebInputStream::Pimpl
{
public:
Pimpl (WebInputStream& ownerStream, const URL& urlToCopy, bool shouldUsePost)
: owner (ownerStream), url (urlToCopy), isPost (shouldUsePost),
httpRequest (isPost ? "POST" : "GET")
Pimpl (WebInputStream& ownerStream, const URL& urlToCopy, bool isPOSTLike)
: owner (ownerStream),
url (urlToCopy),
addParametersToRequestBody (isPOSTLike),
hasPOSTData (url.hasPOSTData()),
httpRequest (isPOSTLike || url.hasPOSTData() ? "POST" : "GET")
{
jassert (symbols); // Unable to load libcurl!
@@ -220,7 +223,7 @@ public:
//==============================================================================
bool setOptions()
{
auto address = url.toString (! isPost);
auto address = url.toString (! addParametersToRequestBody);
curl_version_info_data* data = symbols->curl_version_info (CURLVERSION_NOW);
jassert (data != nullptr);
@@ -228,8 +231,8 @@ public:
if (! requestHeaders.endsWithChar ('\n'))
requestHeaders << "\r\n";
if (isPost)
WebInputStream::createHeadersAndPostData (url, requestHeaders, headersAndPostData);
if (hasPOSTData)
WebInputStream::createHeadersAndPostData (url, requestHeaders, headersAndPostData, addParametersToRequestBody);
if (! requestHeaders.endsWithChar ('\n'))
requestHeaders << "\r\n";
@@ -244,7 +247,7 @@ public:
&& symbols->curl_easy_setopt (curl, CURLOPT_USERAGENT, userAgent.toRawUTF8()) == CURLE_OK
&& symbols->curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, (maxRedirects > 0 ? 1 : 0)) == CURLE_OK)
{
if (isPost)
if (hasPOSTData)
{
if (symbols->curl_easy_setopt (curl, CURLOPT_READDATA, this) != CURLE_OK
|| symbols->curl_easy_setopt (curl, CURLOPT_READFUNCTION, StaticCurlRead) != CURLE_OK)
@@ -256,7 +259,7 @@ public:
}
// handle special http request commands
bool hasSpecialRequestCmd = isPost ? (httpRequest != "POST") : (httpRequest != "GET");
const auto hasSpecialRequestCmd = hasPOSTData ? (httpRequest != "POST") : (httpRequest != "GET");
if (hasSpecialRequestCmd)
if (symbols->curl_easy_setopt (curl, CURLOPT_CUSTOMREQUEST, httpRequest.toRawUTF8()) != CURLE_OK)
@@ -323,7 +326,7 @@ public:
listener = webInputListener;
if (isPost)
if (hasPOSTData)
postBuffer = &headersAndPostData;
size_t lastPos = static_cast<size_t> (-1);
@@ -345,7 +348,7 @@ public:
singleStep();
// call callbacks if this is a post request
if (isPost && listener != nullptr && lastPos != postPosition)
if (hasPOSTData && listener != nullptr && lastPos != postPosition)
{
lastPos = postPosition;
@@ -616,7 +619,7 @@ public:
// Options
int timeOutMs = 0;
int maxRedirects = 5;
const bool isPost;
const bool addParametersToRequestBody, hasPOSTData;
String httpRequest;
//==============================================================================


+ 20
- 14
libs/juce-current/source/modules/juce_core/native/juce_linux_Network.cpp View File

@@ -70,10 +70,13 @@ bool JUCE_CALLTYPE Process::openEmailWithAttachments (const String& /* targetEma
class WebInputStream::Pimpl
{
public:
Pimpl (WebInputStream& pimplOwner, const URL& urlToCopy, bool shouldUsePost)
: owner (pimplOwner), url (urlToCopy),
isPost (shouldUsePost), httpRequestCmd (shouldUsePost ? "POST" : "GET")
{}
Pimpl (WebInputStream& pimplOwner, const URL& urlToCopy, bool isPOSTLike)
: owner (pimplOwner),
url (urlToCopy),
addParametersToRequestBody (isPOSTLike),
httpRequestCmd (isPOSTLike || url.hasPOSTData() ? "POST" : "GET")
{
}
~Pimpl()
{
@@ -127,7 +130,7 @@ public:
return false;
}
address = url.toString (! isPost);
address = url.toString (! addParametersToRequestBody);
statusCode = createConnection (listener, numRedirectsToFollow);
return statusCode != 0;
@@ -256,7 +259,7 @@ private:
MemoryBlock postData;
int64 contentLength = -1, position = 0;
bool finished = false;
const bool isPost;
const bool addParametersToRequestBody;
int timeOutMs = 0;
int numRedirectsToFollow = 5;
String httpRequestCmd;
@@ -285,8 +288,8 @@ private:
{
closeSocket (false);
if (isPost)
WebInputStream::createHeadersAndPostData (url, headers, postData);
if (url.hasPOSTData())
WebInputStream::createHeadersAndPostData (url, headers, postData, addParametersToRequestBody);
auto timeOutTime = Time::getMillisecondCounter();
@@ -367,8 +370,8 @@ private:
freeaddrinfo (result);
{
const MemoryBlock requestHeader (createRequestHeader (hostName, hostPort, proxyName, proxyPort, hostPath,
address, headers, postData, isPost, httpRequestCmd));
const MemoryBlock requestHeader (createRequestHeader (hostName, hostPort, proxyName, proxyPort, hostPath, address,
headers, postData, httpRequestCmd));
if (! sendHeader (socketHandle, requestHeader, timeOutTime, owner, listener))
{
@@ -474,7 +477,7 @@ private:
const String& proxyName, int proxyPort,
const String& hostPath, const String& originalURL,
const String& userHeaders, const MemoryBlock& postData,
bool isPost, const String& httpRequestCmd)
const String& httpRequestCmd)
{
MemoryOutputStream header;
@@ -488,15 +491,18 @@ private:
"." JUCE_STRINGIFY(JUCE_BUILDNUMBER));
writeValueIfNotPresent (header, userHeaders, "Connection:", "close");
if (isPost)
writeValueIfNotPresent (header, userHeaders, "Content-Length:", String ((int) postData.getSize()));
const auto postDataSize = postData.getSize();
const auto hasPostData = postDataSize > 0;
if (hasPostData)
writeValueIfNotPresent (header, userHeaders, "Content-Length:", String ((int) postDataSize));
if (userHeaders.isNotEmpty())
header << "\r\n" << userHeaders;
header << "\r\n\r\n";
if (isPost)
if (hasPostData)
header << postData;
return header.getMemoryBlock();


+ 9
- 7
libs/juce-current/source/modules/juce_core/native/juce_mac_Network.mm View File

@@ -943,9 +943,11 @@ JUCE_END_IGNORE_WARNINGS_GCC_LIKE
class WebInputStream::Pimpl
{
public:
Pimpl (WebInputStream& pimplOwner, const URL& urlToUse, bool shouldBePost)
: owner (pimplOwner), url (urlToUse), isPost (shouldBePost),
httpRequestCmd (shouldBePost ? "POST" : "GET")
Pimpl (WebInputStream& pimplOwner, const URL& urlToUse, bool isPOSTLike)
: owner (pimplOwner),
url (urlToUse),
addParametersToRequestBody (isPOSTLike),
httpRequestCmd (isPOSTLike || url.hasPOSTData() ? "POST" : "GET")
{
}
@@ -1089,7 +1091,7 @@ private:
MemoryBlock postData;
int64 position = 0;
bool finished = false;
const bool isPost;
const bool addParametersToRequestBody;
int timeOutMs = 0;
int numRedirectsToFollow = 5;
String httpRequestCmd;
@@ -1101,7 +1103,7 @@ private:
{
jassert (connection == nullptr);
if (NSURL* nsURL = [NSURL URLWithString: juceStringToNS (url.toString (! isPost))])
if (NSURL* nsURL = [NSURL URLWithString: juceStringToNS (url.toString (! addParametersToRequestBody))])
{
if (NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL: nsURL
cachePolicy: NSURLRequestReloadIgnoringLocalCacheData
@@ -1111,9 +1113,9 @@ private:
{
[req setHTTPMethod: httpMethod];
if (isPost)
if (url.hasPOSTData())
{
WebInputStream::createHeadersAndPostData (url, headers, postData);
WebInputStream::createHeadersAndPostData (url, headers, postData, addParametersToRequestBody);
if (postData.getSize() > 0)
[req setHTTPBody: [NSData dataWithBytes: postData.getData()


+ 12
- 9
libs/juce-current/source/modules/juce_core/native/juce_win32_Network.cpp View File

@@ -35,10 +35,13 @@ namespace juce
class WebInputStream::Pimpl
{
public:
Pimpl (WebInputStream& pimplOwner, const URL& urlToCopy, bool shouldBePost)
: statusCode (0), owner (pimplOwner), url (urlToCopy), isPost (shouldBePost),
httpRequestCmd (isPost ? "POST" : "GET")
{}
Pimpl (WebInputStream& pimplOwner, const URL& urlToCopy, bool isPOSTLike)
: owner (pimplOwner),
url (urlToCopy),
addParametersToRequestBody (isPOSTLike),
httpRequestCmd (isPOSTLike || url.hasPOSTData() ? "POST" : "GET")
{
}
~Pimpl()
{
@@ -75,7 +78,7 @@ public:
return false;
}
String address = url.toString (! isPost);
auto address = url.toString (! addParametersToRequestBody);
while (numRedirectsToFollow-- >= 0)
{
@@ -226,7 +229,7 @@ public:
return true;
}
int statusCode;
int statusCode = 0;
private:
//==============================================================================
@@ -237,7 +240,7 @@ private:
MemoryBlock postData;
int64 position = 0;
bool finished = false;
const bool isPost;
const bool addParametersToRequestBody;
int timeOutMs = 0;
String httpRequestCmd;
int numRedirectsToFollow = 5;
@@ -288,8 +291,8 @@ private:
uc.lpszPassword = password;
uc.dwPasswordLength = passwordNumChars;
if (isPost)
WebInputStream::createHeadersAndPostData (url, headers, postData);
if (url.hasPOSTData())
WebInputStream::createHeadersAndPostData (url, headers, postData, addParametersToRequestBody);
if (InternetCrackUrl (address.toWideCharPointer(), 0, 0, &uc))
openConnection (uc, sessionHandle, address, listener);


+ 132
- 43
libs/juce-current/source/modules/juce_core/network/juce_URL.cpp View File

@@ -377,6 +377,11 @@ String URL::getFileName() const
}
#endif
URL::ParameterHandling URL::toHandling (bool usePostData)
{
return usePostData ? ParameterHandling::inPostData : ParameterHandling::inAddress;
}
File URL::fileFromFileSchemeURL (const URL& fileURL)
{
if (! fileURL.isLocalFile())
@@ -447,7 +452,9 @@ URL URL::getChildURL (const String& subPath) const
return u;
}
void URL::createHeadersAndPostData (String& headers, MemoryBlock& postDataToWrite) const
void URL::createHeadersAndPostData (String& headers,
MemoryBlock& postDataToWrite,
bool addParametersToBody) const
{
MemoryOutputStream data (postDataToWrite, false);
@@ -491,8 +498,10 @@ void URL::createHeadersAndPostData (String& headers, MemoryBlock& postDataToWrit
}
else
{
data << URLHelpers::getMangledParameters (*this)
<< postData;
if (addParametersToBody)
data << URLHelpers::getMangledParameters (*this) << postData;
else
data << postData;
// if the user-supplied headers didn't contain a content-type, add one now..
if (! headers.containsIgnoreCase ("Content-Type"))
@@ -656,73 +665,127 @@ private:
}
};
#endif
//==============================================================================
template <typename Member, typename Item>
static URL::InputStreamOptions with (URL::InputStreamOptions options, Member&& member, Item&& item)
{
options.*member = std::forward<Item> (item);
return options;
}
URL::InputStreamOptions::InputStreamOptions (ParameterHandling handling) : parameterHandling (handling) {}
URL::InputStreamOptions URL::InputStreamOptions::withProgressCallback (std::function<bool (int, int)> cb) const
{
return with (*this, &InputStreamOptions::progressCallback, std::move (cb));
}
URL::InputStreamOptions URL::InputStreamOptions::withExtraHeaders (const String& headers) const
{
return with (*this, &InputStreamOptions::extraHeaders, headers);
}
URL::InputStreamOptions URL::InputStreamOptions::withConnectionTimeoutMs (int timeout) const
{
return with (*this, &InputStreamOptions::connectionTimeOutMs, timeout);
}
URL::InputStreamOptions URL::InputStreamOptions::withResponseHeaders (StringPairArray* headers) const
{
return with (*this, &InputStreamOptions::responseHeaders, headers);
}
URL::InputStreamOptions URL::InputStreamOptions::withStatusCode (int* status) const
{
return with (*this, &InputStreamOptions::statusCode, status);
}
URL::InputStreamOptions URL::InputStreamOptions::withNumRedirectsToFollow (int numRedirects) const
{
return with (*this, &InputStreamOptions::numRedirectsToFollow, numRedirects);
}
URL::InputStreamOptions URL::InputStreamOptions::withHttpRequestCmd (const String& cmd) const
{
return with (*this, &InputStreamOptions::httpRequestCmd, cmd);
}
//==============================================================================
std::unique_ptr<InputStream> URL::createInputStream (bool usePostCommand,
OpenStreamProgressCallback* progressCallback,
void* progressCallbackContext,
String headers,
int timeOutMs,
StringPairArray* responseHeaders,
int* statusCode,
int numRedirectsToFollow,
String httpRequestCmd) const
std::unique_ptr<InputStream> URL::createInputStream (const InputStreamOptions& options) const
{
if (isLocalFile())
{
#if JUCE_IOS
// We may need to refresh the embedded bookmark.
return std::make_unique<iOSFileStreamWrapper<FileInputStream>> (const_cast<URL&>(*this));
return std::make_unique<iOSFileStreamWrapper<FileInputStream>> (const_cast<URL&> (*this));
#else
return getLocalFile().createInputStream();
#endif
}
auto wi = std::make_unique<WebInputStream> (*this, usePostCommand);
auto webInputStream = [&]
{
const auto usePost = options.getParameterHandling() == ParameterHandling::inPostData;
auto stream = std::make_unique<WebInputStream> (*this, usePost);
auto extraHeaders = options.getExtraHeaders();
if (extraHeaders.isNotEmpty())
stream->withExtraHeaders (extraHeaders);
auto timeout = options.getConnectionTimeoutMs();
if (timeout != 0)
stream->withConnectionTimeout (timeout);
auto requestCmd = options.getHttpRequestCmd();
if (requestCmd.isNotEmpty())
stream->withCustomRequestCommand (requestCmd);
stream->withNumRedirectsToFollow (options.getNumRedirectsToFollow());
return stream;
}();
struct ProgressCallbackCaller : public WebInputStream::Listener
{
ProgressCallbackCaller (OpenStreamProgressCallback* progressCallbackToUse, void* progressCallbackContextToUse)
: callback (progressCallbackToUse), data (progressCallbackContextToUse)
{}
ProgressCallbackCaller (std::function<bool (int, int)> progressCallbackToUse)
: callback (std::move (progressCallbackToUse))
{
}
bool postDataSendProgress (WebInputStream&, int bytesSent, int totalBytes) override
{
return callback (data, bytesSent, totalBytes);
return callback (bytesSent, totalBytes);
}
OpenStreamProgressCallback* callback;
void* const data;
std::function<bool (int, int)> callback;
};
std::unique_ptr<ProgressCallbackCaller> callbackCaller
(progressCallback != nullptr ? new ProgressCallbackCaller (progressCallback, progressCallbackContext) : nullptr);
if (headers.isNotEmpty())
wi->withExtraHeaders (headers);
if (timeOutMs != 0)
wi->withConnectionTimeout (timeOutMs);
if (httpRequestCmd.isNotEmpty())
wi->withCustomRequestCommand (httpRequestCmd);
auto callbackCaller = [&options]() -> std::unique_ptr<ProgressCallbackCaller>
{
if (auto progressCallback = options.getProgressCallback())
return std::make_unique<ProgressCallbackCaller> (progressCallback);
wi->withNumRedirectsToFollow (numRedirectsToFollow);
return {};
}();
bool success = wi->connect (callbackCaller.get());
auto success = webInputStream->connect (callbackCaller.get());
if (statusCode != nullptr)
*statusCode = wi->getStatusCode();
if (auto* status = options.getStatusCode())
*status = webInputStream->getStatusCode();
if (responseHeaders != nullptr)
*responseHeaders = wi->getResponseHeaders();
if (auto* responseHeaders = options.getResponseHeaders())
*responseHeaders = webInputStream->getResponseHeaders();
if (! success || wi->isError())
if (! success || webInputStream->isError())
return nullptr;
// Older GCCs complain about binding unique_ptr<WebInputStream>&& to unique_ptr<InputStream>
// if we just `return wi` here.
return std::unique_ptr<InputStream> (std::move (wi));
// std::move() needed here for older compilers
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wredundant-move")
return std::move (webInputStream);
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
#if JUCE_ANDROID
@@ -752,7 +815,7 @@ std::unique_ptr<OutputStream> URL::createOutputStream() const
bool URL::readEntireBinaryStream (MemoryBlock& destData, bool usePostCommand) const
{
const std::unique_ptr<InputStream> in (isLocalFile() ? getLocalFile().createInputStream()
: createInputStream (usePostCommand));
: createInputStream (InputStreamOptions (toHandling (usePostCommand))));
if (in != nullptr)
{
@@ -766,7 +829,7 @@ bool URL::readEntireBinaryStream (MemoryBlock& destData, bool usePostCommand) co
String URL::readEntireTextStream (bool usePostCommand) const
{
const std::unique_ptr<InputStream> in (isLocalFile() ? getLocalFile().createInputStream()
: createInputStream (usePostCommand));
: createInputStream (InputStreamOptions (toHandling (usePostCommand))));
if (in != nullptr)
return in->readEntireStreamAsString();
@@ -911,4 +974,30 @@ bool URL::launchInDefaultBrowser() const
return Process::openDocument (u, {});
}
//==============================================================================
std::unique_ptr<InputStream> URL::createInputStream (bool usePostCommand,
OpenStreamProgressCallback* cb,
void* context,
String headers,
int timeOutMs,
StringPairArray* responseHeaders,
int* statusCode,
int numRedirectsToFollow,
String httpRequestCmd) const
{
std::function<bool (int, int)> callback;
if (cb != nullptr)
callback = [context, cb] (int sent, int total) { return cb (context, sent, total); };
return createInputStream (InputStreamOptions (toHandling (usePostCommand))
.withProgressCallback (std::move (callback))
.withExtraHeaders (headers)
.withConnectionTimeoutMs (timeOutMs)
.withResponseHeaders (responseHeaders)
.withStatusCode (statusCode)
.withNumRedirectsToFollow(numRedirectsToFollow)
.withHttpRequestCmd (httpRequestCmd));
}
} // namespace juce

+ 239
- 132
libs/juce-current/source/modules/juce_core/network/juce_URL.h View File

@@ -30,7 +30,7 @@ class WebInputStream;
Represents a URL and has a bunch of useful functions to manipulate it.
This class can be used to launch URLs in browsers, and also to create
InputStreams that can read from remote http or ftp sources.
InputStreams that can read from remote HTTP or FTP sources.
@tags{Core}
*/
@@ -42,24 +42,21 @@ public:
URL();
/** Creates a URL from a string.
This will parse any embedded parameters after a '?' character and store them
in the list (see getParameterNames etc). If you don't want this to happen, you
can use createWithoutParsing().
*/
URL (const String& url);
URL (const URL&) = default;
URL& operator= (const URL&) = default;
URL (URL&&) = default;
URL& operator= (URL&&) = default;
/** Creates URL referring to a local file on your disk using the file:// scheme. */
explicit URL (File);
explicit URL (File localFile);
/** Destructor. */
~URL() = default;
/** Compares two URLs.
All aspects of the URLs must be identical for them to match, including any parameters,
upload files, etc.
*/
@@ -69,9 +66,11 @@ public:
//==============================================================================
/** Returns a string version of the URL.
If includeGetParameters is true and any parameters have been set with the
withParameter() method, then the string will have these appended on the
end and url-encoded.
@param includeGetParameters if this is true and any parameters have been set
with the withParameter() method, then the string
will have these appended on the end and URL-encoded.
@see getQueryString
*/
String toString (bool includeGetParameters) const;
@@ -82,26 +81,31 @@ public:
bool isWellFormed() const;
/** Returns just the domain part of the URL.
E.g. for "http://www.xyz.com/foobar", this will return "www.xyz.com".
e.g. for "http://www.xyz.com/foobar", this will return "www.xyz.com".
*/
String getDomain() const;
/** Returns the path part of the URL.
E.g. for "http://www.xyz.com/foo/bar?x=1", this will return "foo/bar".
If includeGetParameters is true and any parameters have been set with the
withParameter() method, then the string will have these appended on the
end and url-encoded.
e.g. for "http://www.xyz.com/foo/bar?x=1", this will return "foo/bar".
@param includeGetParameters if this is true and any parameters have been set
with the withParameter() method, then the string
will have these appended on the end and URL-encoded.
@see getQueryString
*/
String getSubPath (bool includeGetParameters = false) const;
/** If any parameters are set, returns these URL encoded, including the "?"
* prefix.
/** If any parameters are set, returns these URL-encoded, including the "?"
prefix.
*/
String getQueryString() const;
/** Returns the scheme of the URL.
E.g. for "http://www.xyz.com/foobar", this will return "http". (It won't
e.g. for "http://www.xyz.com/foobar", this will return "http" (it won't
include the colon).
*/
String getScheme() const;
@@ -110,19 +114,20 @@ public:
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.
This method also supports converting Android's content:// URLs to local file paths.
@see isLocalFile
*/
File getLocalFile() const;
/** Returns the file name. For all but Android's content:// scheme, it will
simply return the last segment of the URL.
E.g. for "http://www.xyz.com/foo/bar.txt", this will return "bar.txt".
/** Returns the file name.
For all but Android's content:// scheme, it will simply return the last segment of
the URL, e.g. for "http://www.xyz.com/foo/bar.txt", this will return "bar.txt".
For Android's content:// scheme, it will attempt to resolve the filename
located under the URL.
@@ -130,36 +135,44 @@ public:
String getFileName() const;
/** Attempts to read a port number from the URL.
@returns the port number, or 0 if none is explicitly specified.
*/
int getPort() const;
/** Returns a new version of this URL with a different domain and path.
E.g. if the URL is "http://www.xyz.com/foo?x=1" and you call this with
e.g. if the URL is "http://www.xyz.com/foo?x=1" and you call this with
"abc.com/zzz", it'll return "http://abc.com/zzz?x=1".
@see withNewSubPath
*/
URL withNewDomainAndPath (const String& newFullPath) const;
/** Returns a new version of this URL with a different sub-path.
E.g. if the URL is "http://www.xyz.com/foo?x=1" and you call this with
e.g. if the URL is "http://www.xyz.com/foo?x=1" and you call this with
"bar", it'll return "http://www.xyz.com/bar?x=1".
@see withNewDomainAndPath
*/
URL withNewSubPath (const String& newPath) const;
/** Attempts to return a URL which is the parent folder containing this URL.
If there isn't a parent, this method will just return a copy of this URL.
*/
URL getParentURL() const;
/** Returns a new URL that refers to a sub-path relative to this one.
E.g. if the URL is "http://www.xyz.com/foo" and you call this with
"bar", it'll return "http://www.xyz.com/foo/bar". Note that there's no way for
this method to know whether the original URL is a file or directory, so it's
up to you to make sure it's a directory. It also won't attempt to be smart about
the content of the childPath string, so if this string is an absolute URL, it'll
still just get bolted onto the end of the path.
e.g. if the URL is "http://www.xyz.com/foo" and you call this with "bar",
it'll return "http://www.xyz.com/foo/bar".
Note that there's no way for this method to know whether the original URL is
a file or directory, so it's up to you to make sure it's a directory. It also
won't attempt to be smart about the content of the childPath string, so if this
string is an absolute URL, it'll still just get bolted onto the end of the path.
@see File::getChildFile
*/
@@ -168,9 +181,10 @@ public:
//==============================================================================
/** Returns a copy of this URL, with a GET or POST parameter added to the end.
Any control characters in the value will be encoded.
Any control characters in the value will be URL-encoded.
e.g. calling "withParameter ("amount", "some fish") for the url "www.fish.com"
would produce a new url whose toString(true) method would return
would produce a new url whose `toString (true)` method would return
"www.fish.com?amount=some+fish".
@see getParameterNames, getParameterValues
@@ -179,7 +193,9 @@ public:
const String& parameterValue) const;
/** Returns a copy of this URL, with a set of GET or POST parameters added.
This is a convenience method, equivalent to calling withParameter for each value.
@see withParameter
*/
URL withParameters (const StringPairArray& parametersToAdd) const;
@@ -203,6 +219,7 @@ public:
When performing a POST where one of your parameters is a binary file, this
lets you specify the file content.
Note that the filename parameter should not be a full path, it's just the
last part of the filename.
@@ -215,7 +232,7 @@ public:
/** Returns an array of the names of all the URL's parameters.
E.g. for the url "www.fish.com?type=haddock&amount=some+fish", this array would
e.g. for the url "www.fish.com?type=haddock&amount=some+fish", this array would
contain two items: "type" and "amount".
You can call getParameterValues() to get the corresponding value of each
@@ -227,7 +244,7 @@ public:
/** Returns an array of the values of all the URL's parameters.
E.g. for the url "www.fish.com?type=haddock&amount=some+fish", this array would
e.g. for the url "www.fish.com?type=haddock&amount=some+fish", this array would
contain two items: "haddock" and "some fish".
The values returned will have been cleaned up to remove any escape characters.
@@ -241,41 +258,37 @@ public:
/** Returns a copy of this URL, with a block of data to send as the POST data.
If you're setting the POST data, be careful not to have any parameters set
as well, otherwise it'll all get thrown in together, and might not have the
desired effect.
If the URL already contains some POST data, this will replace it, rather
than being appended to it.
This data will only be used if you specify a post operation when you call
createInputStream().
If no HTTP command is set when calling createInputStream() to read from
this URL and some data has been set, it will do a POST request.
*/
URL withPOSTData (const String& postData) const;
/** Returns a copy of this URL, with a block of data to send as the POST data.
If you're setting the POST data, be careful not to have any parameters set
as well, otherwise it'll all get thrown in together, and might not have the
desired effect.
If the URL already contains some POST data, this will replace it, rather
than being appended to it.
This data will only be used if you specify a post operation when you call
createInputStream().
If no HTTP command is set when calling createInputStream() to read from
this URL and some data has been set, it will do a POST request.
*/
URL withPOSTData (const MemoryBlock& postData) const;
/** Returns the data that was set using withPOSTData(). */
String getPostData() const { return postData.toString(); }
/** Returns the data that was set using withPOSTData() as MemoryBlock. */
/** Returns the data that was set using withPOSTData() as a MemoryBlock. */
const MemoryBlock& getPostDataAsMemoryBlock() const noexcept { return postData; }
/** Returns true if this URL has POST data set using withPOSTData(). */
bool hasPOSTData() const noexcept { return postData.getSize() > 0; }
//==============================================================================
/** Tries to launch the system's default browser to open the URL.
Returns true if this seems to have worked.
@returns true if this seems to have worked.
*/
bool launchInDefaultBrowser() const;
@@ -290,13 +303,103 @@ public:
*/
static bool isProbablyAnEmailAddress (const String& possibleEmailAddress);
//==============================================================================
/** This callback function can be used by the createInputStream() method.
enum class ParameterHandling
{
inAddress,
inPostData
};
It allows your app to receive progress updates during a lengthy POST operation. If you
want to continue the operation, this should return true, or false to abort.
//==============================================================================
/** Class used to create a set of options to pass to the createInputStream() method.
You can chain together a series of calls to this class's methods to create
a set of whatever options you want to specify, e.g.
@code
if (auto inputStream = URL ("http://www.xyz.com/foobar")
.createInputStream (URL::InputStreamOptions (false).withConnectionTimeoutMs (1000)
.withNumRedirectsToFollow (0)))
{
...
}
@endcode
*/
using OpenStreamProgressCallback = bool (void* context, int bytesSent, int totalBytes);
class JUCE_API InputStreamOptions
{
public:
/** Constructor.
If parameterHandling is ParameterHandling::inPostData and the URL contains some
POST data to send set via one of its withPOSTData() methods, the URL parameters
will be transferred via the request body data. Otherwise the parameters will
be added to the URL address.
*/
explicit InputStreamOptions (ParameterHandling parameterHandling);
//==============================================================================
/** A callback function to keep track of the operation's progress.
This can be useful for lengthy POST operations, so that you can provide user feedback.
*/
InputStreamOptions withProgressCallback (std::function<bool (int bytesSent, int totalBytes)> progressCallback) const;
/** A string that will be appended onto the headers that are used for the request.
It must be a valid set of HTML header directives, separated by newlines.
*/
InputStreamOptions withExtraHeaders (const String& extraHeaders) const;
/** Specifies a timeout for the request in milliseconds.
If 0, this will use whatever default setting the OS chooses. If a negative
number, it will be infinite.
*/
InputStreamOptions withConnectionTimeoutMs (int connectionTimeoutMs) const;
/** If this is non-null, all the (key, value) pairs received as headers
in the response will be stored in this array.
*/
InputStreamOptions withResponseHeaders (StringPairArray* responseHeaders) const;
/** If this is non-null, it will get set to the http status code, if one
is known, or 0 if a code isn't available.
*/
InputStreamOptions withStatusCode (int* statusCode) const;
/** Specifies the number of redirects that will be followed before returning a response.
N.B. This will be ignored on Android which follows up to 5 redirects.
*/
InputStreamOptions withNumRedirectsToFollow (int numRedirectsToFollow) const;
/** Specifies which HTTP request to use.
If this is not set, then this will be determined by the value of `doPostLikeRequest`
or the presence of POST data set via URL::withPOSTData().
*/
InputStreamOptions withHttpRequestCmd (const String& httpRequestCmd) const;
//==============================================================================
ParameterHandling getParameterHandling() const noexcept { return parameterHandling; }
std::function<bool (int, int)> getProgressCallback() const noexcept { return progressCallback; }
String getExtraHeaders() const noexcept { return extraHeaders; }
int getConnectionTimeoutMs() const noexcept { return connectionTimeOutMs; }
StringPairArray* getResponseHeaders() const noexcept { return responseHeaders; }
int* getStatusCode() const noexcept { return statusCode; }
int getNumRedirectsToFollow() const noexcept { return numRedirectsToFollow; }
String getHttpRequestCmd() const noexcept { return httpRequestCmd; }
private:
//==============================================================================
const ParameterHandling parameterHandling;
std::function<bool (int, int)> progressCallback = nullptr;
String extraHeaders;
int connectionTimeOutMs = 0;
StringPairArray* responseHeaders = nullptr;
int* statusCode = nullptr;
int numRedirectsToFollow = 5;
String httpRequestCmd;
};
/** Attempts to open a stream that can read from this URL.
@@ -312,42 +415,11 @@ public:
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,
if the parameter httpRequestCmd is not specified (or empty) then this
parameter will determine which HTTP request command will be used
(POST or GET).
@param progressCallback if this is not a nullptr, it lets you supply a callback function
to keep track of the operation's progress. This can be useful
for lengthy POST operations, so that you can provide user feedback.
@param progressCallbackContext if a callback is specified, this value will be passed to
the function
@param extraHeaders if not empty, this string is appended onto the headers that
are used for the request. It must therefore be a valid set of HTML
header directives, separated by newlines.
@param connectionTimeOutMs if 0, this will use whatever default setting the OS chooses. If
a negative number, it will be infinite. Otherwise it specifies a
time in milliseconds.
@param responseHeaders if this is non-null, all the (key, value) pairs received as headers
in the response will be stored in this array
@param statusCode if this is non-null, it will get set to the http status code, if one
is known, or 0 if a code isn't available
@param numRedirectsToFollow specifies the number of redirects that will be followed before
returning a response (ignored for Android which follows up to 5 redirects)
@param httpRequestCmd Specify which HTTP Request to use. If this is empty, then doPostRequest
will determine the HTTP request.
@returns a valid input stream, or nullptr if there was an error trying to open it.
*/
std::unique_ptr<InputStream> createInputStream (bool doPostLikeRequest,
OpenStreamProgressCallback* progressCallback = nullptr,
void* progressCallbackContext = nullptr,
String extraHeaders = {},
int connectionTimeOutMs = 0,
StringPairArray* responseHeaders = nullptr,
int* statusCode = nullptr,
int numRedirectsToFollow = 5,
String httpRequestCmd = {}) const;
@param options a set of options that will be used when opening the stream.
@returns a valid input stream, or nullptr if there was an error trying to open it.
*/
std::unique_ptr<InputStream> createInputStream (const InputStreamOptions& options) const;
/** Attempts to open an output stream to a URL for writing
@@ -358,32 +430,38 @@ public:
//==============================================================================
/** Represents a download task.
Returned by downloadToFile to allow querying and controlling the download task.
Returned by downloadToFile() to allow querying and controlling the download task.
*/
class JUCE_API DownloadTask
{
public:
/** Used to receive callbacks for download progress */
/** Used to receive callbacks for download progress. */
struct JUCE_API Listener
{
virtual ~Listener();
/** Called when the download has finished. Be aware that this callback may
come on an arbitrary thread. */
come on an arbitrary thread.
*/
virtual void finished (URL::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 (URL::DownloadTask* task, int64 bytesDownloaded, int64 totalLength);
};
/** Releases the resources of the download task, unregisters the listener
and cancels the download if necessary. */
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. */
/** Returns the total length of the download task.
This may return -1 if the length was not returned by the server.
*/
int64 getTotalLength() const { return contentLength; }
/** Returns the number of bytes that have been downloaded so far. */
@@ -393,7 +471,9 @@ public:
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
*/
int statusCode() const { return httpCode; }
@@ -449,9 +529,10 @@ public:
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.
@param destData the memory block to append the new data to
@param usePostCommand whether to use a POST command to get the data (uses
a GET command if this is false)
@param destData the memory block to append the new data to.
@param usePostCommand whether to use a POST command to get the data (uses
a GET command if this is false).
@see readEntireTextStream, readEntireXmlStream
*/
bool readEntireBinaryStream (MemoryBlock& destData,
@@ -467,8 +548,9 @@ public:
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.
@param usePostCommand whether to use a POST command to get the data (uses
a GET command if this is false)
@param usePostCommand whether to use a POST command to get the data (uses
a GET command if this is false).
@see readEntireBinaryStream, readEntireXmlStream
*/
String readEntireTextStream (bool usePostCommand = false) const;
@@ -481,8 +563,8 @@ public:
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.
@param usePostCommand whether to use a POST command to get the data (uses
a GET command if this is false)
@param usePostCommand whether to use a POST command to get the data (uses
a GET command if this is false).
@see readEntireBinaryStream, readEntireTextStream
*/
@@ -496,14 +578,14 @@ public:
This is the opposite of removeEscapeChars().
@param stringToAddEscapeCharsTo The string to escape.
@param isParameter If true then the string is going to be
used as a parameter, so it also encodes
'$' and ',' (which would otherwise be
legal in a URL.
@param roundBracketsAreLegal Technically round brackets are ok in URLs,
however, some servers (like AWS) also want
round brackets to be escaped.
@param stringToAddEscapeCharsTo the string to escape.
@param isParameter if true then the string is going to be
used as a parameter, so it also encodes
'$' and ',' (which would otherwise be
legal in a URL.
@param roundBracketsAreLegal technically round brackets are ok in URLs,
however, some servers (like AWS) also want
round brackets to be escaped.
@see removeEscapeChars
*/
@@ -523,35 +605,34 @@ public:
static String removeEscapeChars (const String& stringToRemoveEscapeCharsFrom);
/** Returns a URL without attempting to remove any embedded parameters from the string.
This may be necessary if you need to create a request that involves both POST
parameters and parameters which are embedded in the URL address itself.
*/
static URL createWithoutParsing (const String& url);
private:
//==============================================================================
friend class WebInputStream;
String url;
MemoryBlock postData;
StringArray parameterNames, parameterValues;
using OpenStreamProgressCallback = bool (void* context, int bytesSent, int totalBytes);
static File fileFromFileSchemeURL (const URL&);
String getDomainInternal (bool) const;
/** This method has been deprecated.
struct Upload : public ReferenceCountedObject
{
Upload (const String&, const String&, const String&, const File&, MemoryBlock*);
String parameterName, filename, mimeType;
File file;
std::unique_ptr<MemoryBlock> data;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Upload)
};
New code should use the method which takes an InputStreamOptions argument instead.
ReferenceCountedArray<Upload> filesToUpload;
@see InputStreamOptions
*/
std::unique_ptr<InputStream> createInputStream (bool doPostLikeRequest,
OpenStreamProgressCallback* progressCallback = nullptr,
void* progressCallbackContext = nullptr,
String extraHeaders = {},
int connectionTimeOutMs = 0,
StringPairArray* responseHeaders = nullptr,
int* statusCode = nullptr,
int numRedirectsToFollow = 5,
String httpRequestCmd = {}) const;
#if JUCE_IOS
private:
//==============================================================================
#if JUCE_IOS
struct Bookmark : public ReferenceCountedObject
{
using Ptr = ReferenceCountedObjectPtr<Bookmark>;
@@ -566,14 +647,40 @@ private:
friend void setURLBookmark (URL&, void*);
friend void* getURLBookmark (URL&);
#endif
#endif
//==============================================================================
struct Upload : public ReferenceCountedObject
{
Upload (const String&, const String&, const String&, const File&, MemoryBlock*);
String parameterName, filename, mimeType;
File file;
std::unique_ptr<MemoryBlock> data;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Upload)
};
//==============================================================================
friend class WebInputStream;
URL (const String&, int);
void init();
void addParameter (const String&, const String&);
void createHeadersAndPostData (String&, MemoryBlock&) const;
void createHeadersAndPostData (String&, MemoryBlock&, bool) const;
URL withUpload (Upload*) const;
static ParameterHandling toHandling (bool);
static File fileFromFileSchemeURL (const URL&);
String getDomainInternal (bool) const;
//==============================================================================
String url;
MemoryBlock postData;
StringArray parameterNames, parameterValues;
ReferenceCountedArray<Upload> filesToUpload;
//==============================================================================
JUCE_LEAK_DETECTOR (URL)
};


+ 22
- 10
libs/juce-current/source/modules/juce_core/network/juce_WebInputStream.cpp View File

@@ -24,13 +24,12 @@ namespace juce
{
WebInputStream::WebInputStream (const URL& url, const bool usePost)
: pimpl (new Pimpl (*this, url, usePost)), hasCalledConnect (false)
: pimpl (std::make_unique<Pimpl> (*this, url, usePost))
{
}
WebInputStream::~WebInputStream()
{
delete pimpl;
}
WebInputStream& WebInputStream::withExtraHeaders (const String& extra) { pimpl->withExtraHeaders (extra); return *this; }
@@ -60,28 +59,41 @@ bool WebInputStream::connect (Listener* listener)
StringPairArray WebInputStream::parseHttpHeaders (const String& headerData)
{
StringPairArray headerPairs;
StringArray headerLines = StringArray::fromLines (headerData);
auto headerLines = StringArray::fromLines (headerData);
// ignore the first line as this is the status line
for (int i = 1; i < headerLines.size(); ++i)
{
const String& headersEntry = headerLines[i];
const auto& headersEntry = headerLines[i];
if (headersEntry.isNotEmpty())
{
const String key (headersEntry.upToFirstOccurrenceOf (": ", false, false));
const String value (headersEntry.fromFirstOccurrenceOf (": ", false, false));
const String previousValue (headerPairs [key]);
headerPairs.set (key, previousValue.isEmpty() ? value : (previousValue + "," + value));
const auto key = headersEntry.upToFirstOccurrenceOf (": ", false, false);
auto value = [&headersEntry, &headerPairs, &key]
{
const auto currentValue = headersEntry.fromFirstOccurrenceOf (": ", false, false);
const auto previousValue = headerPairs [key];
if (previousValue.isNotEmpty())
return previousValue + "," + currentValue;
return currentValue;
}();
headerPairs.set (key, value);
}
}
return headerPairs;
}
void WebInputStream::createHeadersAndPostData (const URL& aURL, String& headers, MemoryBlock& data)
void WebInputStream::createHeadersAndPostData (const URL& aURL,
String& headers,
MemoryBlock& data,
bool addParametersToBody)
{
aURL.createHeadersAndPostData (headers, data);
aURL.createHeadersAndPostData (headers, data, addParametersToBody);
}
} // namespace juce

+ 71
- 52
libs/juce-current/source/modules/juce_core/network/juce_WebInputStream.h View File

@@ -25,35 +25,26 @@ namespace juce
//==============================================================================
/**
An InputStream which can be used to read from a given url.
An InputStream which can be used to read from a given URL.
@tags{Core}
*/
class JUCE_API WebInputStream : public InputStream
class JUCE_API WebInputStream : public InputStream
{
public:
/** Used to receive callbacks for data send progress */
class JUCE_API Listener
{
public:
virtual ~Listener() = default;
virtual bool postDataSendProgress (WebInputStream& /*request*/, int /*bytesSent*/, int /*totalBytes*/) { return true; }
};
/** Creates a new WebInputstream which can be used to read from a url.
/** Creates a new WebInputStream which can be used to read from a URL.
@param url The url that should be retrieved. This parameter may also contain
post data and/or parameters.
@param url The URL that should be retrieved. This parameter may also contain
POST data and/or parameters.
@param usePost Specifies whether a GET or a POST command should be used. This
parameter will also influence the way parameters are encoded.
*/
WebInputStream (const URL& url, const bool usePost);
WebInputStream (const URL& url, bool usePost);
/** Destructor. */
~WebInputStream() override;
/** Add extra headers to http request
/** Add extra headers to the HTTP request.
Returns a reference to itself so that several methods can be chained.
@@ -63,65 +54,67 @@ class JUCE_API WebInputStream : public InputStream
*/
WebInputStream& withExtraHeaders (const String& extraHeaders);
/** Override the http command that is sent
/** Override the HTTP command that is sent.
Returns a reference to itself so that several methods can be chained.
Note that this command will not change the way parameters are sent. This
must be specified in the constructor.
@param customRequestCommand this string is the custom http request command such
as POST or GET.
@param customRequestCommand this string is the custom http request command such
as POST or GET.
*/
WebInputStream& withCustomRequestCommand (const String& customRequestCommand);
/** Specify the connection time-out
/** Specify the connection time-out.
Returns a reference to itself so that several methods can be chained.
@param timeoutInMs the number of milliseconds to wait until the connection
request is aborted.
@param timeoutInMs the number of milliseconds to wait until the connection
request is aborted.
*/
WebInputStream& withConnectionTimeout (int timeoutInMs);
/** Specify the number of redirects to be followed
/** Specify the number of redirects to be followed.
Returns a reference to itself so that several methods can be chained.
@param numRedirects specifies the number of redirects that will be followed
before returning a response (ignored for Android which
follows up to 5 redirects)
@param numRedirects specifies the number of redirects that will be followed
before returning a response (ignored for Android which
follows up to 5 redirects)
*/
WebInputStream& withNumRedirectsToFollow (int numRedirects);
/** Returns a string array pair of the request headers */
StringPairArray getRequestHeaders() const;
/** Returns a string array pair of response headers
If getResponseHeaders is called without an established connection, then
getResponseHeaders will call connect internally and block until connect
returns - either due to a successful connection or a connection
error.
//==============================================================================
/** Used to receive callbacks for POST data send progress.
@see connect
Pass one of these into the `connect()` method and its `postDataSendProgress()`
method will be called periodically with updates on POST data upload progress.
*/
StringPairArray getResponseHeaders();
class JUCE_API Listener
{
public:
/** Destructor. */
virtual ~Listener() = default;
/** Returns the status code returned by the http server
/** This method will be called periodically with updates on POST data upload progress.
If getStatusCode is called without an established connection, then
getStatusCode will call connect internally and block until connect
returns - either due to a successful connection or a connection
error.
@param request the original request
@param bytesSent the number of bytes sent so far
@param totalByes the total number of bytes to send
@see connect
*/
int getStatusCode();
@returns true to continue or false to cancel the upload
*/
virtual bool postDataSendProgress (WebInputStream& request, int bytesSent, int totalBytes)
{
ignoreUnused (request, bytesSent, totalBytes);
return true;
}
};
/** Wait until the first byte is ready for reading
/** Wait until the first byte is ready for reading.
This method will attempt to connect to the url given in the constructor
This method will attempt to connect to the URL given in the constructor
and block until the status code and all response headers have been received or
an error has occurred.
@@ -145,6 +138,31 @@ class JUCE_API WebInputStream : public InputStream
/** Will cancel a blocking read and prevent any subsequent connection attempts. */
void cancel();
/** Returns a StringArrayPair of the request headers. */
StringPairArray getRequestHeaders() const;
/** Returns a StringArrayPair of response headers.
If getResponseHeaders is called without an established connection, then
getResponseHeaders will call connect internally and block until connect
returns - either due to a successful connection or a connection
error.
@see connect
*/
StringPairArray getResponseHeaders();
/** Returns the status code returned by the HTTP server
If getStatusCode is called without an established connection, then
getStatusCode will call connect internally and block until connect
returns - either due to a successful connection or a connection
error.
@see connect
*/
int getStatusCode();
//==============================================================================
/** Returns the total number of bytes available for reading in this stream.
@@ -181,6 +199,7 @@ class JUCE_API WebInputStream : public InputStream
bool isExhausted() override;
/** Returns the offset of the next byte that will be read from the stream.
@see setPosition
*/
int64 getPosition() override;
@@ -201,14 +220,14 @@ class JUCE_API WebInputStream : public InputStream
bool setPosition (int64 wantedPos) override;
private:
static void createHeadersAndPostData (const URL&, String&, MemoryBlock&);
static StringPairArray parseHttpHeaders (const String& headerData);
static void createHeadersAndPostData (const URL&, String&, MemoryBlock&, bool);
static StringPairArray parseHttpHeaders (const String&);
class Pimpl;
friend class Pimpl;
Pimpl* const pimpl;
bool hasCalledConnect;
std::unique_ptr<Pimpl> pimpl;
bool hasCalledConnect = false;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream)
};


Loading…
Cancel
Save