From 0f0b4250ca460d58edb61cc0dd509ba1980c3272 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Fri, 24 Jul 2020 13:19:38 +0300 Subject: Added GmRequest for handling the request This feels a little bit too complex, with GmRequest observing TlsRequest and then notifying its own audience. There are still some issues with cancelling requests as well. --- CMakeLists.txt | 8 +- src/gemini.h | 2 + src/gmdocument.c | 4 +- src/gmdocument.h | 2 - src/gmrequest.c | 240 +++++++++++++++++++++++++++++++++++++++ src/gmrequest.h | 23 ++++ src/gmutil.c | 25 ++++ src/gmutil.h | 16 +++ src/ui/documentwidget.c | 295 ++++++++++++++++++++++++++++-------------------- src/ui/window.c | 4 +- 10 files changed, 491 insertions(+), 128 deletions(-) create mode 100644 src/gmrequest.c create mode 100644 src/gmrequest.h create mode 100644 src/gmutil.c create mode 100644 src/gmutil.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 280dcdf8..be56af23 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,8 +33,12 @@ set (SOURCES src/app.c src/app.h src/gemini.h - src/gmdocument.h src/gmdocument.c + src/gmdocument.h + src/gmrequest.c + src/gmrequest.h + src/gmutil.c + src/gmutil.h src/stb_image.h src/stb_truetype.h # User interface: @@ -42,8 +46,8 @@ set (SOURCES src/ui/color.h src/ui/command.c src/ui/command.h - src/ui/documentwidget.h src/ui/documentwidget.c + src/ui/documentwidget.h src/ui/metrics.c src/ui/metrics.h src/ui/paint.c diff --git a/src/gemini.h b/src/gemini.h index dd281e20..3c00d425 100644 --- a/src/gemini.h +++ b/src/gemini.h @@ -2,6 +2,8 @@ /* Response status codes. */ enum iGmStatusCode { + failedToOpenFile_GmStatusCode = -2, + invalidHeader_GmStatusCode = -1, none_GmStatusCode = 0, input_GmStatusCode = 10, sensitiveInput_GmStatusCode = 11, diff --git a/src/gmdocument.c b/src/gmdocument.c index 3dd2d08f..8355a0eb 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -133,11 +133,11 @@ static void clearLinks_GmDocument_(iGmDocument *d) { } static void doLayout_GmDocument_(iGmDocument *d) { + clear_Array(&d->layout); + clearLinks_GmDocument_(d); if (d->size.x <= 0 || isEmpty_String(&d->source)) { return; } - clear_Array(&d->layout); - clearLinks_GmDocument_(d); iBool isPreformat = iFalse; iInt2 pos = zero_I2(); const iRangecc content = range_String(&d->source); diff --git a/src/gmdocument.h b/src/gmdocument.h index b5102044..959c75ea 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h @@ -17,9 +17,7 @@ struct Impl_GmRun { iGmLinkId linkId; /* zero for non-links */ }; -iDeclareType(GmDocument) iDeclareClass(GmDocument) - iDeclareObjectConstruction(GmDocument) void setWidth_GmDocument (iGmDocument *, int width); diff --git a/src/gmrequest.c b/src/gmrequest.c new file mode 100644 index 00000000..2bda65e9 --- /dev/null +++ b/src/gmrequest.c @@ -0,0 +1,240 @@ +#include "gmrequest.h" +#include "gmutil.h" + +#include +#include +#include + +#include + +static const int BODY_TIMEOUT = 1500; /* ms */ + +enum iGmRequestState { + initialized_GmRequestState, + receivingHeader_GmRequestState, + receivingBody_GmRequestState, + finished_GmRequestState +}; + +struct Impl_GmRequest { + iObject object; + iMutex mutex; + enum iGmRequestState state; + iString url; + iTlsRequest *req; + enum iGmStatusCode code; + iString header; + iBlock body; /* rest of the received data */ + uint32_t timeoutId; /* in case server doesn't close the connection */ + iAudience *updated; + iAudience *finished; +}; + +iDefineObjectConstruction(GmRequest) +iDefineAudienceGetter(GmRequest, updated) +iDefineAudienceGetter(GmRequest, finished) + +void init_GmRequest(iGmRequest *d) { + init_Mutex(&d->mutex); + d->state = initialized_GmRequestState; + init_String(&d->url); + d->req = NULL; + d->code = none_GmStatusCode; + init_String(&d->header); + init_Block(&d->body, 0); + d->timeoutId = 0; + d->updated = NULL; + d->finished = NULL; +} + +void deinit_GmRequest(iGmRequest *d) { + lock_Mutex(&d->mutex); + if (d->timeoutId) { + SDL_RemoveTimer(d->timeoutId); + } + if (d->req) { + if (!isFinished_GmRequest(d)) { + iDisconnectObject(TlsRequest, d->req, readyRead, d); + iDisconnectObject(TlsRequest, d->req, finished, d); + cancel_TlsRequest(d->req); + d->state = finished_GmRequestState; + iRelease(d->req); + d->req = NULL; + } + } + unlock_Mutex(&d->mutex); + delete_Audience(d->finished); + delete_Audience(d->updated); + deinit_Block(&d->body); + deinit_String(&d->header); + deinit_String(&d->url); + deinit_Mutex(&d->mutex); +} + +void setUrl_GmRequest(iGmRequest *d, const iString *url) { + set_String(&d->url, url); +} + +static uint32_t timedOutWhileReceivingBody_GmRequest_(uint32_t interval, void *obj) { + iGmRequest *d = obj; + iGuardMutex(&d->mutex, cancel_TlsRequest(d->req)); + iUnused(interval); + return 0; +} + +static void restartTimeout_GmRequest_(iGmRequest *d) { + /* Note: `d` is currently locked. */ + if (d->timeoutId) { + SDL_RemoveTimer(d->timeoutId); + } + d->timeoutId = SDL_AddTimer(BODY_TIMEOUT, timedOutWhileReceivingBody_GmRequest_, d); +} + +static void readIncoming_GmRequest_(iAnyObject *obj) { + iGmRequest *d = (iGmRequest *) obj; + iBool notifyUpdate = iFalse; + iBool notifyDone = iFalse; + lock_Mutex(&d->mutex); + iAssert(d->state != finished_GmRequestState); /* notifications out of order? */ + iBlock *data = readAll_TlsRequest(d->req); + fflush(stdout); + if (d->state == receivingHeader_GmRequestState) { + appendCStrN_String(&d->header, constData_Block(data), size_Block(data)); + /* Check if the header line is complete. */ + size_t endPos = indexOfCStr_String(&d->header, "\r\n"); + if (endPos != iInvalidPos) { + /* Move remainder to the body. */ + setData_Block(&d->body, + constBegin_String(&d->header) + endPos + 2, + size_String(&d->header) - endPos - 2); + remove_Block(&d->header.chars, endPos, iInvalidSize); + /* parse and remove the code */ + if (size_String(&d->header) < 3) { + clear_String(&d->header); + d->code = invalidHeader_GmStatusCode; + d->state = finished_GmRequestState; + notifyDone = iTrue; + } + const int code = toInt_String(&d->header); + if (code == 0 || cstr_String(&d->header)[2] != ' ') { + clear_String(&d->header); + d->code = invalidHeader_GmStatusCode; + d->state = finished_GmRequestState; + notifyDone = iTrue; + } + remove_Block(&d->header.chars, 0, 3); /* just the meta */ + d->code = code; + d->state = receivingBody_GmRequestState; + notifyUpdate = iTrue; + /* Start a timeout for the remainder of the response, in case the connection + remains open. */ + restartTimeout_GmRequest_(d); + } + } + else if (d->state == receivingBody_GmRequestState) { + append_Block(&d->body, data); + restartTimeout_GmRequest_(d); + notifyUpdate = iTrue; + } + delete_Block(data); + unlock_Mutex(&d->mutex); + if (notifyUpdate) { + iNotifyAudience(d, updated, GmRequestUpdated); + } + if (notifyDone) { + iNotifyAudience(d, finished, GmRequestFinished); + } +} + +static void requestFinished_GmRequest_(iAnyObject *obj) { + iGmRequest *d = (iGmRequest *) obj; + lock_Mutex(&d->mutex); + /* There shouldn't be anything left to read. */ { + iBlock *data = readAll_TlsRequest(d->req); + iAssert(isEmpty_Block(data)); + delete_Block(data); + } + SDL_RemoveTimer(d->timeoutId); + d->timeoutId = 0; + iReleaseLater(d->req); + d->req = NULL; + d->state = finished_GmRequestState; + unlock_Mutex(&d->mutex); + iNotifyAudience(d, finished, GmRequestFinished); +} + +void submit_GmRequest(iGmRequest *d) { + iAssert(d->state == initialized_GmRequestState); + if (d->state != initialized_GmRequestState) { + return; + } + d->code = none_GmStatusCode; + clear_String(&d->header); + clear_Block(&d->body); + iUrl url; + init_Url(&url, &d->url); + if (!cmpCStrSc_Rangecc(&url.protocol, "file", &iCaseInsensitive)) { + iFile *f = new_File(collect_String(newRange_String(url.path))); + if (open_File(f, readOnly_FileMode)) { + /* TODO: Check supported file types: images, audio */ + d->code = success_GmStatusCode; + setCStr_String(&d->header, "text/gemini; charset=utf-8"); + set_Block(&d->body, collect_Block(readAll_File(f))); + iNotifyAudience(d, updated, GmRequestUpdated); + } + else { + d->code = failedToOpenFile_GmStatusCode; + } + iRelease(f); + d->state = finished_GmRequestState; + iNotifyAudience(d, finished, GmRequestFinished); + return; + } + d->state = receivingHeader_GmRequestState; + d->req = new_TlsRequest(); + iConnect(TlsRequest, d->req, readyRead, d, readIncoming_GmRequest_); + iConnect(TlsRequest, d->req, finished, d, requestFinished_GmRequest_); + uint16_t port = toInt_String(collect_String(newRange_String(url.port))); + if (port == 0) { + port = 1965; /* default Gemini port */ + } + setUrl_TlsRequest(d->req, collect_String(newRange_String(url.host)), port); + setContent_TlsRequest(d->req, + utf8_String(collectNewFormat_String("%s\r\n", cstr_String(&d->url)))); + submit_TlsRequest(d->req); +} + +iBool isFinished_GmRequest(const iGmRequest *d) { + iBool done; + iGuardMutex(&d->mutex, done = (d->state == finished_GmRequestState)); + return done; +} + +const char *error_GmRequest(const iGmRequest *d) { + if (d->code == failedToOpenFile_GmStatusCode) { + return "Failed to open file"; + } + if (d->code == invalidHeader_GmStatusCode) { + return "Received invalid header (not Gemini?)"; + } + return NULL; /* TDOO: detailed error string */ +} + +enum iGmStatusCode status_GmRequest(const iGmRequest *d) { + return d->code; +} + +const iString *meta_GmRequest(const iGmRequest *d) { + if (d->state >= receivingBody_GmRequestState) { + return &d->header; + } + return collectNew_String(); +} + +const iBlock *body_GmRequest(const iGmRequest *d) { + iBlock *body; + iGuardMutex(&d->mutex, body = collect_Block(copy_Block(&d->body))); + return body; +} + +iDefineClass(GmRequest) diff --git a/src/gmrequest.h b/src/gmrequest.h new file mode 100644 index 00000000..e7d5916b --- /dev/null +++ b/src/gmrequest.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +#include "gemini.h" + +iDeclareClass(GmRequest) +iDeclareObjectConstruction(GmRequest) + +iDeclareNotifyFunc(GmRequest, Updated) +iDeclareNotifyFunc(GmRequest, Finished) +iDeclareAudienceGetter(GmRequest, updated) +iDeclareAudienceGetter(GmRequest, finished) + +void setUrl_GmRequest (iGmRequest *, const iString *url); +void submit_GmRequest (iGmRequest *); + +iBool isFinished_GmRequest (const iGmRequest *); +const char * error_GmRequest (const iGmRequest *); /* NULL if successful */ +enum iGmStatusCode status_GmRequest (const iGmRequest *); +const iString * meta_GmRequest (const iGmRequest *); +const iBlock * body_GmRequest (const iGmRequest *); diff --git a/src/gmutil.c b/src/gmutil.c new file mode 100644 index 00000000..07861523 --- /dev/null +++ b/src/gmutil.c @@ -0,0 +1,25 @@ +#include "gmutil.h" + +#include +#include + +void init_Url(iUrl *d, const iString *text) { + iRegExp *pattern = + new_RegExp("(.+)://([^/:?]*)(:[0-9]+)?([^?]*)(\\?.*)?", caseInsensitive_RegExpOption); + iRegExpMatch m; + if (matchString_RegExp(pattern, text, &m)) { + d->protocol = capturedRange_RegExpMatch(&m, 1); + d->host = capturedRange_RegExpMatch(&m, 2); + d->port = capturedRange_RegExpMatch(&m, 3); + if (!isEmpty_Range(&d->port)) { + /* Don't include the colon. */ + d->port.start++; + } + d->path = capturedRange_RegExpMatch(&m, 4); + d->query = capturedRange_RegExpMatch(&m, 5); + } + else { + iZap(*d); + } + iRelease(pattern); +} diff --git a/src/gmutil.h b/src/gmutil.h new file mode 100644 index 00000000..264ad8a8 --- /dev/null +++ b/src/gmutil.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +iDeclareType(Url) + +struct Impl_Url { + iRangecc protocol; + iRangecc host; + iRangecc port; + iRangecc path; + iRangecc query; +}; + +void init_Url(iUrl *d, const iString *text); diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 71399d3b..121ca7cf 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -6,16 +6,20 @@ #include "app.h" #include "../gemini.h" #include "../gmdocument.h" +#include "../gmrequest.h" +#include "../gmutil.h" #include #include #include #include -#include + +#include enum iDocumentState { blank_DocumentState, fetching_DocumentState, + receivedPartialResponse_DocumentState, layout_DocumentState, ready_DocumentState, }; @@ -24,9 +28,8 @@ struct Impl_DocumentWidget { iWidget widget; enum iDocumentState state; iString *url; - iTlsRequest *request; - int statusCode; - iString *newSource; + iGmRequest *request; + iAtomicInt isSourcePending; /* request has new content, need to parse it */ iGmDocument *doc; int pageMargin; int scrollY; @@ -36,37 +39,6 @@ struct Impl_DocumentWidget { iScrollWidget *scroll; }; -iDeclareType(Url) - -struct Impl_Url { - iRangecc protocol; - iRangecc host; - iRangecc port; - iRangecc path; - iRangecc query; -}; - -void init_Url(iUrl *d, const iString *text) { - iRegExp *pattern = - new_RegExp("(.+)://([^/:?]*)(:[0-9]+)?([^?]*)(\\?.*)?", caseInsensitive_RegExpOption); - iRegExpMatch m; - if (matchString_RegExp(pattern, text, &m)) { - d->protocol = capturedRange_RegExpMatch(&m, 1); - d->host = capturedRange_RegExpMatch(&m, 2); - d->port = capturedRange_RegExpMatch(&m, 3); - if (!isEmpty_Range(&d->port)) { - /* Don't include the colon. */ - d->port.start++; - } - d->path = capturedRange_RegExpMatch(&m, 4); - d->query = capturedRange_RegExpMatch(&m, 5); - } - else { - iZap(*d); - } - iRelease(pattern); -} - iDefineObjectConstruction(DocumentWidget) void init_DocumentWidget(iDocumentWidget *d) { @@ -75,9 +47,13 @@ void init_DocumentWidget(iDocumentWidget *d) { setId_Widget(w, "document"); d->state = blank_DocumentState; d->url = new_String(); - d->statusCode = 0; +// d->statusCode = 0; d->request = NULL; - d->newSource = new_String(); + d->isSourcePending = iFalse; +// d->requestTimeout = 0; +// d->readPending = iFalse; +// d->newSource = new_String(); +// d->needSourceUpdate = iFalse; d->doc = new_GmDocument(); d->pageMargin = 5; d->scrollY = 0; @@ -90,7 +66,8 @@ void init_DocumentWidget(iDocumentWidget *d) { void deinit_DocumentWidget(iDocumentWidget *d) { deinit_PtrArray(&d->visibleLinks); delete_String(d->url); - delete_String(d->newSource); +// delete_String(d->newSource); + iRelease(d->request); iRelease(d->doc); } @@ -111,90 +88,63 @@ static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) { return rect; } -#if 0 -void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { - /* TODO: lock source during update */ - setSource_GmDocument(d->doc, source, documentWidth_DocumentWidget_(d)); - d->state = ready_DocumentState; -} -#endif - static iRangecc getLine_(iRangecc text) { iRangecc line = { text.start, text.start }; for (; *line.end != '\n' && line.end != text.end; line.end++) {} return line; } -static void requestFinished_DocumentWidget_(iAnyObject *obj) { +static void requestUpdated_DocumentWidget_(iAnyObject *obj) { +#if 0 iDocumentWidget *d = obj; iBlock *response = readAll_TlsRequest(d->request); - iRangecc responseRange = { constBegin_Block(response), constEnd_Block(response) }; - iRangecc respLine = getLine_(responseRange); - responseRange.start = respLine.end + 1; - /* First line is the status code. */ { - iString *line = newRange_String(respLine); - trim_String(line); - d->statusCode = toInt_String(line); - printf("response (%02d): %s\n", d->statusCode, cstr_String(line)); - /* TODO: post a command with the status code */ - switch (d->statusCode) { - case redirectPermanent_GmStatusCode: - case redirectTemporary_GmStatusCode: - postCommandf_App("open url:%s", cstr_String(line) + 3); - break; + if (d->state == fetching_DocumentState) { + iRangecc responseRange = { constBegin_Block(response), constEnd_Block(response) }; + iRangecc respLine = getLine_(responseRange); + responseRange.start = respLine.end + 1; + /* First line is the status code. */ { + iString *line = newRange_String(respLine); + trim_String(line); + d->statusCode = toInt_String(line); + printf("response (%02d): %s\n", d->statusCode, cstr_String(line)); + /* TODO: post a command with the status code */ + switch (d->statusCode) { + case redirectPermanent_GmStatusCode: + case redirectTemporary_GmStatusCode: + postCommandf_App("open url:%s", cstr_String(line) + 3); + break; + } + delete_String(line); } - delete_String(line); + setCStrN_String(d->newSource, responseRange.start, size_Range(&responseRange)); + d->requestTimeout = SDL_AddTimer(2000, requestTimedOut_DocumentWidget_, d); + d->state = receivedPartialResponse_DocumentState; } - setCStrN_String(d->newSource, responseRange.start, size_Range(&responseRange)); - delete_Block(response); - iReleaseLater(d->request); - d->request = NULL; - fflush(stdout); - refresh_Widget(constAs_Widget(d)); -} - -static void fetch_DocumentWidget_(iDocumentWidget *d) { - iAssert(!d->request); - d->state = fetching_DocumentState; - d->statusCode = 0; - iUrl url; - init_Url(&url, d->url); - if (!cmpCStrSc_Rangecc(&url.protocol, "file", &iCaseInsensitive)) { - iFile *f = new_File(collect_String(newRange_String(url.path))); - if (open_File(f, readOnly_FileMode)) { - setBlock_String(d->newSource, collect_Block(readAll_File(f))); - refresh_Widget(constAs_Widget(d)); - } - iRelease(f); - return; + else if (d->state == receivedPartialResponse_DocumentState) { + appendCStr_String(d->newSource, cstr_Block(response)); + d->needSourceUpdate = iTrue; } - d->request = new_TlsRequest(); - uint16_t port = toInt_String(collect_String(newRange_String(url.port))); - if (port == 0) { - port = 1965; /* default Gemini port */ + delete_Block(response); + refresh_Widget(as_Widget(d)); +#endif + iDocumentWidget *d = obj; + const int wasPending = exchange_Atomic(&d->isSourcePending, iTrue); + if (!wasPending) { + postCommand_Widget(obj, "document.request.updated request:%p", d->request); } - setUrl_TlsRequest(d->request, collect_String(newRange_String(url.host)), port); - /* The request string is an UTF-8 encoded absolute URL. */ - iString *content = collectNew_String(); - append_String(content, d->url); - appendCStr_String(content, "\r\n"); - setContent_TlsRequest(d->request, utf8_String(content)); - iConnect(TlsRequest, d->request, finished, d, requestFinished_DocumentWidget_); - submit_TlsRequest(d->request); } -void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) { - iString *newUrl = new_String(); - if (indexOfCStr_String(url, "://") == iInvalidPos && !startsWithCase_String(url, "gemini:")) { - /* Prepend default protocol. */ - setCStr_String(newUrl, "gemini://"); - } - append_String(newUrl, url); - if (cmpStringSc_String(d->url, newUrl, &iCaseInsensitive)) { - set_String(d->url, newUrl); - fetch_DocumentWidget_(d); +static void requestFinished_DocumentWidget_(iAnyObject *obj) { + iDocumentWidget *d = obj; + /* + iReleaseLater(d->request); + d->request = NULL; + if (d->requestTimeout) { + SDL_RemoveTimer(d->requestTimeout); + d->requestTimeout = 0; } - delete_String(newUrl); + refresh_Widget(constAs_Widget(d));*/ + postCommand_Widget(obj, "document.request.finished request:%p", d->request); } static iRangei visibleRange_DocumentWidget_(const iDocumentWidget *d) { @@ -227,6 +177,54 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) { render_GmDocument(d->doc, visRange, addVisibleLink_DocumentWidget_, d); } +static void updateSource_DocumentWidget_(iDocumentWidget *d) { + /* Update the document? */ + // if (d->needSourceUpdate) { + /* TODO: Do this in the background. However, that requires a text metrics calculator + that does not try to cache the glyph bitmaps. */ + iString str; + initBlock_String(&str, body_GmRequest(d->request)); + setSource_GmDocument(d->doc, &str, documentWidth_DocumentWidget_(d)); + deinit_String(&str); + updateVisible_DocumentWidget_(d); + refresh_Widget(as_Widget(d)); + // d->state = ready_DocumentState; + // if (!d->request) { + // d->needSourceUpdate = iFalse; + // postCommandf_App("document.changed url:%s", cstr_String(d->url)); + // } + // } +} + +static void fetch_DocumentWidget_(iDocumentWidget *d) { + /* Forget the previous request. */ + if (d->request) { + iRelease(d->request); + d->request = NULL; + } + d->state = fetching_DocumentState; + set_Atomic(&d->isSourcePending, iFalse); + d->request = new_GmRequest(); + setUrl_GmRequest(d->request, d->url); + iConnect(GmRequest, d->request, updated, d, requestUpdated_DocumentWidget_); + iConnect(GmRequest, d->request, finished, d, requestFinished_DocumentWidget_); + submit_GmRequest(d->request); +} + +void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) { + iString *newUrl = new_String(); + if (indexOfCStr_String(url, "://") == iInvalidPos && !startsWithCase_String(url, "gemini:")) { + /* Prepend default protocol. */ + setCStr_String(newUrl, "gemini://"); + } + append_String(newUrl, url); + if (cmpStringSc_String(d->url, newUrl, &iCaseInsensitive)) { + set_String(d->url, newUrl); + fetch_DocumentWidget_(d); + } + delete_String(newUrl); +} + static void scroll_DocumentWidget_(iDocumentWidget *d, int offset) { d->scrollY += offset; if (d->scrollY < 0) { @@ -296,6 +294,61 @@ static const iString *absoluteUrl_DocumentWidget_(const iDocumentWidget *d, cons return collect_String(absolute); } +static void readResponse_DocumentWidget_(iDocumentWidget *d) { +#if 0 + d->readPending = iFalse; + iBlock *response = collect_Block(readAll_TlsRequest(d->request)); + if (isEmpty_Block(response)) { + return; + } + if (d->state == fetching_DocumentState) { + /* TODO: Bug here is that the first read may occur before the first line is + available, so nothing gets done. Should ensure that the status code is + read successully. */ + iRangecc responseRange = { constBegin_Block(response), constEnd_Block(response) }; + iRangecc respLine = getLine_(responseRange); + responseRange.start = respLine.end + 1; + /* First line is the status code. */ { + iString *line = collect_String(newRange_String(respLine)); + trim_String(line); + d->statusCode = toInt_String(line); + printf("response (%02d): %s\n", d->statusCode, cstr_String(line)); + /* TODO: post a command with the status code */ + switch (d->statusCode) { + case redirectPermanent_GmStatusCode: + case redirectTemporary_GmStatusCode: + postCommandf_App("open url:%s", cstr_String(line) + 3); + return; + } + } + setCStrN_String(d->newSource, responseRange.start, size_Range(&responseRange)); + d->requestTimeout = SDL_AddTimer(2000, requestTimedOut_DocumentWidget_, d); + d->state = receivedPartialResponse_DocumentState; + d->scrollY = 0; + } + else if (d->state == receivedPartialResponse_DocumentState) { + appendCStr_String(d->newSource, cstr_Block(response)); + } +#endif + updateSource_DocumentWidget_(d); +} + +static void checkResponseCode_DocumentWidget_(iDocumentWidget *d) { + if (d->state == fetching_DocumentState) { + d->state = receivedPartialResponse_DocumentState; + d->scrollY = 0; + switch (status_GmRequest(d->request)) { + case redirectTemporary_GmStatusCode: + case redirectPermanent_GmStatusCode: + postCommandf_App("open url:%s", cstr_String(meta_GmRequest(d->request))); + iReleasePtr(&d->request); + break; + default: + break; + } + } +} + static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) { iWidget *w = as_Widget(d); if (isResize_UserEvent(ev)) { @@ -303,6 +356,20 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e updateVisible_DocumentWidget_(d); refresh_Widget(w); } + else if (isCommand_Widget(w, ev, "document.request.updated") && + pointerLabel_Command(command_UserEvent(ev), "request") == d->request) { + updateSource_DocumentWidget_(d); + checkResponseCode_DocumentWidget_(d); + return iTrue; + } + else if (isCommand_Widget(w, ev, "document.request.finished") && + pointerLabel_Command(command_UserEvent(ev), "request") == d->request) { + updateSource_DocumentWidget_(d); + checkResponseCode_DocumentWidget_(d); + d->state = ready_DocumentState; + iReleasePtr(&d->request); + return iTrue; + } else if (isCommand_Widget(w, ev, "scroll.moved")) { d->scrollY = arg_Command(command_UserEvent(ev)); updateVisible_DocumentWidget_(d); @@ -417,19 +484,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { iDrawContext ctx = { .widget = d, .bounds = documentBounds_DocumentWidget_(d) }; init_Paint(&ctx.paint); fillRect_Paint(&ctx.paint, bounds, gray25_ColorId); - /* Update the document? */ - if (!isEmpty_String(d->newSource)) { - iDocumentWidget *m = iConstCast(iDocumentWidget *, d); - /* TODO: Do this in the background. However, that requires a text metrics calculator - that does not try to cache the glyph bitmaps. */ - setSource_GmDocument(m->doc, m->newSource, width_Rect(ctx.bounds)); - postCommandf_App("document.changed url:%s", cstr_String(d->url)); - clear_String(m->newSource); - m->scrollY = 0; - m->state = ready_DocumentState; - updateVisible_DocumentWidget_(m); - } - if (d->state != ready_DocumentState) return; +// if (d->state != ready_DocumentState) return; setClip_Paint(&ctx.paint, bounds); render_GmDocument(d->doc, visibleRange_DocumentWidget_(d), drawRun_DrawContext_, &ctx); clearClip_Paint(&ctx.paint); diff --git a/src/ui/window.c b/src/ui/window.c index 43233010..2cd30459 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -49,7 +49,7 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) { } return iTrue; } - else if (equal_Command(cmd, "setfocus")) { + else if (equal_Command(cmd, "focus.set")) { setFocus_Widget(findWidget_App(cstr_String(string_Command(cmd, "id")))); return iTrue; } @@ -273,7 +273,7 @@ static void setupUserInterface_Window(iWindow *d) { #endif /* Glboal keyboard shortcuts. */ { // addAction_Widget(d->root, SDLK_LEFTBRACKET, KMOD_SHIFT | KMOD_PRIMARY, "tabs.prev"); - addAction_Widget(d->root, 'l', KMOD_PRIMARY, "setfocus id:url"); + addAction_Widget(d->root, 'l', KMOD_PRIMARY, "focus.set id:url"); } } -- cgit v1.2.3