summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/app.c6
-rw-r--r--src/gmrequest.c4
-rw-r--r--src/history.c272
-rw-r--r--src/history.h55
-rw-r--r--src/ui/documentwidget.c124
-rw-r--r--src/ui/documentwidget.h1
6 files changed, 269 insertions, 193 deletions
diff --git a/src/app.c b/src/app.c
index 466a18f4..3aba28b6 100644
--- a/src/app.c
+++ b/src/app.c
@@ -340,7 +340,8 @@ iBool handleCommand_App(const char *cmd) {
340 openInDefaultBrowser_App(url); 340 openInDefaultBrowser_App(url);
341 return iTrue; 341 return iTrue;
342 } 342 }
343 if (!argLabel_Command(cmd, "history")) { 343 const iBool isHistory = argLabel_Command(cmd, "history") != 0;
344 if (!isHistory) {
344 if (argLabel_Command(cmd, "redirect")) { 345 if (argLabel_Command(cmd, "redirect")) {
345 replace_History(d->history, url); 346 replace_History(d->history, url);
346 } 347 }
@@ -348,10 +349,9 @@ iBool handleCommand_App(const char *cmd) {
348 addUrl_History(d->history, url); 349 addUrl_History(d->history, url);
349 } 350 }
350 } 351 }
351 print_History(d->history);
352 iDocumentWidget *doc = findChild_Widget(root, "document"); 352 iDocumentWidget *doc = findChild_Widget(root, "document");
353 setUrl_DocumentWidget(doc, url);
354 setInitialScroll_DocumentWidget(doc, argLabel_Command(cmd, "scroll") * gap_UI); 353 setInitialScroll_DocumentWidget(doc, argLabel_Command(cmd, "scroll") * gap_UI);
354 setUrlFromCache_DocumentWidget(doc, url, isHistory);
355 } 355 }
356 else if (equal_Command(cmd, "document.request.cancelled")) { 356 else if (equal_Command(cmd, "document.request.cancelled")) {
357 /* TODO: How should cancelled requests be treated in the history? */ 357 /* TODO: How should cancelled requests be treated in the history? */
diff --git a/src/gmrequest.c b/src/gmrequest.c
index 692267a5..d2683135 100644
--- a/src/gmrequest.c
+++ b/src/gmrequest.c
@@ -9,6 +9,8 @@
9 9
10#include <SDL_timer.h> 10#include <SDL_timer.h>
11 11
12iDefineTypeConstruction(GmResponse)
13
12void init_GmResponse(iGmResponse *d) { 14void init_GmResponse(iGmResponse *d) {
13 d->statusCode = none_GmStatusCode; 15 d->statusCode = none_GmStatusCode;
14 init_String(&d->meta); 16 init_String(&d->meta);
@@ -343,7 +345,7 @@ const iString *url_GmRequest(const iGmRequest *d) {
343} 345}
344 346
345const iGmResponse *response_GmRequest(const iGmRequest *d) { 347const iGmResponse *response_GmRequest(const iGmRequest *d) {
346 iAssert(d->state == finished_GmRequestState); 348 iAssert(d->state != initialized_GmRequestState);
347 return &d->resp; 349 return &d->resp;
348} 350}
349 351
diff --git a/src/history.c b/src/history.c
index de0c36ca..a2c4271f 100644
--- a/src/history.c
+++ b/src/history.c
@@ -8,136 +8,165 @@
8static const size_t maxStack_History_ = 50; /* back/forward navigable items */ 8static const size_t maxStack_History_ = 50; /* back/forward navigable items */
9static const size_t maxAgeVisited_History_ = 3600 * 24 * 30; /* one month */ 9static const size_t maxAgeVisited_History_ = 3600 * 24 * 30; /* one month */
10 10
11void init_HistoryItem(iHistoryItem *d) { 11void init_RecentUrl(iRecentUrl *d) {
12 initCurrent_Time(&d->when);
13 init_String(&d->url); 12 init_String(&d->url);
14 d->scrollY = 0; 13 d->scrollY = 0;
14 d->cachedResponse = NULL;
15} 15}
16 16
17void deinit_HistoryItem(iHistoryItem *d) { 17void deinit_RecentUrl(iRecentUrl *d) {
18 deinit_String(&d->url); 18 deinit_String(&d->url);
19 delete_GmResponse(d->cachedResponse);
19} 20}
20 21
21struct Impl_History { 22void init_VisitedUrl(iVisitedUrl *d) {
22 iArray stack; /* TODO: should be specific to a DocumentWidget */ 23 initCurrent_Time(&d->when);
23 size_t stackPos; /* zero at the latest item */ 24 init_String(&d->url);
24 iSortedArray visitedUrls; 25}
25};
26 26
27iDefineTypeConstruction(History) 27void deinit_VisitedUrl(iVisitedUrl *d) {
28 deinit_String(&d->url);
29}
28 30
29static int cmpUrl_HistoryItem_(const void *a, const void *b) { 31static int cmpUrl_VisitedUrl_(const void *a, const void *b) {
30 return cmpString_String(&((const iHistoryItem *) a)->url, &((const iHistoryItem *) b)->url); 32 return cmpString_String(&((const iVisitedUrl *) a)->url, &((const iVisitedUrl *) b)->url);
31} 33}
32 34
33static int cmpNewer_HistoryItem_(const void *insert, const void *existing) { 35static int cmpNewer_VisitedUrl_(const void *insert, const void *existing) {
34 return seconds_Time(&((const iHistoryItem *) insert )->when) > 36 return seconds_Time(&((const iVisitedUrl *) insert )->when) >
35 seconds_Time(&((const iHistoryItem *) existing)->when); 37 seconds_Time(&((const iVisitedUrl *) existing)->when);
36} 38}
37 39
40/*----------------------------------------------------------------------------------------------*/
41
42struct Impl_History {
43 iArray recent; /* TODO: should be specific to a DocumentWidget */
44 size_t recentPos; /* zero at the latest item */
45 iSortedArray visited;
46};
47
48iDefineTypeConstruction(History)
49
38void init_History(iHistory *d) { 50void init_History(iHistory *d) {
39 init_Array(&d->stack, sizeof(iHistoryItem)); 51 init_Array(&d->recent, sizeof(iRecentUrl));
40 d->stackPos = 0; 52 d->recentPos = 0;
41 init_SortedArray(&d->visitedUrls, sizeof(iHistoryItem), cmpUrl_HistoryItem_); 53 init_SortedArray(&d->visited, sizeof(iVisitedUrl), cmpUrl_VisitedUrl_);
42} 54}
43 55
44void deinit_History(iHistory *d) { 56void deinit_History(iHistory *d) {
45 clear_History(d); 57 clear_History(d);
46 deinit_Array(&d->stack); 58 deinit_Array(&d->recent);
47} 59 deinit_SortedArray(&d->visited);
48
49static void writeItems_(const iArray *items, iFile *f) {
50 iString *line = new_String();
51 iConstForEach(Array, i, items) {
52 const iHistoryItem *item = i.value;
53 iDate date;
54 init_Date(&date, &item->when);
55 format_String(line,
56 "%04d-%02d-%02dT%02d:%02d:%02d %04x %s\n",
57 date.year,
58 date.month,
59 date.day,
60 date.hour,
61 date.minute,
62 date.second,
63 item->scrollY,
64 cstr_String(&item->url));
65 writeData_File(f, cstr_String(line), size_String(line));
66 }
67 delete_String(line);
68} 60}
69 61
70void save_History(const iHistory *d, const char *dirPath) { 62void save_History(const iHistory *d, const char *dirPath) {
63 iString *line = new_String();
71 iFile *f = newCStr_File(concatPath_CStr(dirPath, "recent.txt")); 64 iFile *f = newCStr_File(concatPath_CStr(dirPath, "recent.txt"));
72 if (open_File(f, writeOnly_FileMode | text_FileMode)) { 65 if (open_File(f, writeOnly_FileMode | text_FileMode)) {
73 writeItems_(&d->stack, f); 66 iConstForEach(Array, i, &d->recent) {
67 const iRecentUrl *item = i.value;
68 format_String(line, "%04x %s\n", item->scrollY, cstr_String(&item->url));
69 writeData_File(f, cstr_String(line), size_String(line));
70 }
74 } 71 }
75 iRelease(f); 72 iRelease(f);
76 f = newCStr_File(concatPath_CStr(dirPath, "visited.txt")); 73 f = newCStr_File(concatPath_CStr(dirPath, "visited.txt"));
77 if (open_File(f, writeOnly_FileMode | text_FileMode)) { 74 if (open_File(f, writeOnly_FileMode | text_FileMode)) {
78 writeItems_(&d->visitedUrls.values, f); 75 iConstForEach(Array, i, &d->visited.values) {
79 } 76 const iVisitedUrl *item = i.value;
80 iRelease(f); 77 iDate date;
81} 78 init_Date(&date, &item->when);
82 79 format_String(line,
83static void loadItems_(iArray *items, iFile *f, double maxAge) { 80 "%04d-%02d-%02dT%02d:%02d:%02d %s\n",
84 const iRangecc src = range_Block(collect_Block(readAll_File(f))); 81 date.year,
85 iRangecc line = iNullRange; 82 date.month,
86 iTime now; 83 date.day,
87 initCurrent_Time(&now); 84 date.hour,
88 while (nextSplit_Rangecc(&src, "\n", &line)) { 85 date.minute,
89 int y, m, D, H, M, S, scroll = 0; 86 date.second,
90 sscanf(line.start, "%04d-%02d-%02dT%02d:%02d:%02d %04x", &y, &m, &D, &H, &M, &S, &scroll); 87 cstr_String(&item->url));
91 if (!y) break; 88 writeData_File(f, cstr_String(line), size_String(line));
92 iHistoryItem item;
93 init_HistoryItem(&item);
94 item.scrollY = scroll;
95 init_Time(
96 &item.when,
97 &(iDate){ .year = y, .month = m, .day = D, .hour = H, .minute = M, .second = S });
98 if (maxAge > 0.0 && secondsSince_Time(&now, &item.when) > maxAge) {
99 continue; /* Too old. */
100 } 89 }
101 initRange_String(&item.url, (iRangecc){ line.start + 25, line.end });
102 pushBack_Array(items, &item);
103 } 90 }
91 iRelease(f);
92 delete_String(line);
104} 93}
105 94
106void load_History(iHistory *d, const char *dirPath) { 95void load_History(iHistory *d, const char *dirPath) {
107 iFile *f = newCStr_File(concatPath_CStr(dirPath, "recent.txt")); 96 iFile *f = newCStr_File(concatPath_CStr(dirPath, "recent.txt"));
108 if (open_File(f, readOnly_FileMode | text_FileMode)) { 97 if (open_File(f, readOnly_FileMode | text_FileMode)) {
109 loadItems_(&d->stack, f, 0); 98 const iRangecc src = range_Block(collect_Block(readAll_File(f)));
99 iRangecc line = iNullRange;
100 while (nextSplit_Rangecc(&src, "\n", &line)) {
101 iRangecc nonwhite = line;
102 trim_Rangecc(&nonwhite);
103 if (isEmpty_Range(&nonwhite)) continue;
104 int scroll = 0;
105 sscanf(nonwhite.start, "%04x", &scroll);
106 iRecentUrl item;
107 init_RecentUrl(&item);
108 item.scrollY = scroll;
109 initRange_String(&item.url, (iRangecc){ nonwhite.start + 5, nonwhite.end });
110 pushBack_Array(&d->recent, &item);
111 }
110 } 112 }
111 iRelease(f); 113 iRelease(f);
112 f = newCStr_File(concatPath_CStr(dirPath, "visited.txt")); 114 f = newCStr_File(concatPath_CStr(dirPath, "visited.txt"));
113 if (open_File(f, readOnly_FileMode | text_FileMode)) { 115 if (open_File(f, readOnly_FileMode | text_FileMode)) {
114 loadItems_(&d->visitedUrls.values, f, maxAgeVisited_History_); 116 const iRangecc src = range_Block(collect_Block(readAll_File(f)));
117 iRangecc line = iNullRange;
118 iTime now;
119 initCurrent_Time(&now);
120 while (nextSplit_Rangecc(&src, "\n", &line)) {
121 int y, m, D, H, M, S;
122 sscanf(line.start, "%04d-%02d-%02dT%02d:%02d:%02d ", &y, &m, &D, &H, &M, &S);
123 if (!y) break;
124 iVisitedUrl item;
125 init_VisitedUrl(&item);
126 init_Time(
127 &item.when,
128 &(iDate){ .year = y, .month = m, .day = D, .hour = H, .minute = M, .second = S });
129 if (secondsSince_Time(&now, &item.when) > maxAgeVisited_History_) {
130 continue; /* Too old. */
131 }
132 initRange_String(&item.url, (iRangecc){ line.start + 20, line.end });
133 insert_SortedArray(&d->visited, &item);
134 }
115 } 135 }
116 iRelease(f); 136 iRelease(f);
117} 137}
118 138
119void clear_History(iHistory *d) { 139void clear_History(iHistory *d) {
120 iForEach(Array, s, &d->stack) { 140 iForEach(Array, s, &d->recent) {
121 deinit_HistoryItem(s.value); 141 deinit_RecentUrl(s.value);
122 } 142 }
123 clear_Array(&d->stack); 143 clear_Array(&d->recent);
124 iForEach(Array, v, &d->visitedUrls.values) { 144 iForEach(Array, v, &d->visited.values) {
125 deinit_HistoryItem(v.value); 145 deinit_VisitedUrl(v.value);
126 } 146 }
127 clear_SortedArray(&d->visitedUrls); 147 clear_SortedArray(&d->visited);
128} 148}
129 149
130iHistoryItem *itemAtPos_History(iHistory *d, size_t pos) { 150iRecentUrl *recentUrl_History(iHistory *d, size_t pos) {
131 if (isEmpty_Array(&d->stack)) return NULL; 151 if (isEmpty_Array(&d->recent)) return NULL;
132 return &value_Array(&d->stack, size_Array(&d->stack) - 1 - pos, iHistoryItem); 152 return &value_Array(&d->recent, size_Array(&d->recent) - 1 - pos, iRecentUrl);
133} 153}
134 154
135iHistoryItem *item_History(iHistory *d) { 155const iRecentUrl *constRecentUrl_History(const iHistory *d, size_t pos) {
136 return itemAtPos_History(d, d->stackPos); 156 if (isEmpty_Array(&d->recent)) return NULL;
157 return &constValue_Array(&d->recent, size_Array(&d->recent) - 1 - pos, iRecentUrl);
137} 158}
138 159
139const iString *url_History(iHistory *d, size_t pos) { 160iRecentUrl *mostRecentUrl_History(iHistory *d) {
140 const iHistoryItem *item = itemAtPos_History(d, pos); 161 return recentUrl_History(d, d->recentPos);
162}
163
164const iRecentUrl *constMostRecentUrl_History(const iHistory *d) {
165 return constRecentUrl_History(d, d->recentPos);
166}
167
168const iString *url_History(const iHistory *d, size_t pos) {
169 const iRecentUrl *item = constRecentUrl_History(d, pos);
141 if (item) { 170 if (item) {
142 return &item->url; 171 return &item->url;
143 } 172 }
@@ -145,24 +174,24 @@ const iString *url_History(iHistory *d, size_t pos) {
145} 174}
146 175
147static void addVisited_History_(iHistory *d, const iString *url) { 176static void addVisited_History_(iHistory *d, const iString *url) {
148 iHistoryItem visit; 177 iVisitedUrl visit;
149 init_HistoryItem(&visit); 178 init_VisitedUrl(&visit);
150 set_String(&visit.url, url); 179 set_String(&visit.url, url);
151 size_t pos; 180 size_t pos;
152 if (locate_SortedArray(&d->visitedUrls, &visit, &pos)) { 181 if (locate_SortedArray(&d->visited, &visit, &pos)) {
153 iHistoryItem *old = at_SortedArray(&d->visitedUrls, pos); 182 iVisitedUrl *old = at_SortedArray(&d->visited, pos);
154 if (cmpNewer_HistoryItem_(&visit, old)) { 183 if (cmpNewer_VisitedUrl_(&visit, old)) {
155 old->when = visit.when; 184 old->when = visit.when;
156 deinit_HistoryItem(&visit); 185 deinit_VisitedUrl(&visit);
157 return; 186 return;
158 } 187 }
159 } 188 }
160 insert_SortedArray(&d->visitedUrls, &visit); 189 insert_SortedArray(&d->visited, &visit);
161} 190}
162 191
163void replace_History(iHistory *d, const iString *url) { 192void replace_History(iHistory *d, const iString *url) {
164 /* Update in the history. */ 193 /* Update in the history. */
165 iHistoryItem *item = item_History(d); 194 iRecentUrl *item = mostRecentUrl_History(d);
166 if (item) { 195 if (item) {
167 set_String(&item->url, url); 196 set_String(&item->url, url);
168 } 197 }
@@ -171,24 +200,24 @@ void replace_History(iHistory *d, const iString *url) {
171 200
172void addUrl_History(iHistory *d, const iString *url ){ 201void addUrl_History(iHistory *d, const iString *url ){
173 /* Cut the trailing history items. */ 202 /* Cut the trailing history items. */
174 if (d->stackPos > 0) { 203 if (d->recentPos > 0) {
175 for (size_t i = 0; i < d->stackPos - 1; i++) { 204 for (size_t i = 0; i < d->recentPos - 1; i++) {
176 deinit_HistoryItem(itemAtPos_History(d, i)); 205 deinit_RecentUrl(recentUrl_History(d, i));
177 } 206 }
178 removeN_Array(&d->stack, size_Array(&d->stack) - d->stackPos, iInvalidSize); 207 removeN_Array(&d->recent, size_Array(&d->recent) - d->recentPos, iInvalidSize);
179 d->stackPos = 0; 208 d->recentPos = 0;
180 } 209 }
181 /* Insert new item. */ 210 /* Insert new item. */
182 const iHistoryItem *lastItem = itemAtPos_History(d, 0); 211 const iRecentUrl *lastItem = recentUrl_History(d, 0);
183 if (!lastItem || cmpString_String(&lastItem->url, url) != 0) { 212 if (!lastItem || cmpString_String(&lastItem->url, url) != 0) {
184 iHistoryItem item; 213 iRecentUrl item;
185 init_HistoryItem(&item); 214 init_RecentUrl(&item);
186 set_String(&item.url, url); 215 set_String(&item.url, url);
187 pushBack_Array(&d->stack, &item); 216 pushBack_Array(&d->recent, &item);
188 /* Limit the number of items. */ 217 /* Limit the number of items. */
189 if (size_Array(&d->stack) > maxStack_History_) { 218 if (size_Array(&d->recent) > maxStack_History_) {
190 deinit_HistoryItem(front_Array(&d->stack)); 219 deinit_RecentUrl(front_Array(&d->recent));
191 remove_Array(&d->stack, 0); 220 remove_Array(&d->recent, 0);
192 } 221 }
193 } 222 }
194 addVisited_History_(d, url); 223 addVisited_History_(d, url);
@@ -199,48 +228,49 @@ void visitUrl_History(iHistory *d, const iString *url) {
199} 228}
200 229
201iBool goBack_History(iHistory *d) { 230iBool goBack_History(iHistory *d) {
202 if (d->stackPos < size_Array(&d->stack) - 1) { 231 if (d->recentPos < size_Array(&d->recent) - 1) {
203 d->stackPos++; 232 d->recentPos++;
204 postCommandf_App("open history:1 scroll:%d url:%s", 233 postCommandf_App("open history:1 scroll:%d url:%s",
205 item_History(d)->scrollY, 234 mostRecentUrl_History(d)->scrollY,
206 cstr_String(url_History(d, d->stackPos))); 235 cstr_String(url_History(d, d->recentPos)));
207 return iTrue; 236 return iTrue;
208 } 237 }
209 return iFalse; 238 return iFalse;
210} 239}
211 240
212iBool goForward_History(iHistory *d) { 241iBool goForward_History(iHistory *d) {
213 if (d->stackPos > 0) { 242 if (d->recentPos > 0) {
214 d->stackPos--; 243 d->recentPos--;
215 postCommandf_App("open history:1 url:%s", cstr_String(url_History(d, d->stackPos))); 244 postCommandf_App("open history:1 url:%s", cstr_String(url_History(d, d->recentPos)));
216 return iTrue; 245 return iTrue;
217 } 246 }
218 return iFalse; 247 return iFalse;
219} 248}
220 249
221iTime urlVisitTime_History(const iHistory *d, const iString *url) { 250iTime urlVisitTime_History(const iHistory *d, const iString *url) {
222 iHistoryItem item; 251 iVisitedUrl item;
223 size_t pos; 252 size_t pos;
224 iZap(item); 253 iZap(item);
225 initCopy_String(&item.url, url); 254 initCopy_String(&item.url, url);
226 if (locate_SortedArray(&d->visitedUrls, &item, &pos)) { 255 if (locate_SortedArray(&d->visited, &item, &pos)) {
227 item.when = ((const iHistoryItem *) constAt_SortedArray(&d->visitedUrls, pos))->when; 256 item.when = ((const iVisitedUrl *) constAt_SortedArray(&d->visited, pos))->when;
228 } 257 }
229 deinit_String(&item.url); 258 deinit_String(&item.url);
230 return item.when; 259 return item.when;
231} 260}
232 261
233void print_History(const iHistory *d) { 262const iGmResponse *cachedResponse_History(const iHistory *d) {
234 iUnused(d); 263 const iRecentUrl *item = constMostRecentUrl_History(d);
235#if 0 264 return item ? item->cachedResponse : NULL;
236 iConstForEach(Array, i, &d->history) {
237 const size_t idx = index_ArrayConstIterator(&i);
238 printf("%s[%zu]: %s\n",
239 d->historyPos == size_Array(&d->history) - idx - 1 ? "->" : " ",
240 idx,
241 cstr_String(&((const iHistoryItem *) i.value)->url));
242 }
243 fflush(stdout);
244#endif
245} 265}
246 266
267void setCachedResponse_History(iHistory *d, const iGmResponse *response) {
268 iRecentUrl *item = mostRecentUrl_History(d);
269 if (item) {
270 delete_GmResponse(item->cachedResponse);
271 item->cachedResponse = NULL;
272 if (category_GmStatusCode(response->statusCode) == categorySuccess_GmStatusCode) {
273 item->cachedResponse = copy_GmResponse(response);
274 }
275 }
276}
diff --git a/src/history.h b/src/history.h
index 78394008..8593a7ad 100644
--- a/src/history.h
+++ b/src/history.h
@@ -1,18 +1,30 @@
1#pragma once 1#pragma once
2 2
3#include "gmrequest.h"
4
3#include <the_Foundation/array.h> 5#include <the_Foundation/array.h>
4#include <the_Foundation/string.h> 6#include <the_Foundation/string.h>
5#include <the_Foundation/time.h> 7#include <the_Foundation/time.h>
6 8
7iDeclareType(HistoryItem) 9iDeclareType(RecentUrl)
8iDeclareTypeConstruction(HistoryItem) 10iDeclareTypeConstruction(RecentUrl)
9 11
10struct Impl_HistoryItem { 12struct Impl_RecentUrl {
11 iTime when; 13 iString url;
14 int scrollY; /* unit is gap_UI */
15 iGmResponse *cachedResponse; /* kept in memory for quicker back navigation */
16};
17
18iDeclareType(VisitedUrl)
19iDeclareTypeConstruction(VisitedUrl)
20
21struct Impl_VisitedUrl {
12 iString url; 22 iString url;
13 int scrollY; /* unit is gap_UI */ 23 iTime when;
14}; 24};
15 25
26/*----------------------------------------------------------------------------------------------*/
27
16iDeclareType(History) 28iDeclareType(History)
17iDeclareTypeConstruction(History) 29iDeclareTypeConstruction(History)
18 30
@@ -21,15 +33,24 @@ void clear_History (iHistory *);
21void load_History (iHistory *, const char *dirPath); 33void load_History (iHistory *, const char *dirPath);
22void save_History (const iHistory *, const char *dirPath); 34void save_History (const iHistory *, const char *dirPath);
23 35
24iHistoryItem * itemAtPos_History (iHistory *, size_t pos); 36iRecentUrl *
25iHistoryItem * item_History (iHistory *); 37 recentUrl_History (iHistory *, size_t pos);
26const iString * url_History (iHistory *, size_t pos); 38iRecentUrl *
27iTime urlVisitTime_History(const iHistory *, const iString *url); 39 mostRecentUrl_History (iHistory *);
28void print_History (const iHistory *); 40
29 41const iString *
30void addUrl_History (iHistory *, const iString *url); /* adds to the stack of recents */ 42 url_History (const iHistory *, size_t pos);
31void visitUrl_History (iHistory *, const iString *url); /* adds URL to the visited URLs set */ 43const iRecentUrl *
32void replace_History (iHistory *, const iString *url); 44 constRecentUrl_History (const iHistory *d, size_t pos);
33 45const iRecentUrl *
34iBool goBack_History (iHistory *); 46 constMostRecentUrl_History (const iHistory *);
35iBool goForward_History (iHistory *); 47iTime urlVisitTime_History (const iHistory *, const iString *url);
48const iGmResponse *
49 cachedResponse_History (const iHistory *);
50
51void addUrl_History (iHistory *, const iString *url); /* adds to the stack of recents */
52void setCachedResponse_History (iHistory *, const iGmResponse *response);
53void visitUrl_History (iHistory *, const iString *url); /* adds URL to the visited URLs set */
54void replace_History (iHistory *, const iString *url);
55iBool goBack_History (iHistory *);
56iBool goForward_History (iHistory *);
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 48383560..5cf0f6a6 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -251,9 +251,9 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) {
251 render_GmDocument(d->doc, visRange, addVisibleLink_DocumentWidget_, d); 251 render_GmDocument(d->doc, visRange, addVisibleLink_DocumentWidget_, d);
252 updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window())); 252 updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window()));
253 /* Remember scroll positions of recently visited pages. */ { 253 /* Remember scroll positions of recently visited pages. */ {
254 iHistoryItem *it = item_History(history_App()); 254 iRecentUrl *recent = mostRecentUrl_History(history_App());
255 if (it) { 255 if (recent) {
256 it->scrollY = d->scrollY / gap_UI; 256 recent->scrollY = d->scrollY / gap_UI;
257 } 257 }
258 } 258 }
259} 259}
@@ -318,16 +318,16 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode
318 d->state = ready_DocumentState; 318 d->state = ready_DocumentState;
319} 319}
320 320
321static void updateSource_DocumentWidget_(iDocumentWidget *d) { 321static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse *response) {
322 if (d->state == ready_DocumentState) { 322 if (d->state == ready_DocumentState) {
323 return; 323 return;
324 } 324 }
325 /* TODO: Do this in the background. However, that requires a text metrics calculator 325 /* TODO: Do this in the background. However, that requires a text metrics calculator
326 that does not try to cache the glyph bitmaps. */ 326 that does not try to cache the glyph bitmaps. */
327 const enum iGmStatusCode statusCode = status_GmRequest(d->request); 327 const enum iGmStatusCode statusCode = response->statusCode;
328 if (category_GmStatusCode(statusCode) != categoryInput_GmStatusCode) { 328 if (category_GmStatusCode(statusCode) != categoryInput_GmStatusCode) {
329 iString str; 329 iString str;
330 /* Theming. */ { 330 /* Update theme. */ {
331 if (isEmpty_String(d->titleUser)) { 331 if (isEmpty_String(d->titleUser)) {
332 setThemeSeed_GmDocument(d->doc, 332 setThemeSeed_GmDocument(d->doc,
333 collect_Block(newRange_Block(urlHost_String(d->url)))); 333 collect_Block(newRange_Block(urlHost_String(d->url))));
@@ -336,10 +336,10 @@ static void updateSource_DocumentWidget_(iDocumentWidget *d) {
336 setThemeSeed_GmDocument(d->doc, &d->titleUser->chars); 336 setThemeSeed_GmDocument(d->doc, &d->titleUser->chars);
337 } 337 }
338 } 338 }
339 initBlock_String(&str, body_GmRequest(d->request)); 339 initBlock_String(&str, &response->body);
340 if (category_GmStatusCode(statusCode) == categorySuccess_GmStatusCode) { 340 if (category_GmStatusCode(statusCode) == categorySuccess_GmStatusCode) {
341 /* Check the MIME type. */ 341 /* Check the MIME type. */
342 const iString *mime = meta_GmRequest(d->request); 342 const iString *mime = &response->meta;
343 if (startsWith_String(mime, "text/plain")) { 343 if (startsWith_String(mime, "text/plain")) {
344 setFormat_GmDocument(d->doc, plainText_GmDocumentFormat); 344 setFormat_GmDocument(d->doc, plainText_GmDocumentFormat);
345 } 345 }
@@ -347,17 +347,17 @@ static void updateSource_DocumentWidget_(iDocumentWidget *d) {
347 setFormat_GmDocument(d->doc, gemini_GmDocumentFormat); 347 setFormat_GmDocument(d->doc, gemini_GmDocumentFormat);
348 } 348 }
349 else if (startsWith_String(mime, "image/")) { 349 else if (startsWith_String(mime, "image/")) {
350 if (isFinished_GmRequest(d->request)) { 350 if (!d->request || isFinished_GmRequest(d->request)) {
351 /* Make a simple document with an image. */ 351 /* Make a simple document with an image. */
352 const char *imageTitle = "Image"; 352 const char *imageTitle = "Image";
353 iUrl parts; 353 iUrl parts;
354 init_Url(&parts, url_GmRequest(d->request)); 354 init_Url(&parts, d->url); // url_GmRequest(d->request));
355 if (!isEmpty_Range(&parts.path)) { 355 if (!isEmpty_Range(&parts.path)) {
356 imageTitle = baseName_Path(collect_String(newRange_String(parts.path))).start; 356 imageTitle = baseName_Path(collect_String(newRange_String(parts.path))).start;
357 } 357 }
358 format_String( 358 format_String(
359 &str, "=> %s %s\n", cstr_String(url_GmRequest(d->request)), imageTitle); 359 &str, "=> %s %s\n", cstr_String(d->url), imageTitle);
360 setImage_GmDocument(d->doc, 1, mime, body_GmRequest(d->request)); 360 setImage_GmDocument(d->doc, 1, mime, &response->body);
361 } 361 }
362 else { 362 else {
363 clear_String(&str); 363 clear_String(&str);
@@ -392,25 +392,70 @@ static void fetch_DocumentWidget_(iDocumentWidget *d) {
392 submit_GmRequest(d->request); 392 submit_GmRequest(d->request);
393} 393}
394 394
395void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) { 395static void updateTrust_DocumentWidget_(iDocumentWidget *d, const iGmResponse *response) {
396#define openLock_CStr "\U0001f513"
397#define closedLock_CStr "\U0001f512"
398 d->certFlags = response->certFlags;
399 d->certExpiry = response->certValidUntil;
400 iLabelWidget *lock = findWidget_App("navbar.lock");
401 if (~d->certFlags & available_GmCertFlag) {
402 setFlags_Widget(as_Widget(lock), disabled_WidgetFlag, iTrue);
403 updateTextCStr_LabelWidget(lock, gray50_ColorEscape openLock_CStr);
404 return;
405 }
406 setFlags_Widget(as_Widget(lock), disabled_WidgetFlag, iFalse);
407 if (~d->certFlags & domainVerified_GmCertFlag) {
408 updateTextCStr_LabelWidget(lock, red_ColorEscape closedLock_CStr);
409 }
410 else if (d->certFlags & trusted_GmCertFlag) {
411 updateTextCStr_LabelWidget(lock, green_ColorEscape closedLock_CStr);
412 }
413 else {
414 updateTextCStr_LabelWidget(lock, orange_ColorEscape closedLock_CStr);
415 }
416}
417
418void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBool isFromCache) {
396 if (cmpStringSc_String(d->url, url, &iCaseInsensitive)) { 419 if (cmpStringSc_String(d->url, url, &iCaseInsensitive)) {
397 set_String(d->url, url); 420 set_String(d->url, url);
398 fetch_DocumentWidget_(d); 421 /* See if there a username in the URL. */ {
399 } 422 clear_String(d->titleUser);
400 /* See if there a username in the URL. */ { 423 iRegExp *userPats[2] = { new_RegExp("~([^/?]+)", 0),
401 clear_String(d->titleUser); 424 new_RegExp("/users/([^/?]+)", caseInsensitive_RegExpOption) };
402 iRegExp *userPats[2] = { new_RegExp("~([^/?]+)", 0), 425 iRegExpMatch m;
403 new_RegExp("/users/([^/?]+)", caseInsensitive_RegExpOption) }; 426 iForIndices(i, userPats) {
404 iRegExpMatch m; 427 if (matchString_RegExp(userPats[i], d->url, &m)) {
405 iForIndices(i, userPats) { 428 setRange_String(d->titleUser, capturedRange_RegExpMatch(&m, 1));
406 if (matchString_RegExp(userPats[i], d->url, &m)) { 429 }
407 setRange_String(d->titleUser, capturedRange_RegExpMatch(&m, 1)); 430 iRelease(userPats[i]);
408 } 431 }
409 iRelease(userPats[i]); 432 }
433 const iRecentUrl *recent = mostRecentUrl_History(history_App());
434 if (isFromCache && recent && recent->cachedResponse) {
435 const iGmResponse *resp = recent->cachedResponse;
436 d->state = fetching_DocumentState;
437 /* Use the cached response data. */
438 printf("cached response: %d [%s] %zu bytes\n{%s}\n",
439 resp->statusCode,
440 cstr_String(&resp->meta),
441 size_Block(&resp->body),
442 cstr_Block(&resp->body));
443 d->scrollY = d->initialScrollY;
444 updateTrust_DocumentWidget_(d, resp);
445 updateDocument_DocumentWidget_(d, resp);
446 d->state = ready_DocumentState;
447 postCommandf_App("document.changed url:%s", cstr_String(d->url));
448 }
449 else {
450 fetch_DocumentWidget_(d);
410 } 451 }
411 } 452 }
412} 453}
413 454
455void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) {
456 setUrlFromCache_DocumentWidget(d, url, iFalse);
457}
458
414void setInitialScroll_DocumentWidget (iDocumentWidget *d, int scrollY) { 459void setInitialScroll_DocumentWidget (iDocumentWidget *d, int scrollY) {
415 d->initialScrollY = scrollY; 460 d->initialScrollY = scrollY;
416} 461}
@@ -440,30 +485,6 @@ static void scrollTo_DocumentWidget_(iDocumentWidget *d, int documentY) {
440 scroll_DocumentWidget_(d, 0); /* clamp it */ 485 scroll_DocumentWidget_(d, 0); /* clamp it */
441} 486}
442 487
443static void updateTrust_DocumentWidget_(iDocumentWidget *d) {
444 iAssert(d->request);
445#define openLock_CStr "\U0001f513"
446#define closedLock_CStr "\U0001f512"
447 d->certFlags = certFlags_GmRequest(d->request);
448 d->certExpiry = certExpirationDate_GmRequest(d->request);
449 iLabelWidget *lock = findWidget_App("navbar.lock");
450 if (~d->certFlags & available_GmCertFlag) {
451 setFlags_Widget(as_Widget(lock), disabled_WidgetFlag, iTrue);
452 updateTextCStr_LabelWidget(lock, gray50_ColorEscape openLock_CStr);
453 return;
454 }
455 setFlags_Widget(as_Widget(lock), disabled_WidgetFlag, iFalse);
456 if (~d->certFlags & domainVerified_GmCertFlag) {
457 updateTextCStr_LabelWidget(lock, red_ColorEscape closedLock_CStr);
458 }
459 else if (d->certFlags & trusted_GmCertFlag) {
460 updateTextCStr_LabelWidget(lock, green_ColorEscape closedLock_CStr);
461 }
462 else {
463 updateTextCStr_LabelWidget(lock, orange_ColorEscape closedLock_CStr);
464 }
465}
466
467static void checkResponse_DocumentWidget_(iDocumentWidget *d) { 488static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
468 if (!d->request) { 489 if (!d->request) {
469 return; 490 return;
@@ -474,7 +495,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
474 } 495 }
475 if (d->state == fetching_DocumentState) { 496 if (d->state == fetching_DocumentState) {
476 d->state = receivedPartialResponse_DocumentState; 497 d->state = receivedPartialResponse_DocumentState;
477 updateTrust_DocumentWidget_(d); 498 updateTrust_DocumentWidget_(d, response_GmRequest(d->request));
478 switch (category_GmStatusCode(statusCode)) { 499 switch (category_GmStatusCode(statusCode)) {
479 case categoryInput_GmStatusCode: { 500 case categoryInput_GmStatusCode: {
480 iUrl parts; 501 iUrl parts;
@@ -498,7 +519,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
498 case categorySuccess_GmStatusCode: 519 case categorySuccess_GmStatusCode:
499 d->scrollY = d->initialScrollY; 520 d->scrollY = d->initialScrollY;
500 reset_GmDocument(d->doc); /* new content incoming */ 521 reset_GmDocument(d->doc); /* new content incoming */
501 updateSource_DocumentWidget_(d); 522 updateDocument_DocumentWidget_(d, response_GmRequest(d->request));
502 break; 523 break;
503 case categoryRedirect_GmStatusCode: 524 case categoryRedirect_GmStatusCode:
504 if (isEmpty_String(meta_GmRequest(d->request))) { 525 if (isEmpty_String(meta_GmRequest(d->request))) {
@@ -531,7 +552,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
531 switch (category_GmStatusCode(statusCode)) { 552 switch (category_GmStatusCode(statusCode)) {
532 case categorySuccess_GmStatusCode: 553 case categorySuccess_GmStatusCode:
533 /* More content available. */ 554 /* More content available. */
534 updateSource_DocumentWidget_(d); 555 updateDocument_DocumentWidget_(d, response_GmRequest(d->request));
535 break; 556 break;
536 default: 557 default:
537 break; 558 break;
@@ -739,6 +760,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
739 pointerLabel_Command(command_UserEvent(ev), "request") == d->request) { 760 pointerLabel_Command(command_UserEvent(ev), "request") == d->request) {
740 checkResponse_DocumentWidget_(d); 761 checkResponse_DocumentWidget_(d);
741 d->state = ready_DocumentState; 762 d->state = ready_DocumentState;
763 setCachedResponse_History(history_App(), response_GmRequest(d->request));
742 iReleasePtr(&d->request); 764 iReleasePtr(&d->request);
743 postCommandf_App("document.changed url:%s", cstr_String(d->url)); 765 postCommandf_App("document.changed url:%s", cstr_String(d->url));
744 return iFalse; 766 return iFalse;
diff --git a/src/ui/documentwidget.h b/src/ui/documentwidget.h
index 6b0ea607..7064d0d7 100644
--- a/src/ui/documentwidget.h
+++ b/src/ui/documentwidget.h
@@ -6,5 +6,6 @@ iDeclareWidgetClass(DocumentWidget)
6iDeclareObjectConstruction(DocumentWidget) 6iDeclareObjectConstruction(DocumentWidget)
7 7
8void setUrl_DocumentWidget (iDocumentWidget *, const iString *url); 8void setUrl_DocumentWidget (iDocumentWidget *, const iString *url);
9void setUrlFromCache_DocumentWidget (iDocumentWidget *d, const iString *url, iBool isFromCache);
9void setInitialScroll_DocumentWidget (iDocumentWidget *, int scrollY); /* set after content received */ 10void setInitialScroll_DocumentWidget (iDocumentWidget *, int scrollY); /* set after content received */
10iBool isRequestOngoing_DocumentWidget (const iDocumentWidget *); 11iBool isRequestOngoing_DocumentWidget (const iDocumentWidget *);