diff options
-rw-r--r-- | src/app.c | 6 | ||||
-rw-r--r-- | src/gmrequest.c | 4 | ||||
-rw-r--r-- | src/history.c | 272 | ||||
-rw-r--r-- | src/history.h | 55 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 124 | ||||
-rw-r--r-- | src/ui/documentwidget.h | 1 |
6 files changed, 269 insertions, 193 deletions
@@ -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 | ||
12 | iDefineTypeConstruction(GmResponse) | ||
13 | |||
12 | void init_GmResponse(iGmResponse *d) { | 14 | void 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 | ||
345 | const iGmResponse *response_GmRequest(const iGmRequest *d) { | 347 | const 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 @@ | |||
8 | static const size_t maxStack_History_ = 50; /* back/forward navigable items */ | 8 | static const size_t maxStack_History_ = 50; /* back/forward navigable items */ |
9 | static const size_t maxAgeVisited_History_ = 3600 * 24 * 30; /* one month */ | 9 | static const size_t maxAgeVisited_History_ = 3600 * 24 * 30; /* one month */ |
10 | 10 | ||
11 | void init_HistoryItem(iHistoryItem *d) { | 11 | void 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 | ||
17 | void deinit_HistoryItem(iHistoryItem *d) { | 17 | void deinit_RecentUrl(iRecentUrl *d) { |
18 | deinit_String(&d->url); | 18 | deinit_String(&d->url); |
19 | delete_GmResponse(d->cachedResponse); | ||
19 | } | 20 | } |
20 | 21 | ||
21 | struct Impl_History { | 22 | void 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 | ||
27 | iDefineTypeConstruction(History) | 27 | void deinit_VisitedUrl(iVisitedUrl *d) { |
28 | deinit_String(&d->url); | ||
29 | } | ||
28 | 30 | ||
29 | static int cmpUrl_HistoryItem_(const void *a, const void *b) { | 31 | static 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 | ||
33 | static int cmpNewer_HistoryItem_(const void *insert, const void *existing) { | 35 | static 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 | |||
42 | struct 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 | |||
48 | iDefineTypeConstruction(History) | ||
49 | |||
38 | void init_History(iHistory *d) { | 50 | void 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 | ||
44 | void deinit_History(iHistory *d) { | 56 | void 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 | |||
49 | static 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 | ||
70 | void save_History(const iHistory *d, const char *dirPath) { | 62 | void 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, | |
83 | static 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 | ||
106 | void load_History(iHistory *d, const char *dirPath) { | 95 | void 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 | ||
119 | void clear_History(iHistory *d) { | 139 | void 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 | ||
130 | iHistoryItem *itemAtPos_History(iHistory *d, size_t pos) { | 150 | iRecentUrl *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 | ||
135 | iHistoryItem *item_History(iHistory *d) { | 155 | const 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 | ||
139 | const iString *url_History(iHistory *d, size_t pos) { | 160 | iRecentUrl *mostRecentUrl_History(iHistory *d) { |
140 | const iHistoryItem *item = itemAtPos_History(d, pos); | 161 | return recentUrl_History(d, d->recentPos); |
162 | } | ||
163 | |||
164 | const iRecentUrl *constMostRecentUrl_History(const iHistory *d) { | ||
165 | return constRecentUrl_History(d, d->recentPos); | ||
166 | } | ||
167 | |||
168 | const 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 | ||
147 | static void addVisited_History_(iHistory *d, const iString *url) { | 176 | static 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 | ||
163 | void replace_History(iHistory *d, const iString *url) { | 192 | void 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 | ||
172 | void addUrl_History(iHistory *d, const iString *url ){ | 201 | void 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 | ||
201 | iBool goBack_History(iHistory *d) { | 230 | iBool 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 | ||
212 | iBool goForward_History(iHistory *d) { | 241 | iBool 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 | ||
221 | iTime urlVisitTime_History(const iHistory *d, const iString *url) { | 250 | iTime 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 | ||
233 | void print_History(const iHistory *d) { | 262 | const 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 | ||
267 | void 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 | ||
7 | iDeclareType(HistoryItem) | 9 | iDeclareType(RecentUrl) |
8 | iDeclareTypeConstruction(HistoryItem) | 10 | iDeclareTypeConstruction(RecentUrl) |
9 | 11 | ||
10 | struct Impl_HistoryItem { | 12 | struct 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 | |||
18 | iDeclareType(VisitedUrl) | ||
19 | iDeclareTypeConstruction(VisitedUrl) | ||
20 | |||
21 | struct Impl_VisitedUrl { | ||
12 | iString url; | 22 | iString url; |
13 | int scrollY; /* unit is gap_UI */ | 23 | iTime when; |
14 | }; | 24 | }; |
15 | 25 | ||
26 | /*----------------------------------------------------------------------------------------------*/ | ||
27 | |||
16 | iDeclareType(History) | 28 | iDeclareType(History) |
17 | iDeclareTypeConstruction(History) | 29 | iDeclareTypeConstruction(History) |
18 | 30 | ||
@@ -21,15 +33,24 @@ void clear_History (iHistory *); | |||
21 | void load_History (iHistory *, const char *dirPath); | 33 | void load_History (iHistory *, const char *dirPath); |
22 | void save_History (const iHistory *, const char *dirPath); | 34 | void save_History (const iHistory *, const char *dirPath); |
23 | 35 | ||
24 | iHistoryItem * itemAtPos_History (iHistory *, size_t pos); | 36 | iRecentUrl * |
25 | iHistoryItem * item_History (iHistory *); | 37 | recentUrl_History (iHistory *, size_t pos); |
26 | const iString * url_History (iHistory *, size_t pos); | 38 | iRecentUrl * |
27 | iTime urlVisitTime_History(const iHistory *, const iString *url); | 39 | mostRecentUrl_History (iHistory *); |
28 | void print_History (const iHistory *); | 40 | |
29 | 41 | const iString * | |
30 | void addUrl_History (iHistory *, const iString *url); /* adds to the stack of recents */ | 42 | url_History (const iHistory *, size_t pos); |
31 | void visitUrl_History (iHistory *, const iString *url); /* adds URL to the visited URLs set */ | 43 | const iRecentUrl * |
32 | void replace_History (iHistory *, const iString *url); | 44 | constRecentUrl_History (const iHistory *d, size_t pos); |
33 | 45 | const iRecentUrl * | |
34 | iBool goBack_History (iHistory *); | 46 | constMostRecentUrl_History (const iHistory *); |
35 | iBool goForward_History (iHistory *); | 47 | iTime urlVisitTime_History (const iHistory *, const iString *url); |
48 | const iGmResponse * | ||
49 | cachedResponse_History (const iHistory *); | ||
50 | |||
51 | void addUrl_History (iHistory *, const iString *url); /* adds to the stack of recents */ | ||
52 | void setCachedResponse_History (iHistory *, const iGmResponse *response); | ||
53 | void visitUrl_History (iHistory *, const iString *url); /* adds URL to the visited URLs set */ | ||
54 | void replace_History (iHistory *, const iString *url); | ||
55 | iBool goBack_History (iHistory *); | ||
56 | iBool 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 | ||
321 | static void updateSource_DocumentWidget_(iDocumentWidget *d) { | 321 | static 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 | ||
395 | void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) { | 395 | static 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 | |||
418 | void 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 | ||
455 | void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) { | ||
456 | setUrlFromCache_DocumentWidget(d, url, iFalse); | ||
457 | } | ||
458 | |||
414 | void setInitialScroll_DocumentWidget (iDocumentWidget *d, int scrollY) { | 459 | void 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 | ||
443 | static 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 | |||
467 | static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | 488 | static 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) | |||
6 | iDeclareObjectConstruction(DocumentWidget) | 6 | iDeclareObjectConstruction(DocumentWidget) |
7 | 7 | ||
8 | void setUrl_DocumentWidget (iDocumentWidget *, const iString *url); | 8 | void setUrl_DocumentWidget (iDocumentWidget *, const iString *url); |
9 | void setUrlFromCache_DocumentWidget (iDocumentWidget *d, const iString *url, iBool isFromCache); | ||
9 | void setInitialScroll_DocumentWidget (iDocumentWidget *, int scrollY); /* set after content received */ | 10 | void setInitialScroll_DocumentWidget (iDocumentWidget *, int scrollY); /* set after content received */ |
10 | iBool isRequestOngoing_DocumentWidget (const iDocumentWidget *); | 11 | iBool isRequestOngoing_DocumentWidget (const iDocumentWidget *); |