summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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) {