Skip to content

Commit 79fb6f5

Browse files
committed
Handle connection errors correctly, improve download threading logic
1 parent bcf2df6 commit 79fb6f5

File tree

2 files changed

+86
-30
lines changed

2 files changed

+86
-30
lines changed

Source/Dialogs/PatchStore.h

Lines changed: 85 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ class DownloadPool final : public DeletedAtShutdown {
1010
virtual ~DownloadListener() = default;
1111
virtual void downloadProgressed(hash32 hash, float progress) { }
1212
virtual void databaseDownloadCompleted(HeapArray<std::pair<PatchInfo, int>> const& patches) { }
13+
virtual void databaseDownloadFailed() { }
1314
virtual void patchDownloadCompleted(hash32 hash, bool success) { }
1415
virtual void imageDownloadCompleted(hash32 hash, MemoryBlock const& imageData) { } // calls back on worker thread, so we can still process the image without consuming message thread time
1516
};
1617

1718
~DownloadPool() override
1819
{
20+
cancelledImageDownload = true;
1921
imagePool.removeAllJobs(true, -1);
2022
patchPool.removeAllJobs(true, -1);
2123
clearSingletonInstance();
@@ -35,27 +37,42 @@ class DownloadPool final : public DeletedAtShutdown {
3537

3638
void cancelImageDownloads()
3739
{
38-
imagePool.removeAllJobs(true, 500);
40+
cancelledImageDownload = true;
41+
imagePool.removeAllJobs(true, -1);
42+
cancelledImageDownload = false;
3943
}
4044

4145
void downloadDatabase()
4246
{
47+
cancelImageDownloads();
4348
imagePool.addJob([this] {
4449
SmallArray<PatchInfo> patches;
50+
int statusCode = 0;
51+
auto const webstream = URL("https://plugdata.org/store.json").createInputStream(URL::InputStreamOptions(URL::ParameterHandling::inAddress).withConnectionTimeoutMs(10000).withStatusCode(&statusCode));
52+
53+
if (!webstream || statusCode >= 400) {
54+
MessageManager::callAsync([this] {
55+
for (auto& listener : listeners) {
56+
listener->databaseDownloadFailed();
57+
}
58+
});
59+
return;
60+
}
4561

46-
auto const webstream = std::make_unique<WebInputStream>(URL("https://plugdata.org/store.json"), false);
47-
webstream->connect(nullptr);
62+
MemoryBlock jsonData;
63+
MemoryOutputStream mo(jsonData, false);
4864

49-
if (webstream->isError() || webstream->getStatusCode() == 400) {
50-
// TODO: show error
51-
return;
65+
mo.preallocate(32000); // fit store.json file with some extra space
66+
while(true)
67+
{
68+
auto const written = mo.writeFromInputStream(*webstream, 1<<14);
69+
if (written == 0)
70+
break;
5271
}
5372

54-
MemoryBlock block;
55-
webstream->readIntoMemoryBlock(block);
56-
MemoryInputStream memstream(block, false);
73+
MemoryInputStream jsonStream(jsonData, false);
5774

58-
auto const parsedData = JSON::parse(memstream);
75+
auto const parsedData = JSON::parse(jsonStream);
5976
auto patchData = parsedData["Patches"];
6077
if (patchData.isArray()) {
6178
for (int i = 0; i < patchData.size(); ++i) {
@@ -98,35 +115,52 @@ class DownloadPool final : public DeletedAtShutdown {
98115

99116
void downloadImage(hash32 hash, URL location)
100117
{
101-
imagePool.addJob([this, hash, location] {
118+
imagePool.addJob([this, hash, location] (){
102119
static UnorderedMap<hash32, MemoryBlock> downloadImageCache;
103-
static CriticalSection cacheMutex;
120+
static CriticalSection cacheMutex; // Prevent threadpool jobs from touching cache at the same time
104121

105122
// Try loading from cache
106-
MemoryBlock block;
123+
MemoryBlock imageData;
107124
{
108125
ScopedLock lock(cacheMutex);
109126

110127
if (downloadImageCache.contains(hash)) {
111128
if (auto blockIter = downloadImageCache.find(hash); blockIter != downloadImageCache.end()) {
112-
block = blockIter->second;
129+
imageData = blockIter->second;
113130
}
114131
}
115132
}
116133

117134
// Load the image data from the URL
118-
if(block.getSize() == 0) {
119-
WebInputStream memstream(location, false);
120-
memstream.readIntoMemoryBlock(block);
135+
if(imageData.getSize() == 0) {
136+
int statusCode = 0;
137+
auto const webstream = location.createInputStream(URL::InputStreamOptions(URL::ParameterHandling::inAddress).withConnectionTimeoutMs(10000).withStatusCode(&statusCode));
138+
if (!webstream || statusCode >= 400) {
139+
return; // For images, we just quietly fail
140+
}
141+
MemoryOutputStream mo(imageData, false);
142+
143+
mo.preallocate(300000); // fits most store thumbnails
144+
while(true)
145+
{
146+
if(cancelledImageDownload) return;
147+
148+
auto const written = mo.writeFromInputStream(*webstream, 1<<16);
149+
if (written == 0)
150+
break;
151+
}
152+
121153
{
122154
ScopedLock lock(cacheMutex);
123-
downloadImageCache[hash] = block;
155+
downloadImageCache[hash] = imageData;
124156
}
125157
}
158+
159+
if(cancelledImageDownload) return;
126160

127161
ScopedLock const lock(listenersLock);
128162
for (auto& listener : listeners) {
129-
listener->imageDownloadCompleted(hash, block);
163+
listener->imageDownloadCompleted(hash, imageData);
130164
}
131165
});
132166
}
@@ -154,14 +188,17 @@ class DownloadPool final : public DeletedAtShutdown {
154188

155189
float progress = static_cast<long double>(bytesDownloaded) / static_cast<long double>(totalBytes);
156190

157-
if (cancelledDownloads.contains(downloadHash)) {
158-
cancelledDownloads.erase(downloadHash);
159-
MessageManager::callAsync([this, downloadHash]() mutable {
160-
for (auto& listener : listeners) {
161-
listener->patchDownloadCompleted(downloadHash, false);
162-
}
163-
});
164-
return;
191+
{
192+
ScopedLock const cancelLock(cancelledDownloadsLock);
193+
if (cancelledDownloads.contains(downloadHash)) {
194+
cancelledDownloads.erase(downloadHash);
195+
MessageManager::callAsync([this, downloadHash]() mutable {
196+
for (auto& listener : listeners) {
197+
listener->patchDownloadCompleted(downloadHash, false);
198+
}
199+
});
200+
return;
201+
}
165202
}
166203

167204
MessageManager::callAsync([this, downloadHash, progress]() mutable {
@@ -210,6 +247,7 @@ class DownloadPool final : public DeletedAtShutdown {
210247

211248
void cancelDownload(hash32 hash)
212249
{
250+
ScopedLock const cancelLock(cancelledDownloadsLock);
213251
cancelledDownloads.insert(hash);
214252
}
215253

@@ -222,6 +260,8 @@ class DownloadPool final : public DeletedAtShutdown {
222260

223261
ThreadPool imagePool = ThreadPool(3);
224262
ThreadPool patchPool = ThreadPool(2);
263+
264+
std::atomic<bool> cancelledImageDownload = false;
225265

226266
public:
227267
JUCE_DECLARE_SINGLETON(DownloadPool, false);
@@ -316,14 +356,12 @@ class OnlineImage final : public Component
316356
}
317357
}
318358

319-
assert(webpImage.isValid());
320359
MessageManager::callAsync([_this = SafePointer(this), image = webpImage.createCopy()] {
321360
if(_this)
322361
{
323362
_this->image = image;
324363
_this->repaint();
325364
_this->spinner.stopSpinning();
326-
assert(image.isValid());
327365
}
328366
});
329367
}
@@ -973,6 +1011,8 @@ struct PatchStore final : public Component
9731011

9741012
SearchEditor input;
9751013
Spinner spinner;
1014+
1015+
bool connectionError = false;
9761016

9771017
PatchStore()
9781018
{
@@ -1082,6 +1122,11 @@ struct PatchStore final : public Component
10821122

10831123
g.setColour(findColour(PlugDataColour::toolbarOutlineColourId));
10841124
g.drawLine(0, 40, getWidth(), 40);
1125+
1126+
if(connectionError)
1127+
{
1128+
Fonts::drawStyledText(g, "Error: could not connect to server", Rectangle<float>(0.0f, 54.0f, getWidth(), 32.0f), Colours::orange, Semibold, 15, Justification::centred);
1129+
}
10851130
}
10861131

10871132
void resized() override
@@ -1107,8 +1152,19 @@ struct PatchStore final : public Component
11071152

11081153
void databaseDownloadCompleted(HeapArray<std::pair<PatchInfo, int>> const& patches) override
11091154
{
1155+
connectionError = false;
11101156
patchContainer.showPatches(patches);
11111157
refreshButton.setEnabled(true);
11121158
spinner.stopSpinning();
1159+
repaint();
1160+
}
1161+
1162+
void databaseDownloadFailed() override
1163+
{
1164+
connectionError = true;
1165+
patchContainer.showPatches({});
1166+
refreshButton.setEnabled(true);
1167+
spinner.stopSpinning();
1168+
repaint();
11131169
}
11141170
};

Source/Heavy/Toolchain.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ class ToolchainInstaller final : public Component
198198
}
199199

200200
if (errorMessage.isNotEmpty()) {
201-
Fonts::drawText(g, errorMessage, Rectangle<int>(90, 300, getWidth(), 20), Colours::red, 15);
201+
Fonts::drawText(g, errorMessage, Rectangle<int>(30, 300, getWidth() - 60, 20), Colours::red, 15, Justification::centred);
202202
}
203203

204204
if (isTimerRunning()) {

0 commit comments

Comments
 (0)