diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-10-01 16:29:39 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-10-01 16:29:39 +0300 |
commit | b5c1e658fb7cd1e81c512323c6c3fa6dfb565522 (patch) | |
tree | 38d5682b56d7989c85cca519a0d94701eb0055e6 /src/feeds.c | |
parent | 692db72f23b6157aad2d450ec47410115c1fb76f (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.c | 53 |
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; |