summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--res/about/version.gmi4
-rw-r--r--src/feeds.c94
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
114static iBool isSubscribed_(void *context, const iBookmark *bm) {
115 iUnused(context);
116 return indexOfCStr_String(&bm->tags, "subscribed") != iInvalidPos; /* TODO: RegExp with \b */
117}
118
119static const iPtrArray *listSubscriptions_(void) {
120 return list_Bookmarks(bookmarks_App(), NULL, isSubscribed_, NULL);
121}
122
114static iFeedJob *startNextJob_Feeds_(iFeeds *d) { 123static 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
189static 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
180static iBool updateEntries_Feeds_(iFeeds *d, iPtrArray *incoming) { 226static 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
260static iBool isSubscribed_(void *context, const iBookmark *bm) {
261 iUnused(context);
262 return indexOfCStr_String(&bm->tags, "subscribed") != iInvalidPos; /* TODO: RegExp with \b */
263}
264
265static const iPtrArray *listSubscriptions_(void) {
266 return list_Bookmarks(bookmarks_App(), NULL, isSubscribed_, NULL);
267}
268
269static iBool startWorker_Feeds_(iFeeds *d) { 307static 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
311static 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
348iDeclareType(FeedHashNode) 349iDeclareType(FeedHashNode)
349 350
350struct Impl_FeedHashNode { 351struct 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) {