diff options
Diffstat (limited to 'src/history.c')
-rw-r--r-- | src/history.c | 272 |
1 files changed, 151 insertions, 121 deletions
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 | } | ||