Skip to content

Commit

Permalink
[Feedbin] Fetch saved searches (#873)
Browse files Browse the repository at this point in the history
  • Loading branch information
jocmp authored Feb 17, 2025
1 parent b322951 commit f92294e
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ import com.jocmp.capy.common.withResult
import com.jocmp.capy.db.Database
import com.jocmp.capy.persistence.ArticleRecords
import com.jocmp.capy.persistence.FeedRecords
import com.jocmp.capy.persistence.SavedSearchRecords
import com.jocmp.capy.persistence.TaggingRecords
import com.jocmp.feedbinclient.CreateSubscriptionRequest
import com.jocmp.feedbinclient.CreateTaggingRequest
import com.jocmp.feedbinclient.Entry
import com.jocmp.feedbinclient.Feedbin
import com.jocmp.feedbinclient.Icon
import com.jocmp.feedbinclient.SavedSearch
import com.jocmp.feedbinclient.StarredEntriesRequest
import com.jocmp.feedbinclient.Subscription
import com.jocmp.feedbinclient.UnreadEntriesRequest
Expand All @@ -41,11 +43,13 @@ internal class FeedbinAccountDelegate(
private val articleRecords = ArticleRecords(database)
private val feedRecords = FeedRecords(database)
private val taggingRecords = TaggingRecords(database)
private val savedSearchRecords = SavedSearchRecords(database)

override suspend fun refresh(filter: ArticleFilter, cutoffDate: ZonedDateTime?): Result<Unit> {
return try {
refreshFeeds()
refreshTaggings()
refreshSavedSearches()
refreshArticles(since = maxArrivedAt())

Result.success(Unit)
Expand Down Expand Up @@ -254,6 +258,40 @@ internal class FeedbinAccountDelegate(
}
}

private suspend fun refreshSavedSearches() {
withResult(feedbin.savedSearches()) { savedSearches ->
database.transactionWithErrorHandling {
savedSearches.forEach {
upsertSavedSearch(it)
}

savedSearchRecords.deleteOrphaned(
excludedIDs = savedSearches.map { it.id.toString() }
)
}
}

coroutineScope {
savedSearchRecords.allIDs()
.forEach { savedSearchID ->
launch {
fetchSavedSearchArticles(savedSearchID = savedSearchID)
}
}
}
}

private suspend fun fetchSavedSearchArticles(savedSearchID: String) {
val ids = feedbin.savedSearchEntries(savedSearchID = savedSearchID).body() ?: return

ids.chunked(MAX_ENTRY_LIMIT).map { chunkedIDs ->
fetchPaginatedEntries(
ids = chunkedIDs,
savedSearchID = savedSearchID
)
}
}

private suspend fun refreshAllArticles(since: String) {
fetchPaginatedEntries(since = since)
}
Expand All @@ -273,7 +311,8 @@ internal class FeedbinAccountDelegate(
private suspend fun fetchPaginatedEntries(
since: String? = null,
nextPage: Int? = 1,
ids: List<Long>? = null
ids: List<Long>? = null,
savedSearchID: String? = null,
) {
nextPage ?: return

Expand All @@ -285,7 +324,7 @@ internal class FeedbinAccountDelegate(
val entries = response.body()

if (entries != null) {
saveEntries(entries)
saveEntries(entries, savedSearchID = savedSearchID)
}

fetchPaginatedEntries(
Expand All @@ -295,13 +334,17 @@ internal class FeedbinAccountDelegate(
)
}

private fun saveEntries(entries: List<Entry>) {
private fun saveEntries(
entries: List<Entry>,
savedSearchID: String? = null,
) {
database.transactionWithErrorHandling {
entries.forEach { entry ->
val updated = TimeHelpers.nowUTC()
val articleID = entry.id.toString()

database.articlesQueries.create(
id = entry.id.toString(),
id = articleID,
feed_id = entry.feed_id.toString(),
title = entry.title?.let { Jsoup.parse(it).text() },
author = entry.author,
Expand All @@ -314,14 +357,29 @@ internal class FeedbinAccountDelegate(
)

articleRecords.createStatus(
articleID = entry.id.toString(),
articleID = articleID,
updatedAt = updated,
read = true
)

if (savedSearchID != null) {
savedSearchRecords.upsertArticle(
articleID = articleID,
savedSearchID = savedSearchID,
)
}
}
}
}

private fun upsertSavedSearch(savedSearch: SavedSearch) {
savedSearchRecords.upsert(
id = savedSearch.id.toString(),
name = savedSearch.name,
query = savedSearch.query
)
}

private fun maxArrivedAt() = articleRecords.maxArrivedAt().toString()

private suspend fun fetchIcons(): List<Icon> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.jocmp.capy.db.Database
import com.jocmp.capy.logging.CapyLog
import com.jocmp.capy.persistence.ArticleRecords
import com.jocmp.capy.persistence.FeedRecords
import com.jocmp.capy.persistence.SavedSearchRecords
import com.jocmp.capy.persistence.TaggingRecords
import com.jocmp.feedfinder.DefaultFeedFinder
import com.jocmp.feedfinder.FeedFinder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ internal class ReaderAccountDelegate(
if (labels.contains(category)) {
savedSearchRecords.upsertArticle(
articleID = item.hexID,
id = category,
savedSearchID = category,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ internal class ArticleRecords internal constructor(
unreadSort = unreadSort,
since = since
)

is ArticleFilter.Feeds -> byFeed.findPages(
articleID = articleID,
feedIDs = listOf(filter.feedID),
Expand All @@ -60,6 +61,7 @@ internal class ArticleRecords internal constructor(
unreadSort = unreadSort,
since = since
)

is ArticleFilter.Folders -> byFeed.findPages(
articleID = articleID,
feedIDs = folderFeedIDs(filter),
Expand All @@ -68,6 +70,7 @@ internal class ArticleRecords internal constructor(
unreadSort = unreadSort,
since = since
)

is ArticleFilter.SavedSearches -> bySavedSearch.findPages(
articleID = articleID,
savedSearchID = filter.savedSearchID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,17 @@ internal class SavedSearchRecords(private val database: Database) {
return savedSearchQueries.find(savedSearchID, mapper = ::mapper).executeAsOneOrNull()
}

internal fun upsert(id: String, name: String) {
savedSearchQueries.upsert(id = id, name = name)
internal fun upsert(
id: String,
name: String,
query: String? = null
) {
savedSearchQueries.upsert(id = id, name = name, query_text = query)
}

internal fun upsertArticle(articleID: String, id: String) {
internal fun upsertArticle(articleID: String, savedSearchID: String) {
savedSearchQueries.upsertArticle(
saved_search_id = id,
saved_search_id = savedSearchID,
article_id = articleID
)
}
Expand Down
4 changes: 2 additions & 2 deletions capy/src/main/sqldelight/com/jocmp/capy/db/articles.sq
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ VALUES (
0,
NULL
)
ON CONFLICT(article_id) DO UPDATE
ON CONFLICT DO UPDATE
SET
last_read_at = excluded.last_read_at,
read = excluded.read;
Expand All @@ -143,7 +143,7 @@ VALUES (
:updatedAt,
1
)
ON CONFLICT(article_id) DO UPDATE
ON CONFLICT DO UPDATE
SET
starred = excluded.starred;

Expand Down
6 changes: 4 additions & 2 deletions capy/src/main/sqldelight/com/jocmp/capy/db/saved_searches.sq
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ WHERE id = :id;
upsert:
INSERT OR REPLACE INTO saved_searches(
id,
name
name,
query_text
)
VALUES (
:id,
:name
:name,
:query_text
);

upsertArticle:
Expand Down
5 changes: 5 additions & 0 deletions capy/src/test/java/com/jocmp/capy/RandomID.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.jocmp.capy

import java.security.SecureRandom

fun randomID() = SecureRandom.getInstanceStrong().nextLong()
Loading

0 comments on commit f92294e

Please sign in to comment.