summaryrefslogtreecommitdiff
path: root/src/feeds.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-10-01 16:29:39 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-10-01 16:29:39 +0300
commitb5c1e658fb7cd1e81c512323c6c3fa6dfb565522 (patch)
tree38d5682b56d7989c85cca519a0d94701eb0055e6 /src/feeds.c
parent692db72f23b6157aad2d450ec47410115c1fb76f (diff)
Feeds: Don't forget entries or their unread status
Three important changes: 1) Visited URLs can be marked as "kept" so they will never be discarded due to old age. 2) Feed entries are not discarded from the database until they are removed from the source, and then become too old (six months). 3) Visited feed entry URLs are always flagged as kept, so the (un)read status will not be forgotten.
Diffstat (limited to 'src/feeds.c')
-rw-r--r--src/feeds.c53
1 files changed, 44 insertions, 9 deletions
diff --git a/src/feeds.c b/src/feeds.c
index 86f33367..3417b4e9 100644
--- a/src/feeds.c
+++ b/src/feeds.c
@@ -317,10 +317,10 @@ static iBool updateEntries_Feeds_(iFeeds *d, iBool isHeadings, uint32_t sourceId
317 /* Entries are removed from `incoming` if they are added to the Feeds entries array. 317 /* Entries are removed from `incoming` if they are added to the Feeds entries array.
318 Anything remaining in `incoming` will be deleted afterwards. */ 318 Anything remaining in `incoming` will be deleted afterwards. */
319 iBool gotNew = iFalse; 319 iBool gotNew = iFalse;
320 lock_Mutex(d->mtx);
321 iTime now; 320 iTime now;
322 initCurrent_Time(&now); 321 initCurrent_Time(&now);
323 if (isHeadings) { 322 if (isHeadings) {
323 lock_Mutex(d->mtx);
324// printf("Updating sourceID %d...\n", sourceId); 324// printf("Updating sourceID %d...\n", sourceId);
325 iStringSet *known = listHeadingEntriesFrom_Feeds_(d, sourceId); 325 iStringSet *known = listHeadingEntriesFrom_Feeds_(d, sourceId);
326// puts(" Known URLs:"); 326// puts(" Known URLs:");
@@ -357,16 +357,22 @@ static iBool updateEntries_Feeds_(iFeeds *d, iBool isHeadings, uint32_t sourceId
357// puts("Done."); 357// puts("Done.");
358 iRelease(presentInSource); 358 iRelease(presentInSource);
359 iRelease(known); 359 iRelease(known);
360 unlock_Mutex(d->mtx);
360 } 361 }
361 else { 362 else {
363 /* All visited URLs still present in the source should be kept indefinitely so their
364 read status remains correct. The Kept flag will be cleared after the URL has been
365 discarded from the entry database and enough time has passed. */ {
366// printf("updating entries from %d:\n", sourceId);
367 iForEach(PtrArray, i, incoming) {
368 const iFeedEntry *entry = i.ptr;
369// printf("marking as kept: {%s}\n", cstr_String(&entry->url));
370 setUrlKept_Visited(visited_App(), &entry->url, iTrue);
371 }
372 }
373 lock_Mutex(d->mtx);
362 iForEach(PtrArray, i, incoming) { 374 iForEach(PtrArray, i, incoming) {
363 iFeedEntry *entry = i.ptr; 375 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; 376 size_t pos;
371 if (locate_SortedArray(&d->entries, &entry, &pos)) { 377 if (locate_SortedArray(&d->entries, &entry, &pos)) {
372 iFeedEntry *existing = *(iFeedEntry **) at_SortedArray(&d->entries, pos); 378 iFeedEntry *existing = *(iFeedEntry **) at_SortedArray(&d->entries, pos);
@@ -384,7 +390,8 @@ static iBool updateEntries_Feeds_(iFeeds *d, iBool isHeadings, uint32_t sourceId
384 changed = iTrue; 390 changed = iTrue;
385 } 391 }
386 set_String(&existing->title, &entry->title); 392 set_String(&existing->title, &entry->title);
387 existing->posted = entry->posted; 393 existing->posted = entry->posted;
394 existing->discovered = entry->discovered; /* prevent discarding */
388 delete_FeedEntry(entry); 395 delete_FeedEntry(entry);
389 if (changed) { 396 if (changed) {
390 /* TODO: better to use a new flag for read feed entries? */ 397 /* TODO: better to use a new flag for read feed entries? */
@@ -398,8 +405,8 @@ static iBool updateEntries_Feeds_(iFeeds *d, iBool isHeadings, uint32_t sourceId
398 } 405 }
399 remove_PtrArrayIterator(&i); 406 remove_PtrArrayIterator(&i);
400 } 407 }
408 unlock_Mutex(d->mtx);
401 } 409 }
402 unlock_Mutex(d->mtx);
403 return gotNew; 410 return gotNew;
404} 411}
405 412
@@ -410,6 +417,8 @@ static iThreadResult fetch_Feeds_(iThread *thread) {
410 iZap(work); 417 iZap(work);
411 iBool gotNew = iFalse; 418 iBool gotNew = iFalse;
412 postCommand_App("feeds.update.started"); 419 postCommand_App("feeds.update.started");
420 const int totalJobs = size_PtrArray(&d->jobs);
421 int numFinishedJobs = 0;
413 while (!d->stopWorker) { 422 while (!d->stopWorker) {
414 /* Start new jobs. */ 423 /* Start new jobs. */
415 iForIndices(i, work) { 424 iForIndices(i, work) {
@@ -420,6 +429,7 @@ static iThreadResult fetch_Feeds_(iThread *thread) {
420 sleep_Thread(0.5); /* TODO: wait on a Condition so we can exit quickly */ 429 sleep_Thread(0.5); /* TODO: wait on a Condition so we can exit quickly */
421 if (d->stopWorker) break; 430 if (d->stopWorker) break;
422 size_t ongoing = 0; 431 size_t ongoing = 0;
432 iBool doNotify = iFalse;
423 iForIndices(i, work) { 433 iForIndices(i, work) {
424 if (work[i]) { 434 if (work[i]) {
425 if (isFinished_GmRequest(work[i]->request)) { 435 if (isFinished_GmRequest(work[i]->request)) {
@@ -429,11 +439,15 @@ static iThreadResult fetch_Feeds_(iThread *thread) {
429 d, work[i]->checkHeadings, work[i]->bookmarkId, &work[i]->results); 439 d, work[i]->checkHeadings, work[i]->bookmarkId, &work[i]->results);
430 delete_FeedJob(work[i]); 440 delete_FeedJob(work[i]);
431 work[i] = NULL; 441 work[i] = NULL;
442 numFinishedJobs++;
443 doNotify = iTrue;
432 } 444 }
433 else if (isTimedOut_FeedJob_(work[i])) { 445 else if (isTimedOut_FeedJob_(work[i])) {
434 /* Maybe we'll get it next time! */ 446 /* Maybe we'll get it next time! */
435 delete_FeedJob(work[i]); 447 delete_FeedJob(work[i]);
436 work[i] = NULL; 448 work[i] = NULL;
449 numFinishedJobs++;
450 doNotify = iTrue;
437 } 451 }
438 else { 452 else {
439 ongoing++; 453 ongoing++;
@@ -441,6 +455,9 @@ static iThreadResult fetch_Feeds_(iThread *thread) {
441 /* TODO: abort job if it takes too long (> 15 seconds?) */ 455 /* TODO: abort job if it takes too long (> 15 seconds?) */
442 } 456 }
443 } 457 }
458 if (doNotify) {
459 postCommandf_App("feeds.update.progress arg:%d total:%d", numFinishedJobs, totalJobs);
460 }
444 /* Stop if everything has finished. */ 461 /* Stop if everything has finished. */
445 if (ongoing == 0 && isEmpty_PtrArray(&d->jobs)) { 462 if (ongoing == 0 && isEmpty_PtrArray(&d->jobs)) {
446 break; 463 break;
@@ -448,6 +465,24 @@ static iThreadResult fetch_Feeds_(iThread *thread) {
448 } 465 }
449 initCurrent_Time(&d->lastRefreshedAt); 466 initCurrent_Time(&d->lastRefreshedAt);
450 save_Feeds_(d); 467 save_Feeds_(d);
468 /* Check if there are visited URLs marked as Kept that can be cleared because they are no
469 longer present in the database. */ {
470 iStringSet *knownEntryUrls = new_StringSet();
471 lock_Mutex(d->mtx);
472 iConstForEach(Array, i, &d->entries.values) {
473 const iFeedEntry *entry = *(const iFeedEntry **) i.value;
474 insert_StringSet(knownEntryUrls, &entry->url);
475 }
476 unlock_Mutex(d->mtx);
477 iConstForEach(PtrArray, j, listKept_Visited(visited_App())) {
478 iVisitedUrl *visUrl = j.ptr;
479 if (!contains_StringSet(knownEntryUrls, &visUrl->url)) {
480 visUrl->flags &= ~kept_VisitedUrlFlag;
481// printf("unkept: {%s}\n", cstr_String(&visUrl->url));
482 }
483 }
484 iRelease(knownEntryUrls);
485 }
451 postCommandf_App("feeds.update.finished arg:%d unread:%zu", gotNew ? 1 : 0, 486 postCommandf_App("feeds.update.finished arg:%d unread:%zu", gotNew ? 1 : 0,
452 numUnread_Feeds()); 487 numUnread_Feeds());
453 return 0; 488 return 0;