diff options
-rw-r--r-- | res/about/version.gmi | 4 | ||||
-rw-r--r-- | src/feeds.c | 94 |
2 files changed, 50 insertions, 48 deletions
diff --git a/res/about/version.gmi b/res/about/version.gmi index a5830726..d209d376 100644 --- a/res/about/version.gmi +++ b/res/about/version.gmi | |||
@@ -6,8 +6,10 @@ | |||
6 | ``` | 6 | ``` |
7 | # Release notes | 7 | # Release notes |
8 | 8 | ||
9 | ## 0.12 | ||
10 | |||
9 | ## 0.11 | 11 | ## 0.11 |
10 | * Added feed subscriptions. A subscription is any bookmark with the "subscribed" tag. Subscriptions are refreshed in the background while Lagrange is running. | 12 | * Added feed subscriptions. A subscription is any bookmark with the "subscribed" tag. Subscribed feeds are refreshed in the background while Lagrange is running. |
11 | * Added a new sidebar tab for feeds. | 13 | * Added a new sidebar tab for feeds. |
12 | * Added "about:feeds" to show entries from all subscriptions on one page. | 14 | * Added "about:feeds" to show entries from all subscriptions on one page. |
13 | * Added icons for special bookmark tags, and context menu items for toggling "homepage" and "subscribed". | 15 | * Added icons for special bookmark tags, and context menu items for toggling "homepage" and "subscribed". |
diff --git a/src/feeds.c b/src/feeds.c index dc82fe0d..d8a2f24d 100644 --- a/src/feeds.c +++ b/src/feeds.c | |||
@@ -111,6 +111,15 @@ static void submit_FeedJob_(iFeedJob *d) { | |||
111 | submit_GmRequest(d->request); | 111 | submit_GmRequest(d->request); |
112 | } | 112 | } |
113 | 113 | ||
114 | static iBool isSubscribed_(void *context, const iBookmark *bm) { | ||
115 | iUnused(context); | ||
116 | return indexOfCStr_String(&bm->tags, "subscribed") != iInvalidPos; /* TODO: RegExp with \b */ | ||
117 | } | ||
118 | |||
119 | static const iPtrArray *listSubscriptions_(void) { | ||
120 | return list_Bookmarks(bookmarks_App(), NULL, isSubscribed_, NULL); | ||
121 | } | ||
122 | |||
114 | static iFeedJob *startNextJob_Feeds_(iFeeds *d) { | 123 | static iFeedJob *startNextJob_Feeds_(iFeeds *d) { |
115 | if (isEmpty_PtrArray(&d->jobs)) { | 124 | if (isEmpty_PtrArray(&d->jobs)) { |
116 | return NULL; | 125 | return NULL; |
@@ -177,6 +186,43 @@ static void parseResult_FeedJob_(iFeedJob *d) { | |||
177 | } | 186 | } |
178 | } | 187 | } |
179 | 188 | ||
189 | static void save_Feeds_(iFeeds *d) { | ||
190 | iFile *f = new_File(collect_String(concatCStr_Path(&d->saveDir, feedsFilename_Feeds_))); | ||
191 | if (open_File(f, write_FileMode | text_FileMode)) { | ||
192 | lock_Mutex(d->mtx); | ||
193 | iString *str = new_String(); | ||
194 | format_String(str, "%llu\n# Feeds\n", integralSeconds_Time(&d->lastRefreshedAt)); | ||
195 | write_File(f, utf8_String(str)); | ||
196 | /* Index of feeds for IDs. */ { | ||
197 | iConstForEach(PtrArray, i, listSubscriptions_()) { | ||
198 | const iBookmark *bm = i.ptr; | ||
199 | format_String(str, "%08x %s\n", id_Bookmark(bm), cstr_String(&bm->url)); | ||
200 | write_File(f, utf8_String(str)); | ||
201 | } | ||
202 | } | ||
203 | writeData_File(f, "# Entries\n", 10); | ||
204 | iTime now; | ||
205 | initCurrent_Time(&now); | ||
206 | iConstForEach(Array, i, &d->entries.values) { | ||
207 | const iFeedEntry *entry = *(const iFeedEntry **) i.value; | ||
208 | if (secondsSince_Time(&now, &entry->discovered) > maxAge_Visited) { | ||
209 | continue; /* Forget entries discovered long ago. */ | ||
210 | } | ||
211 | format_String(str, "%x\n%llu\n%llu\n%s\n%s\n", | ||
212 | entry->bookmarkId, | ||
213 | integralSeconds_Time(&entry->posted), | ||
214 | integralSeconds_Time(&entry->discovered), | ||
215 | cstr_String(&entry->url), | ||
216 | cstr_String(&entry->title)); | ||
217 | write_File(f, utf8_String(str)); | ||
218 | } | ||
219 | delete_String(str); | ||
220 | close_File(f); | ||
221 | unlock_Mutex(d->mtx); | ||
222 | } | ||
223 | iRelease(f); | ||
224 | } | ||
225 | |||
180 | static iBool updateEntries_Feeds_(iFeeds *d, iPtrArray *incoming) { | 226 | static iBool updateEntries_Feeds_(iFeeds *d, iPtrArray *incoming) { |
181 | iBool gotNew = iFalse; | 227 | iBool gotNew = iFalse; |
182 | lock_Mutex(d->mtx); | 228 | lock_Mutex(d->mtx); |
@@ -252,20 +298,12 @@ static iThreadResult fetch_Feeds_(iThread *thread) { | |||
252 | break; | 298 | break; |
253 | } | 299 | } |
254 | } | 300 | } |
301 | save_Feeds_(d); | ||
255 | postCommandf_App("feeds.update.finished arg:%d", gotNew ? 1 : 0); | 302 | postCommandf_App("feeds.update.finished arg:%d", gotNew ? 1 : 0); |
256 | initCurrent_Time(&d->lastRefreshedAt); | 303 | initCurrent_Time(&d->lastRefreshedAt); |
257 | return 0; | 304 | return 0; |
258 | } | 305 | } |
259 | 306 | ||
260 | static iBool isSubscribed_(void *context, const iBookmark *bm) { | ||
261 | iUnused(context); | ||
262 | return indexOfCStr_String(&bm->tags, "subscribed") != iInvalidPos; /* TODO: RegExp with \b */ | ||
263 | } | ||
264 | |||
265 | static const iPtrArray *listSubscriptions_(void) { | ||
266 | return list_Bookmarks(bookmarks_App(), NULL, isSubscribed_, NULL); | ||
267 | } | ||
268 | |||
269 | static iBool startWorker_Feeds_(iFeeds *d) { | 307 | static iBool startWorker_Feeds_(iFeeds *d) { |
270 | if (d->worker) { | 308 | if (d->worker) { |
271 | return iFalse; /* Refresh is already ongoing. */ | 309 | return iFalse; /* Refresh is already ongoing. */ |
@@ -308,43 +346,6 @@ static int cmp_FeedEntryPtr_(const void *a, const void *b) { | |||
308 | return cmpString_String(&(*elem[0])->url, &(*elem[1])->url); | 346 | return cmpString_String(&(*elem[0])->url, &(*elem[1])->url); |
309 | } | 347 | } |
310 | 348 | ||
311 | static void save_Feeds_(iFeeds *d) { | ||
312 | iFile *f = new_File(collect_String(concatCStr_Path(&d->saveDir, feedsFilename_Feeds_))); | ||
313 | if (open_File(f, write_FileMode | text_FileMode)) { | ||
314 | lock_Mutex(d->mtx); | ||
315 | iString *str = new_String(); | ||
316 | format_String(str, "%llu\n# Feeds\n", integralSeconds_Time(&d->lastRefreshedAt)); | ||
317 | write_File(f, utf8_String(str)); | ||
318 | /* Index of feeds for IDs. */ { | ||
319 | iConstForEach(PtrArray, i, listSubscriptions_()) { | ||
320 | const iBookmark *bm = i.ptr; | ||
321 | format_String(str, "%08x %s\n", id_Bookmark(bm), cstr_String(&bm->url)); | ||
322 | write_File(f, utf8_String(str)); | ||
323 | } | ||
324 | } | ||
325 | writeData_File(f, "# Entries\n", 10); | ||
326 | iTime now; | ||
327 | initCurrent_Time(&now); | ||
328 | iConstForEach(Array, i, &d->entries.values) { | ||
329 | const iFeedEntry *entry = *(const iFeedEntry **) i.value; | ||
330 | if (secondsSince_Time(&now, &entry->discovered) > maxAge_Visited) { | ||
331 | continue; /* Forget entries discovered long ago. */ | ||
332 | } | ||
333 | format_String(str, "%x\n%llu\n%llu\n%s\n%s\n", | ||
334 | entry->bookmarkId, | ||
335 | integralSeconds_Time(&entry->posted), | ||
336 | integralSeconds_Time(&entry->discovered), | ||
337 | cstr_String(&entry->url), | ||
338 | cstr_String(&entry->title)); | ||
339 | write_File(f, utf8_String(str)); | ||
340 | } | ||
341 | delete_String(str); | ||
342 | close_File(f); | ||
343 | unlock_Mutex(d->mtx); | ||
344 | } | ||
345 | iRelease(f); | ||
346 | } | ||
347 | |||
348 | iDeclareType(FeedHashNode) | 349 | iDeclareType(FeedHashNode) |
349 | 350 | ||
350 | struct Impl_FeedHashNode { | 351 | struct Impl_FeedHashNode { |
@@ -474,7 +475,6 @@ void deinit_Feeds(void) { | |||
474 | stopWorker_Feeds_(d); | 475 | stopWorker_Feeds_(d); |
475 | iAssert(isEmpty_PtrArray(&d->jobs)); | 476 | iAssert(isEmpty_PtrArray(&d->jobs)); |
476 | deinit_PtrArray(&d->jobs); | 477 | deinit_PtrArray(&d->jobs); |
477 | save_Feeds_(d); | ||
478 | deinit_String(&d->saveDir); | 478 | deinit_String(&d->saveDir); |
479 | delete_Mutex(d->mtx); | 479 | delete_Mutex(d->mtx); |
480 | iForEach(Array, i, &d->entries.values) { | 480 | iForEach(Array, i, &d->entries.values) { |