@@ -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())); | |||
} |
@@ -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 | |||
} | |||
@@ -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) | |||
@@ -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 |
@@ -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; | |||
@@ -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; | |||
//============================================================================== | |||
@@ -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(); | |||
@@ -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() | |||
@@ -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); | |||
@@ -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 |
@@ -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) | |||
}; | |||
@@ -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 |
@@ -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) | |||
}; | |||