summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-11-24 22:11:11 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-11-24 22:11:11 +0200
commit5268fb9f7e1bca4b0fc496ff115c253aea724e49 (patch)
tree8023f45f3538f7966c2f75674a4b14fb4778e79c
parent9421f64ee86e6aec26e05a45bcfc65edd895a8a8 (diff)
Fixed threading issues and data races
The most serious problem was that GmRequest's response body was being accessed while the TlsRequest thread was modifying it. Now the response must always be locked before accessing elsewhere. There were also inefficient data updates in the media players.
-rw-r--r--src/app.c14
-rw-r--r--src/audio/player.c2
-rw-r--r--src/gmdocument.c2
-rw-r--r--src/gmrequest.c233
-rw-r--r--src/gmrequest.h5
-rw-r--r--src/media.c10
-rw-r--r--src/media.h2
-rw-r--r--src/ui/documentwidget.c51
8 files changed, 176 insertions, 143 deletions
diff --git a/src/app.c b/src/app.c
index 8db863c6..01a2e5a8 100644
--- a/src/app.c
+++ b/src/app.c
@@ -99,7 +99,7 @@ struct Impl_App {
99 uint32_t lastTickerTime; 99 uint32_t lastTickerTime;
100 uint32_t elapsedSinceLastTicker; 100 uint32_t elapsedSinceLastTicker;
101 iBool running; 101 iBool running;
102 iBool pendingRefresh; 102 iAtomicInt pendingRefresh;
103 int tabEnum; 103 int tabEnum;
104 iStringList *launchCommands; 104 iStringList *launchCommands;
105 iBool isFinishedLaunching; 105 iBool isFinishedLaunching;
@@ -355,7 +355,7 @@ static void init_App_(iApp *d, int argc, char **argv) {
355 setCStr_String(&d->prefs.downloadDir, downloadDir_App_); 355 setCStr_String(&d->prefs.downloadDir, downloadDir_App_);
356 d->running = iFalse; 356 d->running = iFalse;
357 d->window = NULL; 357 d->window = NULL;
358 d->pendingRefresh = iFalse; 358 set_Atomic(&d->pendingRefresh, iFalse);
359 d->certs = new_GmCerts(dataDir_App_); 359 d->certs = new_GmCerts(dataDir_App_);
360 d->visited = new_Visited(); 360 d->visited = new_Visited();
361 d->bookmarks = new_Bookmarks(); 361 d->bookmarks = new_Bookmarks();
@@ -484,7 +484,7 @@ const iString *debugInfo_App(void) {
484} 484}
485 485
486iLocalDef iBool isWaitingAllowed_App_(const iApp *d) { 486iLocalDef iBool isWaitingAllowed_App_(const iApp *d) {
487 return !d->pendingRefresh && isEmpty_SortedArray(&d->tickers); 487 return !value_Atomic(&d->pendingRefresh) && isEmpty_SortedArray(&d->tickers);
488} 488}
489 489
490void processEvents_App(enum iAppEventMode eventMode) { 490void processEvents_App(enum iAppEventMode eventMode) {
@@ -583,11 +583,11 @@ void refresh_App(void) {
583 iApp *d = &app_; 583 iApp *d = &app_;
584 destroyPending_Widget(); 584 destroyPending_Widget();
585 draw_Window(d->window); 585 draw_Window(d->window);
586 d->pendingRefresh = iFalse; 586 set_Atomic(&d->pendingRefresh, iFalse);
587} 587}
588 588
589iBool isRefreshPending_App(void) { 589iBool isRefreshPending_App(void) {
590 return app_.pendingRefresh; 590 return value_Atomic(&app_.pendingRefresh);
591} 591}
592 592
593uint32_t elapsedSinceLastTicker_App(void) { 593uint32_t elapsedSinceLastTicker_App(void) {
@@ -642,8 +642,8 @@ int run_App(int argc, char **argv) {
642 642
643void postRefresh_App(void) { 643void postRefresh_App(void) {
644 iApp *d = &app_; 644 iApp *d = &app_;
645 if (!d->pendingRefresh) { 645 const iBool wasPending = exchange_Atomic(&d->pendingRefresh, iTrue);
646 d->pendingRefresh = iTrue; 646 if (!wasPending) {
647 SDL_Event ev; 647 SDL_Event ev;
648 ev.user.type = SDL_USEREVENT; 648 ev.user.type = SDL_USEREVENT;
649 ev.user.code = refresh_UserEventCode; 649 ev.user.code = refresh_UserEventCode;
diff --git a/src/audio/player.c b/src/audio/player.c
index 77c23104..1c8538b4 100644
--- a/src/audio/player.c
+++ b/src/audio/player.c
@@ -667,7 +667,7 @@ void updateSourceData_Player(iPlayer *d, const iString *mimeType, const iBlock *
667 const size_t newSize = size_Block(data); 667 const size_t newSize = size_Block(data);
668 iAssert(newSize >= oldSize); 668 iAssert(newSize >= oldSize);
669 /* The old parts cannot have changed. */ 669 /* The old parts cannot have changed. */
670 iAssert(memcmp(constData_Block(&input->data), constData_Block(data), oldSize) == 0); 670// iAssert(memcmp(constData_Block(&input->data), constData_Block(data), oldSize) == 0);
671 appendData_Block(&input->data, constBegin_Block(data) + oldSize, newSize - oldSize); 671 appendData_Block(&input->data, constBegin_Block(data) + oldSize, newSize - oldSize);
672 input->isComplete = iFalse; 672 input->isComplete = iFalse;
673 break; 673 break;
diff --git a/src/gmdocument.c b/src/gmdocument.c
index c8fc9869..50c79459 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -535,7 +535,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
535 } 535 }
536 /* Flag the end of line, too. */ 536 /* Flag the end of line, too. */
537 ((iGmRun *) back_Array(&d->layout))->flags |= endOfLine_GmRunFlag; 537 ((iGmRun *) back_Array(&d->layout))->flags |= endOfLine_GmRunFlag;
538 /* Image content. */ 538 /* Image or audio content. */
539 if (type == link_GmLineType) { 539 if (type == link_GmLineType) {
540 const iMediaId imageId = findLinkImage_Media(d->media, run.linkId); 540 const iMediaId imageId = findLinkImage_Media(d->media, run.linkId);
541 const iMediaId audioId = !imageId ? findLinkAudio_Media(d->media, run.linkId) : 0; 541 const iMediaId audioId = !imageId ? findLinkAudio_Media(d->media, run.linkId) : 0;
diff --git a/src/gmrequest.c b/src/gmrequest.c
index 15782e01..14f77405 100644
--- a/src/gmrequest.c
+++ b/src/gmrequest.c
@@ -123,13 +123,15 @@ enum iGmRequestState {
123 123
124struct Impl_GmRequest { 124struct Impl_GmRequest {
125 iObject object; 125 iObject object;
126 iMutex mutex; 126 iMutex * mtx;
127 iGmCerts * certs; /* not owned */ 127 iGmCerts * certs; /* not owned */
128 enum iGmRequestState state; 128 enum iGmRequestState state;
129 iString url; 129 iString url;
130 iTlsRequest * req; 130 iTlsRequest * req;
131 iGopher gopher; 131 iGopher gopher;
132 iGmResponse resp; 132 iGmResponse * resp;
133 iBool respLocked;
134 iAtomicInt allowUpdate;
133 iAudience * updated; 135 iAudience * updated;
134 iAudience * finished; 136 iAudience * finished;
135}; 137};
@@ -140,85 +142,77 @@ iDefineAudienceGetter(GmRequest, finished)
140 142
141static void checkServerCertificate_GmRequest_(iGmRequest *d) { 143static void checkServerCertificate_GmRequest_(iGmRequest *d) {
142 const iTlsCertificate *cert = serverCertificate_TlsRequest(d->req); 144 const iTlsCertificate *cert = serverCertificate_TlsRequest(d->req);
143 d->resp.certFlags = 0; 145 iGmResponse *resp = d->resp;
146 resp->certFlags = 0;
144 if (cert) { 147 if (cert) {
145 const iRangecc domain = range_String(hostName_Address(address_TlsRequest(d->req))); 148 const iRangecc domain = range_String(hostName_Address(address_TlsRequest(d->req)));
146 d->resp.certFlags |= available_GmCertFlag; 149 resp->certFlags |= available_GmCertFlag;
147 set_Block(&d->resp.certFingerprint, collect_Block(fingerprint_TlsCertificate(cert))); 150 set_Block(&resp->certFingerprint, collect_Block(fingerprint_TlsCertificate(cert)));
148 d->resp.certFlags |= haveFingerprint_GmCertFlag; 151 resp->certFlags |= haveFingerprint_GmCertFlag;
149 if (!isExpired_TlsCertificate(cert)) { 152 if (!isExpired_TlsCertificate(cert)) {
150 d->resp.certFlags |= timeVerified_GmCertFlag; 153 resp->certFlags |= timeVerified_GmCertFlag;
151 } 154 }
152 /* TODO: Check for IP too (see below), because it may be specified in the SAN. */
153#if 0
154 iString *ip = toStringFlags_Address(address_TlsRequest(d->req), noPort_SocketStringFlag, 0);
155 if (verifyIp_TlsCertificate(cert, ip)) {
156 printf("[GmRequest] IP address %s matches!\n", cstr_String(ip));
157 }
158 else {
159 printf("[GmRequest] IP address %s not matched\n", cstr_String(ip));
160 }
161 delete_String(ip);
162#endif
163 if (verifyDomain_TlsCertificate(cert, domain)) { 155 if (verifyDomain_TlsCertificate(cert, domain)) {
164 d->resp.certFlags |= domainVerified_GmCertFlag; 156 resp->certFlags |= domainVerified_GmCertFlag;
165 } 157 }
166 if (checkTrust_GmCerts(d->certs, domain, cert)) { 158 if (checkTrust_GmCerts(d->certs, domain, cert)) {
167 d->resp.certFlags |= trusted_GmCertFlag; 159 resp->certFlags |= trusted_GmCertFlag;
168 } 160 }
169 validUntil_TlsCertificate(cert, &d->resp.certValidUntil); 161 validUntil_TlsCertificate(cert, &resp->certValidUntil);
170 set_String(&d->resp.certSubject, collect_String(subject_TlsCertificate(cert))); 162 set_String(&resp->certSubject, collect_String(subject_TlsCertificate(cert)));
171 } 163 }
172} 164}
173 165
174static void readIncoming_GmRequest_(iGmRequest *d, iTlsRequest *req) { 166static void readIncoming_GmRequest_(iGmRequest *d, iTlsRequest *req) {
175 iBool notifyUpdate = iFalse; 167 iBool notifyUpdate = iFalse;
176 iBool notifyDone = iFalse; 168 iBool notifyDone = iFalse;
177 lock_Mutex(&d->mutex); 169 lock_Mutex(d->mtx);
170 iGmResponse *resp =d->resp;
178 iAssert(d->state != finished_GmRequestState); /* notifications out of order? */ 171 iAssert(d->state != finished_GmRequestState); /* notifications out of order? */
179 iBlock *data = readAll_TlsRequest(req); 172 iBlock *data = readAll_TlsRequest(req);
180 if (d->state == receivingHeader_GmRequestState) { 173 if (d->state == receivingHeader_GmRequestState) {
181 appendCStrN_String(&d->resp.meta, constData_Block(data), size_Block(data)); 174 appendCStrN_String(&resp->meta, constData_Block(data), size_Block(data));
182 /* Check if the header line is complete. */ 175 /* Check if the header line is complete. */
183 size_t endPos = indexOfCStr_String(&d->resp.meta, "\r\n"); 176 size_t endPos = indexOfCStr_String(&resp->meta, "\r\n");
184 if (endPos != iInvalidPos) { 177 if (endPos != iInvalidPos) {
185 /* Move remainder to the body. */ 178 /* Move remainder to the body. */
186 setData_Block(&d->resp.body, 179 setData_Block(&resp->body,
187 constBegin_String(&d->resp.meta) + endPos + 2, 180 constBegin_String(&resp->meta) + endPos + 2,
188 size_String(&d->resp.meta) - endPos - 2); 181 size_String(&resp->meta) - endPos - 2);
189 remove_Block(&d->resp.meta.chars, endPos, iInvalidSize); 182 remove_Block(&resp->meta.chars, endPos, iInvalidSize);
190 /* parse and remove the code */ 183 /* parse and remove the code */
191 if (size_String(&d->resp.meta) < 3) { 184 if (size_String(&resp->meta) < 3) {
192 clear_String(&d->resp.meta); 185 clear_String(&resp->meta);
193 d->resp.statusCode = invalidHeader_GmStatusCode; 186 resp->statusCode = invalidHeader_GmStatusCode;
194 d->state = finished_GmRequestState; 187 d->state = finished_GmRequestState;
195 notifyDone = iTrue; 188 notifyDone = iTrue;
196 } 189 }
197 const int code = toInt_String(&d->resp.meta); 190 const int code = toInt_String(&resp->meta);
198 if (code == 0) { 191 if (code == 0) {
199 clear_String(&d->resp.meta); 192 clear_String(&resp->meta);
200 d->resp.statusCode = invalidHeader_GmStatusCode; 193 resp->statusCode = invalidHeader_GmStatusCode;
201 d->state = finished_GmRequestState; 194 d->state = finished_GmRequestState;
202 notifyDone = iTrue; 195 notifyDone = iTrue;
203 } 196 }
204 remove_Block(&d->resp.meta.chars, 0, 3); /* just the meta */ 197 remove_Block(&resp->meta.chars, 0, 3); /* just the meta */
205 if (code == success_GmStatusCode && isEmpty_String(&d->resp.meta)) { 198 if (code == success_GmStatusCode && isEmpty_String(&resp->meta)) {
206 setCStr_String(&d->resp.meta, "text/gemini; charset=utf-8"); /* default */ 199 setCStr_String(&resp->meta, "text/gemini; charset=utf-8"); /* default */
207 } 200 }
208 d->resp.statusCode = code; 201 resp->statusCode = code;
209 d->state = receivingBody_GmRequestState; 202 d->state = receivingBody_GmRequestState;
210 checkServerCertificate_GmRequest_(d); 203 checkServerCertificate_GmRequest_(d);
211 notifyUpdate = iTrue; 204 notifyUpdate = iTrue;
212 } 205 }
213 } 206 }
214 else if (d->state == receivingBody_GmRequestState) { 207 else if (d->state == receivingBody_GmRequestState) {
215 append_Block(&d->resp.body, data); 208 append_Block(&resp->body, data);
216 notifyUpdate = iTrue; 209 notifyUpdate = iTrue;
217 } 210 }
218 initCurrent_Time(&d->resp.when); 211 initCurrent_Time(&resp->when);
219 delete_Block(data); 212 delete_Block(data);
220 unlock_Mutex(&d->mutex); 213 unlock_Mutex(d->mtx);
221 if (notifyUpdate) { 214 const iBool allowed = exchange_Atomic(&d->allowUpdate, iFalse);
215 if (notifyUpdate && allowed) {
222 iNotifyAudience(d, updated, GmRequestUpdated); 216 iNotifyAudience(d, updated, GmRequestUpdated);
223 } 217 }
224 if (notifyDone) { 218 if (notifyDone) {
@@ -228,21 +222,21 @@ static void readIncoming_GmRequest_(iGmRequest *d, iTlsRequest *req) {
228 222
229static void requestFinished_GmRequest_(iGmRequest *d, iTlsRequest *req) { 223static void requestFinished_GmRequest_(iGmRequest *d, iTlsRequest *req) {
230 iAssert(req == d->req); 224 iAssert(req == d->req);
231 lock_Mutex(&d->mutex); 225 lock_Mutex(d->mtx);
232 /* There shouldn't be anything left to read. */ { 226 /* There shouldn't be anything left to read. */ {
233 iBlock *data = readAll_TlsRequest(req); 227 iBlock *data = readAll_TlsRequest(req);
234 iAssert(isEmpty_Block(data)); 228 iAssert(isEmpty_Block(data));
235 delete_Block(data); 229 delete_Block(data);
236 initCurrent_Time(&d->resp.when); 230 initCurrent_Time(&d->resp->when);
237 } 231 }
238 d->state = (status_TlsRequest(req) == error_TlsRequestStatus ? failure_GmRequestState 232 d->state = (status_TlsRequest(req) == error_TlsRequestStatus ? failure_GmRequestState
239 : finished_GmRequestState); 233 : finished_GmRequestState);
240 if (d->state == failure_GmRequestState) { 234 if (d->state == failure_GmRequestState) {
241 d->resp.statusCode = tlsFailure_GmStatusCode; 235 d->resp->statusCode = tlsFailure_GmStatusCode;
242 set_String(&d->resp.meta, errorMessage_TlsRequest(req)); 236 set_String(&d->resp->meta, errorMessage_TlsRequest(req));
243 } 237 }
244 checkServerCertificate_GmRequest_(d); 238 checkServerCertificate_GmRequest_(d);
245 unlock_Mutex(&d->mutex); 239 unlock_Mutex(d->mtx);
246 iNotifyAudience(d, finished, GmRequestFinished); 240 iNotifyAudience(d, finished, GmRequestFinished);
247} 241}
248 242
@@ -349,14 +343,14 @@ static const iBlock *replaceVariables_(const iBlock *block) {
349 343
350static void gopherRead_GmRequest_(iGmRequest *d, iSocket *socket) { 344static void gopherRead_GmRequest_(iGmRequest *d, iSocket *socket) {
351 iBool notifyUpdate = iFalse; 345 iBool notifyUpdate = iFalse;
352 lock_Mutex(&d->mutex); 346 lock_Mutex(d->mtx);
353 d->resp.statusCode = success_GmStatusCode; 347 d->resp->statusCode = success_GmStatusCode;
354 iBlock *data = readAll_Socket(socket); 348 iBlock *data = readAll_Socket(socket);
355 if (!isEmpty_Block(data)) { 349 if (!isEmpty_Block(data)) {
356 processResponse_Gopher(&d->gopher, data); 350 processResponse_Gopher(&d->gopher, data);
357 } 351 }
358 delete_Block(data); 352 delete_Block(data);
359 unlock_Mutex(&d->mutex); 353 unlock_Mutex(d->mtx);
360 if (notifyUpdate) { 354 if (notifyUpdate) {
361 iNotifyAudience(d, updated, GmRequestUpdated); 355 iNotifyAudience(d, updated, GmRequestUpdated);
362 } 356 }
@@ -365,12 +359,12 @@ static void gopherRead_GmRequest_(iGmRequest *d, iSocket *socket) {
365static void gopherDisconnected_GmRequest_(iGmRequest *d, iSocket *socket) { 359static void gopherDisconnected_GmRequest_(iGmRequest *d, iSocket *socket) {
366 iUnused(socket); 360 iUnused(socket);
367 iBool notify = iFalse; 361 iBool notify = iFalse;
368 lock_Mutex(&d->mutex); 362 lock_Mutex(d->mtx);
369 if (d->state != failure_GmRequestState) { 363 if (d->state != failure_GmRequestState) {
370 d->state = finished_GmRequestState; 364 d->state = finished_GmRequestState;
371 notify = iTrue; 365 notify = iTrue;
372 } 366 }
373 unlock_Mutex(&d->mutex); 367 unlock_Mutex(d->mtx);
374 if (notify) { 368 if (notify) {
375 iNotifyAudience(d, finished, GmRequestFinished); 369 iNotifyAudience(d, finished, GmRequestFinished);
376 } 370 }
@@ -378,12 +372,12 @@ static void gopherDisconnected_GmRequest_(iGmRequest *d, iSocket *socket) {
378 372
379static void gopherError_GmRequest_(iGmRequest *d, iSocket *socket, int error, const char *msg) { 373static void gopherError_GmRequest_(iGmRequest *d, iSocket *socket, int error, const char *msg) {
380 iUnused(socket); 374 iUnused(socket);
381 lock_Mutex(&d->mutex); 375 lock_Mutex(d->mtx);
382 d->state = failure_GmRequestState; 376 d->state = failure_GmRequestState;
383 d->resp.statusCode = tlsFailure_GmStatusCode; 377 d->resp->statusCode = tlsFailure_GmStatusCode;
384 format_String(&d->resp.meta, "%s (errno %d)", msg, error); 378 format_String(&d->resp->meta, "%s (errno %d)", msg, error);
385 clear_Block(&d->resp.body); 379 clear_Block(&d->resp->body);
386 unlock_Mutex(&d->mutex); 380 unlock_Mutex(d->mtx);
387 iNotifyAudience(d, finished, GmRequestFinished); 381 iNotifyAudience(d, finished, GmRequestFinished);
388} 382}
389 383
@@ -392,8 +386,9 @@ static void beginGopherConnection_GmRequest_(iGmRequest *d, const iString *host,
392 port = 70; /* default port */ 386 port = 70; /* default port */
393 } 387 }
394 clear_Block(&d->gopher.source); 388 clear_Block(&d->gopher.source);
395 d->gopher.meta = &d->resp.meta; 389 iGmResponse *resp = d->resp;
396 d->gopher.output = &d->resp.body; 390 d->gopher.meta = &resp->meta;
391 d->gopher.output = &resp->body;
397 d->state = receivingBody_GmRequestState; 392 d->state = receivingBody_GmRequestState;
398 d->gopher.socket = new_Socket(cstr_String(host), port); 393 d->gopher.socket = new_Socket(cstr_String(host), port);
399 iConnect(Socket, d->gopher.socket, readyRead, d, gopherRead_GmRequest_); 394 iConnect(Socket, d->gopher.socket, readyRead, d, gopherRead_GmRequest_);
@@ -401,8 +396,8 @@ static void beginGopherConnection_GmRequest_(iGmRequest *d, const iString *host,
401 iConnect(Socket, d->gopher.socket, error, d, gopherError_GmRequest_); 396 iConnect(Socket, d->gopher.socket, error, d, gopherError_GmRequest_);
402 open_Gopher(&d->gopher, &d->url); 397 open_Gopher(&d->gopher, &d->url);
403 if (d->gopher.needQueryArgs) { 398 if (d->gopher.needQueryArgs) {
404 d->resp.statusCode = input_GmStatusCode; 399 resp->statusCode = input_GmStatusCode;
405 setCStr_String(&d->resp.meta, "Enter query:"); 400 setCStr_String(&resp->meta, "Enter query:");
406 d->state = finished_GmRequestState; 401 d->state = finished_GmRequestState;
407 iNotifyAudience(d, finished, GmRequestFinished); 402 iNotifyAudience(d, finished, GmRequestFinished);
408 } 403 }
@@ -411,8 +406,10 @@ static void beginGopherConnection_GmRequest_(iGmRequest *d, const iString *host,
411/*----------------------------------------------------------------------------------------------*/ 406/*----------------------------------------------------------------------------------------------*/
412 407
413void init_GmRequest(iGmRequest *d, iGmCerts *certs) { 408void init_GmRequest(iGmRequest *d, iGmCerts *certs) {
414 init_Mutex(&d->mutex); 409 d->mtx = new_Mutex();
415 init_GmResponse(&d->resp); 410 d->resp = new_GmResponse();
411 d->respLocked = iFalse;
412 set_Atomic(&d->allowUpdate, iTrue);
416 init_String(&d->url); 413 init_String(&d->url);
417 init_Gopher(&d->gopher); 414 init_Gopher(&d->gopher);
418 d->certs = certs; 415 d->certs = certs;
@@ -427,22 +424,24 @@ void deinit_GmRequest(iGmRequest *d) {
427 iDisconnectObject(TlsRequest, d->req, readyRead, d); 424 iDisconnectObject(TlsRequest, d->req, readyRead, d);
428 iDisconnectObject(TlsRequest, d->req, finished, d); 425 iDisconnectObject(TlsRequest, d->req, finished, d);
429 } 426 }
430 lock_Mutex(&d->mutex); 427 lock_Mutex(d->mtx);
431 if (!isFinished_GmRequest(d)) { 428 if (!isFinished_GmRequest(d)) {
432 unlock_Mutex(&d->mutex); 429 unlock_Mutex(d->mtx);
433 cancel_GmRequest(d); 430 cancel_GmRequest(d);
434 d->state = finished_GmRequestState; 431 d->state = finished_GmRequestState;
435 } 432 }
436 else { 433 else {
437 unlock_Mutex(&d->mutex); 434 unlock_Mutex(d->mtx);
438 } 435 }
439 iReleasePtr(&d->req); 436 iReleasePtr(&d->req);
440 deinit_Gopher(&d->gopher); 437 deinit_Gopher(&d->gopher);
441 delete_Audience(d->finished); 438 delete_Audience(d->finished);
442 delete_Audience(d->updated); 439 delete_Audience(d->updated);
443 deinit_GmResponse(&d->resp); 440// delete_GmResponse(d->respPub);
441// deinit_GmResponse(&d->respInt);
442 delete_GmResponse(d->resp);
444 deinit_String(&d->url); 443 deinit_String(&d->url);
445 deinit_Mutex(&d->mutex); 444 delete_Mutex(d->mtx);
446} 445}
447 446
448void setUrl_GmRequest(iGmRequest *d, const iString *url) { 447void setUrl_GmRequest(iGmRequest *d, const iString *url) {
@@ -455,7 +454,9 @@ void submit_GmRequest(iGmRequest *d) {
455 if (d->state != initialized_GmRequestState) { 454 if (d->state != initialized_GmRequestState) {
456 return; 455 return;
457 } 456 }
458 clear_GmResponse(&d->resp); 457 set_Atomic(&d->allowUpdate, iTrue);
458 iGmResponse *resp = d->resp;
459 clear_GmResponse(resp);
459 iUrl url; 460 iUrl url;
460 init_Url(&url, &d->url); 461 init_Url(&url, &d->url);
461 /* Check for special schemes. */ 462 /* Check for special schemes. */
@@ -466,14 +467,14 @@ void submit_GmRequest(iGmRequest *d) {
466 if (equalCase_Rangecc(url.scheme, "about")) { 467 if (equalCase_Rangecc(url.scheme, "about")) {
467 const iBlock *src = aboutPageSource_(url.path); 468 const iBlock *src = aboutPageSource_(url.path);
468 if (src) { 469 if (src) {
469 d->resp.statusCode = success_GmStatusCode; 470 resp->statusCode = success_GmStatusCode;
470 setCStr_String(&d->resp.meta, "text/gemini; charset=utf-8"); 471 setCStr_String(&resp->meta, "text/gemini; charset=utf-8");
471 set_Block(&d->resp.body, replaceVariables_(src)); 472 set_Block(&resp->body, replaceVariables_(src));
472 d->state = receivingBody_GmRequestState; 473 d->state = receivingBody_GmRequestState;
473 iNotifyAudience(d, updated, GmRequestUpdated); 474 iNotifyAudience(d, updated, GmRequestUpdated);
474 } 475 }
475 else { 476 else {
476 d->resp.statusCode = invalidLocalResource_GmStatusCode; 477 resp->statusCode = invalidLocalResource_GmStatusCode;
477 } 478 }
478 d->state = finished_GmRequestState; 479 d->state = finished_GmRequestState;
479 iNotifyAudience(d, finished, GmRequestFinished); 480 iNotifyAudience(d, finished, GmRequestFinished);
@@ -485,41 +486,41 @@ void submit_GmRequest(iGmRequest *d) {
485 if (open_File(f, readOnly_FileMode)) { 486 if (open_File(f, readOnly_FileMode)) {
486 /* TODO: Check supported file types: images, audio */ 487 /* TODO: Check supported file types: images, audio */
487 /* TODO: Detect text files based on contents? E.g., is the content valid UTF-8. */ 488 /* TODO: Detect text files based on contents? E.g., is the content valid UTF-8. */
488 d->resp.statusCode = success_GmStatusCode; 489 resp->statusCode = success_GmStatusCode;
489 if (endsWithCase_String(path, ".gmi") || endsWithCase_String(path, ".gemini")) { 490 if (endsWithCase_String(path, ".gmi") || endsWithCase_String(path, ".gemini")) {
490 setCStr_String(&d->resp.meta, "text/gemini; charset=utf-8"); 491 setCStr_String(&resp->meta, "text/gemini; charset=utf-8");
491 } 492 }
492 else if (endsWithCase_String(path, ".txt")) { 493 else if (endsWithCase_String(path, ".txt")) {
493 setCStr_String(&d->resp.meta, "text/plain"); 494 setCStr_String(&resp->meta, "text/plain");
494 } 495 }
495 else if (endsWithCase_String(path, ".png")) { 496 else if (endsWithCase_String(path, ".png")) {
496 setCStr_String(&d->resp.meta, "image/png"); 497 setCStr_String(&resp->meta, "image/png");
497 } 498 }
498 else if (endsWithCase_String(path, ".jpg") || endsWithCase_String(path, ".jpeg")) { 499 else if (endsWithCase_String(path, ".jpg") || endsWithCase_String(path, ".jpeg")) {
499 setCStr_String(&d->resp.meta, "image/jpeg"); 500 setCStr_String(&resp->meta, "image/jpeg");
500 } 501 }
501 else if (endsWithCase_String(path, ".gif")) { 502 else if (endsWithCase_String(path, ".gif")) {
502 setCStr_String(&d->resp.meta, "image/gif"); 503 setCStr_String(&resp->meta, "image/gif");
503 } 504 }
504 else if (endsWithCase_String(path, ".wav")) { 505 else if (endsWithCase_String(path, ".wav")) {
505 setCStr_String(&d->resp.meta, "audio/wave"); 506 setCStr_String(&resp->meta, "audio/wave");
506 } 507 }
507 else if (endsWithCase_String(path, ".ogg")) { 508 else if (endsWithCase_String(path, ".ogg")) {
508 setCStr_String(&d->resp.meta, "audio/ogg"); 509 setCStr_String(&resp->meta, "audio/ogg");
509 } 510 }
510 else if (endsWithCase_String(path, ".mp3")) { 511 else if (endsWithCase_String(path, ".mp3")) {
511 setCStr_String(&d->resp.meta, "audio/mpeg"); 512 setCStr_String(&resp->meta, "audio/mpeg");
512 } 513 }
513 else { 514 else {
514 setCStr_String(&d->resp.meta, "application/octet-stream"); 515 setCStr_String(&resp->meta, "application/octet-stream");
515 } 516 }
516 set_Block(&d->resp.body, collect_Block(readAll_File(f))); 517 set_Block(&resp->body, collect_Block(readAll_File(f)));
517 d->state = receivingBody_GmRequestState; 518 d->state = receivingBody_GmRequestState;
518 iNotifyAudience(d, updated, GmRequestUpdated); 519 iNotifyAudience(d, updated, GmRequestUpdated);
519 } 520 }
520 else { 521 else {
521 d->resp.statusCode = failedToOpenFile_GmStatusCode; 522 resp->statusCode = failedToOpenFile_GmStatusCode;
522 setCStr_String(&d->resp.meta, cstr_String(path)); 523 setCStr_String(&resp->meta, cstr_String(path));
523 } 524 }
524 iRelease(f); 525 iRelease(f);
525 d->state = finished_GmRequestState; 526 d->state = finished_GmRequestState;
@@ -527,14 +528,14 @@ void submit_GmRequest(iGmRequest *d) {
527 return; 528 return;
528 } 529 }
529 else if (equalCase_Rangecc(url.scheme, "data")) { 530 else if (equalCase_Rangecc(url.scheme, "data")) {
530 d->resp.statusCode = success_GmStatusCode; 531 resp->statusCode = success_GmStatusCode;
531 iString *src = collectNewCStr_String(url.scheme.start + 5); 532 iString *src = collectNewCStr_String(url.scheme.start + 5);
532 iRangecc header = { constBegin_String(src), constBegin_String(src) }; 533 iRangecc header = { constBegin_String(src), constBegin_String(src) };
533 while (header.end < constEnd_String(src) && *header.end != ',') { 534 while (header.end < constEnd_String(src) && *header.end != ',') {
534 header.end++; 535 header.end++;
535 } 536 }
536 iBool isBase64 = iFalse; 537 iBool isBase64 = iFalse;
537 setRange_String(&d->resp.meta, header); 538 setRange_String(&resp->meta, header);
538 /* Check what's in the header. */ { 539 /* Check what's in the header. */ {
539 iRangecc entry = iNullRange; 540 iRangecc entry = iNullRange;
540 while (nextSplit_Rangecc(header, ";", &entry)) { 541 while (nextSplit_Rangecc(header, ";", &entry)) {
@@ -550,7 +551,7 @@ void submit_GmRequest(iGmRequest *d) {
550 else { 551 else {
551 set_String(src, collect_String(urlDecode_String(src))); 552 set_String(src, collect_String(urlDecode_String(src)));
552 } 553 }
553 set_Block(&d->resp.body, &src->chars); 554 set_Block(&resp->body, &src->chars);
554 d->state = receivingBody_GmRequestState; 555 d->state = receivingBody_GmRequestState;
555 iNotifyAudience(d, updated, GmRequestUpdated); 556 iNotifyAudience(d, updated, GmRequestUpdated);
556 d->state = finished_GmRequestState; 557 d->state = finished_GmRequestState;
@@ -575,7 +576,7 @@ void submit_GmRequest(iGmRequest *d) {
575 return; 576 return;
576 } 577 }
577 else if (!equalCase_Rangecc(url.scheme, "gemini")) { 578 else if (!equalCase_Rangecc(url.scheme, "gemini")) {
578 d->resp.statusCode = unsupportedProtocol_GmStatusCode; 579 resp->statusCode = unsupportedProtocol_GmStatusCode;
579 d->state = finished_GmRequestState; 580 d->state = finished_GmRequestState;
580 iNotifyAudience(d, finished, GmRequestFinished); 581 iNotifyAudience(d, finished, GmRequestFinished);
581 return; 582 return;
@@ -604,45 +605,63 @@ void cancel_GmRequest(iGmRequest *d) {
604 cancel_Gopher(&d->gopher); 605 cancel_Gopher(&d->gopher);
605} 606}
606 607
608iGmResponse *lockResponse_GmRequest(iGmRequest *d) {
609 iAssert(!d->respLocked);
610 lock_Mutex(d->mtx);
611 d->respLocked = iTrue;
612 return d->resp;
613}
614
615void unlockResponse_GmRequest(iGmRequest *d) {
616 iAssert(d->respLocked);
617 d->respLocked = iFalse;
618 set_Atomic(&d->allowUpdate, iTrue);
619 unlock_Mutex(d->mtx);
620}
621
607iBool isFinished_GmRequest(const iGmRequest *d) { 622iBool isFinished_GmRequest(const iGmRequest *d) {
608 iBool done; 623 iBool done;
609 iGuardMutex(&d->mutex, 624 iGuardMutex(d->mtx,
610 done = (d->state == finished_GmRequestState || d->state == failure_GmRequestState)); 625 done = (d->state == finished_GmRequestState || d->state == failure_GmRequestState));
611 return done; 626 return done;
612} 627}
613 628
614enum iGmStatusCode status_GmRequest(const iGmRequest *d) { 629enum iGmStatusCode status_GmRequest(const iGmRequest *d) {
615 return d->resp.statusCode; 630 enum iGmStatusCode code;
631 iGuardMutex(d->mtx, code = d->resp->statusCode);
632 return code;
616} 633}
617 634
618const iString *meta_GmRequest(const iGmRequest *d) { 635const iString *meta_GmRequest(const iGmRequest *d) {
619 if (d->state >= receivingBody_GmRequestState) { 636 iAssert(isFinished_GmRequest(d));
620 return &d->resp.meta; 637 return &d->resp->meta;
621 }
622 return collectNew_String();
623} 638}
624 639
625const iBlock *body_GmRequest(const iGmRequest *d) { 640const iBlock *body_GmRequest(const iGmRequest *d) {
626 iBlock *body; 641 iAssert(isFinished_GmRequest(d));
627 iGuardMutex(&d->mutex, body = collect_Block(copy_Block(&d->resp.body))); 642 return &d->resp->body;
628 return body;
629} 643}
630 644
631const iString *url_GmRequest(const iGmRequest *d) { 645size_t bodySize_GmRequest(const iGmRequest *d) {
632 return &d->url; 646 size_t size;
647 iGuardMutex(d->mtx, size = size_Block(&d->resp->body));
648 return size;
633} 649}
634 650
635const iGmResponse *response_GmRequest(const iGmRequest *d) { 651const iString *url_GmRequest(const iGmRequest *d) {
636 iAssert(d->state != initialized_GmRequestState); 652 return &d->url;
637 return &d->resp;
638} 653}
639 654
640int certFlags_GmRequest(const iGmRequest *d) { 655int certFlags_GmRequest(const iGmRequest *d) {
641 return d->resp.certFlags; 656 int flags;
657 iGuardMutex(d->mtx, flags = d->resp->certFlags);
658 return flags;
642} 659}
643 660
644iDate certExpirationDate_GmRequest(const iGmRequest *d) { 661iDate certExpirationDate_GmRequest(const iGmRequest *d) {
645 return d->resp.certValidUntil; 662 iDate expr;
663 iGuardMutex(d->mtx, expr = d->resp->certValidUntil);
664 return expr;
646} 665}
647 666
648iDefineClass(GmRequest) 667iDefineClass(GmRequest)
diff --git a/src/gmrequest.h b/src/gmrequest.h
index 31059a44..bd340cf1 100644
--- a/src/gmrequest.h
+++ b/src/gmrequest.h
@@ -68,12 +68,15 @@ void setUrl_GmRequest (iGmRequest *, const iString *ur
68void submit_GmRequest (iGmRequest *); 68void submit_GmRequest (iGmRequest *);
69void cancel_GmRequest (iGmRequest *); 69void cancel_GmRequest (iGmRequest *);
70 70
71iGmResponse * lockResponse_GmRequest (iGmRequest *);
72void unlockResponse_GmRequest (iGmRequest *);
73
71iBool isFinished_GmRequest (const iGmRequest *); 74iBool isFinished_GmRequest (const iGmRequest *);
72enum iGmStatusCode status_GmRequest (const iGmRequest *); 75enum iGmStatusCode status_GmRequest (const iGmRequest *);
73const iString * meta_GmRequest (const iGmRequest *); 76const iString * meta_GmRequest (const iGmRequest *);
74const iBlock * body_GmRequest (const iGmRequest *); 77const iBlock * body_GmRequest (const iGmRequest *);
78size_t bodySize_GmRequest (const iGmRequest *);
75const iString * url_GmRequest (const iGmRequest *); 79const iString * url_GmRequest (const iGmRequest *);
76const iGmResponse * response_GmRequest (const iGmRequest *);
77 80
78int certFlags_GmRequest (const iGmRequest *); 81int certFlags_GmRequest (const iGmRequest *);
79iDate certExpirationDate_GmRequest(const iGmRequest *); 82iDate certExpirationDate_GmRequest(const iGmRequest *);
diff --git a/src/media.c b/src/media.c
index cd3dfb82..8bd635a5 100644
--- a/src/media.c
+++ b/src/media.c
@@ -154,12 +154,13 @@ void clear_Media(iMedia *d) {
154 clear_PtrArray(&d->audio); 154 clear_PtrArray(&d->audio);
155} 155}
156 156
157void setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlock *data, 157iBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlock *data,
158 int flags) { 158 int flags) {
159 const iBool isPartial = (flags & partialData_MediaFlag) != 0; 159 const iBool isPartial = (flags & partialData_MediaFlag) != 0;
160 const iBool allowHide = (flags & allowHide_MediaFlag) != 0; 160 const iBool allowHide = (flags & allowHide_MediaFlag) != 0;
161 const iBool isDeleting = (!mime || !data); 161 const iBool isDeleting = (!mime || !data);
162 iMediaId existing = findLinkImage_Media(d, linkId); 162 iMediaId existing = findLinkImage_Media(d, linkId);
163 iBool isNew = iFalse;
163 if (existing) { 164 if (existing) {
164 iGmImage *img; 165 iGmImage *img;
165 if (isDeleting) { 166 if (isDeleting) {
@@ -205,6 +206,7 @@ void setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBloc
205 if (!isPartial) { 206 if (!isPartial) {
206 makeTexture_GmImage(img); 207 makeTexture_GmImage(img);
207 } 208 }
209 isNew = iTrue;
208 } 210 }
209 else if (startsWith_String(mime, "audio/")) { 211 else if (startsWith_String(mime, "audio/")) {
210 iGmAudio *audio = new_GmAudio(); 212 iGmAudio *audio = new_GmAudio();
@@ -219,8 +221,10 @@ void setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBloc
219 /* Start playing right away. */ 221 /* Start playing right away. */
220 start_Player(audio->player); 222 start_Player(audio->player);
221 postCommandf_App("media.player.started player:%p", audio->player); 223 postCommandf_App("media.player.started player:%p", audio->player);
224 isNew = iTrue;
222 } 225 }
223 } 226 }
227 return isNew;
224} 228}
225 229
226iMediaId findLinkImage_Media(const iMedia *d, iGmLinkId linkId) { 230iMediaId findLinkImage_Media(const iMedia *d, iGmLinkId linkId) {
diff --git a/src/media.h b/src/media.h
index 12334936..ddaa2d3c 100644
--- a/src/media.h
+++ b/src/media.h
@@ -54,7 +54,7 @@ enum iMediaFlags {
54}; 54};
55 55
56void clear_Media (iMedia *); 56void clear_Media (iMedia *);
57void setData_Media (iMedia *, uint16_t linkId, const iString *mime, const iBlock *data, int flags); 57iBool setData_Media (iMedia *, uint16_t linkId, const iString *mime, const iBlock *data, int flags);
58 58
59iMediaId findLinkImage_Media (const iMedia *, uint16_t linkId); 59iMediaId findLinkImage_Media (const iMedia *, uint16_t linkId);
60iBool imageInfo_Media (const iMedia *, iMediaId imageId, iGmImageInfo *info_out); 60iBool imageInfo_Media (const iMedia *, iMediaId imageId, iGmImageInfo *info_out);
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index d7d15a67..a843e840 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -766,7 +766,7 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode
766 766
767static void updateFetchProgress_DocumentWidget_(iDocumentWidget *d) { 767static void updateFetchProgress_DocumentWidget_(iDocumentWidget *d) {
768 iLabelWidget *prog = findWidget_App("document.progress"); 768 iLabelWidget *prog = findWidget_App("document.progress");
769 const size_t dlSize = d->request ? size_Block(body_GmRequest(d->request)) : 0; 769 const size_t dlSize = d->request ? bodySize_GmRequest(d->request) : 0;
770 setFlags_Widget(as_Widget(prog), hidden_WidgetFlag, dlSize < 250000); 770 setFlags_Widget(as_Widget(prog), hidden_WidgetFlag, dlSize < 250000);
771 if (isVisible_Widget(prog)) { 771 if (isVisible_Widget(prog)) {
772 updateText_LabelWidget(prog, 772 updateText_LabelWidget(prog,
@@ -1032,9 +1032,10 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1032 if (statusCode == none_GmStatusCode) { 1032 if (statusCode == none_GmStatusCode) {
1033 return; 1033 return;
1034 } 1034 }
1035 iGmResponse *resp = lockResponse_GmRequest(d->request);
1035 if (d->state == fetching_RequestState) { 1036 if (d->state == fetching_RequestState) {
1036 d->state = receivedPartialResponse_RequestState; 1037 d->state = receivedPartialResponse_RequestState;
1037 updateTrust_DocumentWidget_(d, response_GmRequest(d->request)); 1038 updateTrust_DocumentWidget_(d, resp);
1038 init_Anim(&d->sideOpacity, 0); 1039 init_Anim(&d->sideOpacity, 0);
1039 switch (category_GmStatusCode(statusCode)) { 1040 switch (category_GmStatusCode(statusCode)) {
1040 case categoryInput_GmStatusCode: { 1041 case categoryInput_GmStatusCode: {
@@ -1045,9 +1046,9 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1045 as_Widget(d), 1046 as_Widget(d),
1046 NULL, 1047 NULL,
1047 format_CStr(uiHeading_ColorEscape "%s", cstr_Rangecc(parts.host)), 1048 format_CStr(uiHeading_ColorEscape "%s", cstr_Rangecc(parts.host)),
1048 isEmpty_String(meta_GmRequest(d->request)) 1049 isEmpty_String(&resp->meta)
1049 ? format_CStr("Please enter input for %s:", cstr_Rangecc(parts.path)) 1050 ? format_CStr("Please enter input for %s:", cstr_Rangecc(parts.path))
1050 : cstr_String(meta_GmRequest(d->request)), 1051 : cstr_String(&resp->meta),
1051 uiTextCaution_ColorEscape "Send \u21d2", 1052 uiTextCaution_ColorEscape "Send \u21d2",
1052 "document.input.submit"); 1053 "document.input.submit");
1053 setSensitive_InputWidget(findChild_Widget(dlg, "input"), 1054 setSensitive_InputWidget(findChild_Widget(dlg, "input"),
@@ -1057,15 +1058,15 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1057 case categorySuccess_GmStatusCode: 1058 case categorySuccess_GmStatusCode:
1058 init_Anim(&d->scrollY, 0); 1059 init_Anim(&d->scrollY, 0);
1059 reset_GmDocument(d->doc); /* new content incoming */ 1060 reset_GmDocument(d->doc); /* new content incoming */
1060 updateDocument_DocumentWidget_(d, response_GmRequest(d->request), iTrue); 1061 updateDocument_DocumentWidget_(d, resp, iTrue);
1061 break; 1062 break;
1062 case categoryRedirect_GmStatusCode: 1063 case categoryRedirect_GmStatusCode:
1063 if (isEmpty_String(meta_GmRequest(d->request))) { 1064 if (isEmpty_String(&resp->meta)) {
1064 showErrorPage_DocumentWidget_(d, invalidRedirect_GmStatusCode, NULL); 1065 showErrorPage_DocumentWidget_(d, invalidRedirect_GmStatusCode, NULL);
1065 } 1066 }
1066 else { 1067 else {
1067 /* Only accept redirects that use gemini scheme. */ 1068 /* Only accept redirects that use gemini scheme. */
1068 const iString *dstUrl = absoluteUrl_String(d->mod.url, meta_GmRequest(d->request)); 1069 const iString *dstUrl = absoluteUrl_String(d->mod.url, &resp->meta);
1069 if (d->redirectCount >= 5) { 1070 if (d->redirectCount >= 5) {
1070 showErrorPage_DocumentWidget_(d, tooManyRedirects_GmStatusCode, dstUrl); 1071 showErrorPage_DocumentWidget_(d, tooManyRedirects_GmStatusCode, dstUrl);
1071 } 1072 }
@@ -1085,17 +1086,17 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1085 break; 1086 break;
1086 default: 1087 default:
1087 if (isDefined_GmError(statusCode)) { 1088 if (isDefined_GmError(statusCode)) {
1088 showErrorPage_DocumentWidget_(d, statusCode, meta_GmRequest(d->request)); 1089 showErrorPage_DocumentWidget_(d, statusCode, &resp->meta);
1089 } 1090 }
1090 else if (category_GmStatusCode(statusCode) == 1091 else if (category_GmStatusCode(statusCode) ==
1091 categoryTemporaryFailure_GmStatusCode) { 1092 categoryTemporaryFailure_GmStatusCode) {
1092 showErrorPage_DocumentWidget_( 1093 showErrorPage_DocumentWidget_(
1093 d, temporaryFailure_GmStatusCode, meta_GmRequest(d->request)); 1094 d, temporaryFailure_GmStatusCode, &resp->meta);
1094 } 1095 }
1095 else if (category_GmStatusCode(statusCode) == 1096 else if (category_GmStatusCode(statusCode) ==
1096 categoryPermanentFailure_GmStatusCode) { 1097 categoryPermanentFailure_GmStatusCode) {
1097 showErrorPage_DocumentWidget_( 1098 showErrorPage_DocumentWidget_(
1098 d, permanentFailure_GmStatusCode, meta_GmRequest(d->request)); 1099 d, permanentFailure_GmStatusCode, &resp->meta);
1099 } 1100 }
1100 break; 1101 break;
1101 } 1102 }
@@ -1104,12 +1105,13 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1104 switch (category_GmStatusCode(statusCode)) { 1105 switch (category_GmStatusCode(statusCode)) {
1105 case categorySuccess_GmStatusCode: 1106 case categorySuccess_GmStatusCode:
1106 /* More content available. */ 1107 /* More content available. */
1107 updateDocument_DocumentWidget_(d, response_GmRequest(d->request), iFalse); 1108 updateDocument_DocumentWidget_(d, resp, iFalse);
1108 break; 1109 break;
1109 default: 1110 default:
1110 break; 1111 break;
1111 } 1112 }
1112 } 1113 }
1114 unlockResponse_GmRequest(d->request);
1113} 1115}
1114 1116
1115static const char *sourceLoc_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { 1117static const char *sourceLoc_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) {
@@ -1190,18 +1192,21 @@ static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *
1190 /* Pass new data to media players. */ 1192 /* Pass new data to media players. */
1191 const enum iGmStatusCode code = status_GmRequest(req->req); 1193 const enum iGmStatusCode code = status_GmRequest(req->req);
1192 if (isSuccess_GmStatusCode(code)) { 1194 if (isSuccess_GmStatusCode(code)) {
1193 if (startsWith_String(meta_GmRequest(req->req), "audio/")) { 1195 iGmResponse *resp = lockResponse_GmRequest(req->req);
1196 if (startsWith_String(&resp->meta, "audio/")) {
1194 /* TODO: Use a helper? This is same as below except for the partialData flag. */ 1197 /* TODO: Use a helper? This is same as below except for the partialData flag. */
1195 setData_Media(media_GmDocument(d->doc), 1198 if (setData_Media(media_GmDocument(d->doc),
1196 req->linkId, 1199 req->linkId,
1197 meta_GmRequest(req->req), 1200 &resp->meta,
1198 body_GmRequest(req->req), 1201 &resp->body,
1199 partialData_MediaFlag | allowHide_MediaFlag); 1202 partialData_MediaFlag | allowHide_MediaFlag)) {
1200 redoLayout_GmDocument(d->doc); 1203 redoLayout_GmDocument(d->doc);
1204 }
1201 updateVisible_DocumentWidget_(d); 1205 updateVisible_DocumentWidget_(d);
1202 invalidate_DocumentWidget_(d); 1206 invalidate_DocumentWidget_(d);
1203 refresh_Widget(as_Widget(d)); 1207 refresh_Widget(as_Widget(d));
1204 } 1208 }
1209 unlockResponse_GmRequest(req->req);
1205 } 1210 }
1206 /* Update the link's progress. */ 1211 /* Update the link's progress. */
1207 invalidateLink_DocumentWidget_(d, req->linkId); 1212 invalidateLink_DocumentWidget_(d, req->linkId);
@@ -1482,8 +1487,9 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
1482 return iTrue; 1487 return iTrue;
1483 } 1488 }
1484 else if (equalWidget_Command(cmd, w, "document.request.updated") && 1489 else if (equalWidget_Command(cmd, w, "document.request.updated") &&
1485 pointerLabel_Command(cmd, "request") == d->request) { 1490 d->request && pointerLabel_Command(cmd, "request") == d->request) {
1486 set_Block(&d->sourceContent, body_GmRequest(d->request)); 1491 set_Block(&d->sourceContent, &lockResponse_GmRequest(d->request)->body);
1492 unlockResponse_GmRequest(d->request);
1487 if (document_App() == d) { 1493 if (document_App() == d) {
1488 updateFetchProgress_DocumentWidget_(d); 1494 updateFetchProgress_DocumentWidget_(d);
1489 } 1495 }
@@ -1501,7 +1507,8 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
1501 /* The response may be cached. */ { 1507 /* The response may be cached. */ {
1502 if (!equal_Rangecc(urlScheme_String(d->mod.url), "about") && 1508 if (!equal_Rangecc(urlScheme_String(d->mod.url), "about") &&
1503 startsWithCase_String(meta_GmRequest(d->request), "text/")) { 1509 startsWithCase_String(meta_GmRequest(d->request), "text/")) {
1504 setCachedResponse_History(d->mod.history, response_GmRequest(d->request)); 1510 setCachedResponse_History(d->mod.history, lockResponse_GmRequest(d->request));
1511 unlockResponse_GmRequest(d->request);
1505 } 1512 }
1506 } 1513 }
1507 iReleasePtr(&d->request); 1514 iReleasePtr(&d->request);
@@ -2496,7 +2503,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
2496 topRight_Rect(linkRect), 2503 topRight_Rect(linkRect),
2497 tmInlineContentMetadata_ColorId, 2504 tmInlineContentMetadata_ColorId,
2498 " \u2014 Fetching\u2026 (%.1f MB)", 2505 " \u2014 Fetching\u2026 (%.1f MB)",
2499 (float) size_Block(body_GmRequest(mr->req)) / 1.0e6f); 2506 (float) bodySize_GmRequest(mr->req) / 1.0e6f);
2500 } 2507 }
2501 } 2508 }
2502 else if (isHover) { 2509 else if (isHover) {