summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-09-25 08:59:49 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-09-25 08:59:49 +0300
commit69ef98b895be4be6d3471f22e5e153878495523a (patch)
treee86774dab9a818d503be0fe5ebe6f23f69812b56
parentde976d7d8bcfcb7bcc1436e215a8713aefbf091f (diff)
Feeds: Fixed tracking of heading entries; unread count
Heading entries were being discarded from the list of known entries prematurely, causing them to be rediscovered as new later on. The unread count ignores heading entries without a valid discovery time, i.e., the ones from the first update.
-rw-r--r--src/feeds.c109
-rw-r--r--src/feeds.h1
2 files changed, 87 insertions, 23 deletions
diff --git a/src/feeds.c b/src/feeds.c
index 91ef8c2c..9770ca0a 100644
--- a/src/feeds.c
+++ b/src/feeds.c
@@ -50,6 +50,7 @@ void init_FeedEntry(iFeedEntry *d) {
50 init_String(&d->url); 50 init_String(&d->url);
51 init_String(&d->title); 51 init_String(&d->title);
52 d->bookmarkId = 0; 52 d->bookmarkId = 0;
53 d->isHeading = iFalse;
53} 54}
54 55
55void deinit_FeedEntry(iFeedEntry *d) { 56void deinit_FeedEntry(iFeedEntry *d) {
@@ -234,6 +235,7 @@ static void parseResult_FeedJob_(iFeedJob *d) {
234 } 235 }
235 trimStart_Rangecc(&line); 236 trimStart_Rangecc(&line);
236 iFeedEntry *entry = new_FeedEntry(); 237 iFeedEntry *entry = new_FeedEntry();
238 entry->isHeading = iTrue;
237 entry->posted = now; 239 entry->posted = now;
238 if (!d->isFirstUpdate) { 240 if (!d->isFirstUpdate) {
239 entry->discovered = now; 241 entry->discovered = now;
@@ -275,7 +277,8 @@ static void save_Feeds_(iFeeds *d) {
275 initCurrent_Time(&now); 277 initCurrent_Time(&now);
276 iConstForEach(Array, i, &d->entries.values) { 278 iConstForEach(Array, i, &d->entries.values) {
277 const iFeedEntry *entry = *(const iFeedEntry **) i.value; 279 const iFeedEntry *entry = *(const iFeedEntry **) i.value;
278 if (isValid_Time(&entry->discovered) && 280 /* Heading entries are kept as long as they are present in the source. */
281 if (!entry->isHeading && isValid_Time(&entry->discovered) &&
279 secondsSince_Time(&now, &entry->discovered) > maxAge_Visited) { 282 secondsSince_Time(&now, &entry->discovered) > maxAge_Visited) {
280 continue; /* Forget entries discovered long ago. */ 283 continue; /* Forget entries discovered long ago. */
281 } 284 }
@@ -298,25 +301,78 @@ static iBool isHeadingEntry_FeedEntry_(const iFeedEntry *d) {
298 return contains_String(&d->url, '#'); 301 return contains_String(&d->url, '#');
299} 302}
300 303
301static iBool updateEntries_Feeds_(iFeeds *d, iPtrArray *incoming) { 304static iStringSet *listHeadingEntriesFrom_Feeds_(const iFeeds *d, uint32_t sourceId) {
305 iStringSet *set = new_StringSet();
306 iConstForEach(Array, i, &d->entries.values) {
307 const iFeedEntry *entry = *(const iFeedEntry **) i.value;
308 if (entry->bookmarkId == sourceId) {
309 insert_StringSet(set, &entry->url);
310 }
311 }
312 return set;
313}
314
315static iBool updateEntries_Feeds_(iFeeds *d, iBool isHeadings, uint32_t sourceId,
316 iPtrArray *incoming) {
317 /* Entries are removed from `incoming` if they are added to the Feeds entries array.
318 Anything remaining in `incoming` will be deleted afterwards. */
302 iBool gotNew = iFalse; 319 iBool gotNew = iFalse;
303 lock_Mutex(d->mtx); 320 lock_Mutex(d->mtx);
304 iTime now; 321 iTime now;
305 initCurrent_Time(&now); 322 initCurrent_Time(&now);
306 iForEach(PtrArray, i, incoming) { 323 if (isHeadings) {
307 iFeedEntry *entry = i.ptr; 324// printf("Updating sourceID %d...\n", sourceId);
308 /* Disregard old entries. */ 325 iStringSet *known = listHeadingEntriesFrom_Feeds_(d, sourceId);
309 if (secondsSince_Time(&now, &entry->posted) >= maxAge_Visited) { 326// puts(" Known URLs:");
310 /* We don't remember this far back, so the unread status of the entry would 327// iConstForEach(StringSet, ss, known) {
311 be incorrect. */ 328// printf(" {%s}\n", cstr_String(ss.value));
312 continue; 329// }
330 iStringSet *presentInSource = new_StringSet();
331 /* Look for unknown entries. */
332 iForEach(PtrArray, i, incoming) {
333 iFeedEntry *entry = i.ptr;
334 insert_StringSet(presentInSource, &entry->url);
335 if (!contains_StringSet(known, &entry->url)) {
336// printf(" {%s} is new\n", cstr_String(&entry->url));
337 insert_SortedArray(&d->entries, &entry);
338 gotNew = iTrue;
339 remove_PtrArrayIterator(&i);
340 }
341 }
342// puts(" URLs present in source:");
343// iConstForEach(StringSet, ps, presentInSource) {
344// printf(" {%s}\n", cstr_String(ps.value));
345// }
346// puts(" URLs to purge:");
347 /* All known entries that are no longer present in source must be deleted. */
348 iForEach(Array, e, &d->entries.values) {
349 iFeedEntry *entry = *(iFeedEntry **) e.value;
350 if (entry->bookmarkId == sourceId &&
351 !contains_StringSet(presentInSource, &entry->url)) {
352// printf(" {%s}\n", cstr_String(&entry->url));
353 delete_FeedEntry(entry);
354 remove_ArrayIterator(&e);
355 }
313 } 356 }
314 size_t pos; 357// puts("Done.");
315 if (locate_SortedArray(&d->entries, &entry, &pos)) { 358 iRelease(presentInSource);
316 iFeedEntry *existing = *(iFeedEntry **) at_SortedArray(&d->entries, pos); 359 iRelease(known);
317 iAssert(isHeadingEntry_FeedEntry_(existing) == isHeadingEntry_FeedEntry_(entry)); 360 }
318 /* Already known, but update it, maybe the time and label have changed. */ 361 else {
319 if (!isHeadingEntry_FeedEntry_(existing)) { 362 iForEach(PtrArray, i, incoming) {
363 iFeedEntry *entry = i.ptr;
364 /* Disregard old incoming entries. */
365 if (secondsSince_Time(&now, &entry->posted) >= maxAge_Visited) {
366 /* We don't remember this far back, so the unread status of the entry would
367 be incorrect. */
368 continue;
369 }
370 size_t pos;
371 if (locate_SortedArray(&d->entries, &entry, &pos)) {
372 iFeedEntry *existing = *(iFeedEntry **) at_SortedArray(&d->entries, pos);
373 iAssert(!isHeadingEntry_FeedEntry_(existing));
374 iAssert(!isHeadingEntry_FeedEntry_(entry));
375 /* Already known, but update it, maybe the time and label have changed. */
320 iBool changed = iFalse; 376 iBool changed = iFalse;
321 iDate newDate; 377 iDate newDate;
322 iDate oldDate; 378 iDate oldDate;
@@ -336,12 +392,12 @@ static iBool updateEntries_Feeds_(iFeeds *d, iPtrArray *incoming) {
336 gotNew = iTrue; 392 gotNew = iTrue;
337 } 393 }
338 } 394 }
395 else {
396 insert_SortedArray(&d->entries, &entry);
397 gotNew = iTrue;
398 }
399 remove_PtrArrayIterator(&i);
339 } 400 }
340 else {
341 insert_SortedArray(&d->entries, &entry);
342 gotNew = iTrue;
343 }
344 remove_PtrArrayIterator(&i);
345 } 401 }
346 unlock_Mutex(d->mtx); 402 unlock_Mutex(d->mtx);
347 return gotNew; 403 return gotNew;
@@ -369,7 +425,8 @@ static iThreadResult fetch_Feeds_(iThread *thread) {
369 if (isFinished_GmRequest(work[i]->request)) { 425 if (isFinished_GmRequest(work[i]->request)) {
370 /* TODO: Handle redirects. Need to resubmit the job with new URL. */ 426 /* TODO: Handle redirects. Need to resubmit the job with new URL. */
371 parseResult_FeedJob_(work[i]); 427 parseResult_FeedJob_(work[i]);
372 gotNew |= updateEntries_Feeds_(d, &work[i]->results); 428 gotNew |= updateEntries_Feeds_(
429 d, work[i]->checkHeadings, work[i]->bookmarkId, &work[i]->results);
373 delete_FeedJob(work[i]); 430 delete_FeedJob(work[i]);
374 work[i] = NULL; 431 work[i] = NULL;
375 } 432 }
@@ -407,7 +464,7 @@ static iBool startWorker_Feeds_(iFeeds *d) {
407 if (!contains_IntSet(&d->previouslyCheckedFeeds, id_Bookmark(bm))) { 464 if (!contains_IntSet(&d->previouslyCheckedFeeds, id_Bookmark(bm))) {
408 job->isFirstUpdate = iTrue; 465 job->isFirstUpdate = iTrue;
409// printf("first check of %x: %s\n", id_Bookmark(bm), cstr_String(&bm->title)); 466// printf("first check of %x: %s\n", id_Bookmark(bm), cstr_String(&bm->title));
410 fflush(stdout); 467// fflush(stdout);
411 insert_IntSet(&d->previouslyCheckedFeeds, id_Bookmark(bm)); 468 insert_IntSet(&d->previouslyCheckedFeeds, id_Bookmark(bm));
412 } 469 }
413 pushBack_PtrArray(&d->jobs, job); 470 pushBack_PtrArray(&d->jobs, job);
@@ -539,6 +596,11 @@ static void load_Feeds_(iFeeds *d) {
539 stripDefaultUrlPort_String(&entry->url); 596 stripDefaultUrlPort_String(&entry->url);
540 set_String(&entry->url, canonicalUrl_String(&entry->url)); 597 set_String(&entry->url, canonicalUrl_String(&entry->url));
541 set_String(&entry->title, title); 598 set_String(&entry->title, title);
599 entry->isHeading = isHeadingEntry_FeedEntry_(entry);
600 if (entry->isHeading) {
601 printf("[Feeds] src:%d url:{%s}\n", entry->bookmarkId,
602 cstr_String(&entry->url));
603 }
542 insert_SortedArray(&d->entries, &entry); 604 insert_SortedArray(&d->entries, &entry);
543 } 605 }
544 delete_String(title); 606 delete_String(title);
@@ -642,7 +704,8 @@ size_t numUnread_Feeds(void) {
642 size_t max = 100; /* match the number of items shown in the sidebar */ 704 size_t max = 100; /* match the number of items shown in the sidebar */
643 iConstForEach(PtrArray, i, listEntries_Feeds()) { 705 iConstForEach(PtrArray, i, listEntries_Feeds()) {
644 if (!max--) break; 706 if (!max--) break;
645 if (isUnread_FeedEntry(i.ptr)) { 707 const iFeedEntry *entry = i.ptr;
708 if (isValid_Time(&entry->discovered) && isUnread_FeedEntry(i.ptr)) {
646 count++; 709 count++;
647 } 710 }
648 } 711 }
diff --git a/src/feeds.h b/src/feeds.h
index 5ff2adfb..8863d24f 100644
--- a/src/feeds.h
+++ b/src/feeds.h
@@ -34,6 +34,7 @@ struct Impl_FeedEntry {
34 iTime discovered; 34 iTime discovered;
35 iString url; 35 iString url;
36 iString title; 36 iString title;
37 iBool isHeading; /* URL fragment points to a heading */
37 uint32_t bookmarkId; /* note: runtime only, not a persistent ID */ 38 uint32_t bookmarkId; /* note: runtime only, not a persistent ID */
38}; 39};
39 40