Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ Capy Reader is an RSS reader for Android split into several gradle modules
- When naming accessors, prefer "savedSearches" over `getSavedSearches` unless there's a parameter, in which case use "get"
- Prefer explicit named parameters when passing arguments to Jetpack Compose functions over positional arguments.
- JavaScript files are written using JSDoc to ensure typechecking without the overhead of TypeScript.
- Where possible, prefer functional iteration (map, forEach) as opposed to for-loops
- Prefer `orEmpty()` instead of `?: ""`
- Prefer functional iteration (map, forEach) as opposed to for-loops
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ fun Account.buildArticlePager(
query: String? = null,
sortOrder: SortOrder = SortOrder.NEWEST_FIRST,
since: OffsetDateTime = OffsetDateTime.now()
): Pager<Int, Article> {
): Pager<Long, Article> {
return Pager(
config = PagingConfig(
pageSize = 100,
prefetchDistance = 10,
initialLoadSize = 50,
),
pagingSourceFactory = {
ArticlePagerFactory(database).findArticles(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class ArticlePagerFactory(private val database: Database) {
query: String?,
sortOrder: SortOrder,
since: OffsetDateTime
): PagingSource<Int, Article> {
): PagingSource<Long, Article> {
return when (filter) {
is ArticleFilter.Articles -> articleSource(filter, query, sortOrder, since)
is ArticleFilter.Feeds -> feedSource(filter, query, sortOrder, since)
Expand All @@ -35,25 +35,21 @@ class ArticlePagerFactory(private val database: Database) {
query: String?,
sortOrder: SortOrder,
since: OffsetDateTime
): PagingSource<Int, Article> {
): PagingSource<Long, Article> {
return QueryPagingSource(
countQuery = articles.byStatus.count(
transacter = database.articlesQueries,
context = Dispatchers.IO,
pageBoundariesProvider = articles.byStatus.pageBoundaries(
status = filter.status,
query = query,
since = since
since = since,
),
queryProvider = articles.byStatus.keyed(
status = filter.status,
query = query,
sortOrder = sortOrder,
since = since,
),
transacter = database.articlesQueries,
context = Dispatchers.IO,
queryProvider = { limit, offset ->
articles.byStatus.all(
status = filter.status,
query = query,
since = since,
limit = limit,
sortOrder = sortOrder,
offset = offset,
)
}
)
}

Expand All @@ -62,7 +58,7 @@ class ArticlePagerFactory(private val database: Database) {
query: String?,
sortOrder: SortOrder,
since: OffsetDateTime,
): PagingSource<Int, Article> {
): PagingSource<Long, Article> {
val feedIDs = listOf(filter.feedID)

return feedsSource(
Expand All @@ -80,7 +76,7 @@ class ArticlePagerFactory(private val database: Database) {
query: String?,
sortOrder: SortOrder,
since: OffsetDateTime
): PagingSource<Int, Article> {
): PagingSource<Long, Article> {
val feedIDs = database
.taggingsQueries
.findFeedIDs(folderTitle = filter.folderTitle)
Expand All @@ -103,29 +99,25 @@ class ArticlePagerFactory(private val database: Database) {
sortOrder: SortOrder,
priority: FeedPriority,
since: OffsetDateTime
): PagingSource<Int, Article> {
): PagingSource<Long, Article> {
return QueryPagingSource(
countQuery = articles.byFeed.count(
transacter = database.articlesQueries,
context = Dispatchers.IO,
pageBoundariesProvider = articles.byFeed.pageBoundaries(
feedIDs = feedIDs,
status = filter.status,
query = query,
since = since,
priority = priority,
),
transacter = database.articlesQueries,
context = Dispatchers.IO,
queryProvider = { limit, offset ->
articles.byFeed.all(
feedIDs = feedIDs,
status = filter.status,
query = query,
since = since,
limit = limit,
sortOrder = sortOrder,
offset = offset,
priority = priority,
)
}
queryProvider = articles.byFeed.keyed(
feedIDs = feedIDs,
status = filter.status,
query = query,
sortOrder = sortOrder,
since = since,
priority = priority,
),
)
}

Expand All @@ -134,27 +126,23 @@ class ArticlePagerFactory(private val database: Database) {
query: String?,
sortOrder: SortOrder,
since: OffsetDateTime
): PagingSource<Int, Article> {
): PagingSource<Long, Article> {
return QueryPagingSource(
countQuery = articles.bySavedSearch.count(
transacter = database.articlesQueries,
context = Dispatchers.IO,
pageBoundariesProvider = articles.bySavedSearch.pageBoundaries(
savedSearchID = filter.savedSearchID,
status = filter.status,
query = query,
since = since
since = since,
),
queryProvider = articles.bySavedSearch.keyed(
savedSearchID = filter.savedSearchID,
status = filter.status,
query = query,
sortOrder = sortOrder,
since = since,
),
transacter = database.articlesQueries,
context = Dispatchers.IO,
queryProvider = { limit, offset ->
articles.bySavedSearch.all(
savedSearchID = filter.savedSearchID,
status = filter.status,
query = query,
since = since,
limit = limit,
sortOrder = sortOrder,
offset = offset,
)
}
)
}

Expand Down Expand Up @@ -189,25 +177,21 @@ class ArticlePagerFactory(private val database: Database) {
query: String?,
sortOrder: SortOrder,
since: OffsetDateTime
): PagingSource<Int, Article> {
): PagingSource<Long, Article> {
return QueryPagingSource(
countQuery = articles.byToday.count(
transacter = database.articlesQueries,
context = Dispatchers.IO,
pageBoundariesProvider = articles.byToday.pageBoundaries(
status = filter.status,
query = query,
since = since
since = since,
),
queryProvider = articles.byToday.keyed(
status = filter.status,
query = query,
sortOrder = sortOrder,
since = since,
),
transacter = database.articlesQueries,
context = Dispatchers.IO,
queryProvider = { limit, offset ->
articles.byToday.all(
status = filter.status,
query = query,
limit = limit,
sortOrder = sortOrder,
offset = offset,
since = since,
)
}
)
}
}
34 changes: 19 additions & 15 deletions bench/src/main/kotlin/com/jocmp/bench/Commands.kt
Original file line number Diff line number Diff line change
Expand Up @@ -89,30 +89,34 @@ suspend fun commandSelectProfile(account: Account) {

val total = account.countAllByStatus(ArticleStatus.ALL).first()

val boundariesProvider = records.byStatus.pageBoundaries(
status = ArticleStatus.ALL,
)
val queryProvider = records.byStatus.keyed(
status = ArticleStatus.ALL,
sortOrder = SortOrder.NEWEST_FIRST,
)

val (pageCount, pageDuration) = measureTimedValue {
var offset = 0L
val boundaries = boundariesProvider(null, pageSize).executeAsList()
var pages = 0

while (offset < total) {
records.byStatus.all(
status = ArticleStatus.ALL,
limit = pageSize,
offset = offset,
sortOrder = SortOrder.NEWEST_FIRST,
).executeAsList()
offset += pageSize
for (i in boundaries.indices) {
val begin = boundaries[i]
val end = boundaries.getOrNull(i + 1)
queryProvider(begin, end).executeAsList()
pages++
}

pages
}

val firstArticleID = records.byStatus.all(
status = ArticleStatus.ALL,
limit = 1,
offset = 0,
sortOrder = SortOrder.NEWEST_FIRST,
).executeAsOneOrNull()?.id
val firstPage = boundariesProvider(null, 1).executeAsList()
val firstArticleID = if (firstPage.isNotEmpty()) {
queryProvider(firstPage.first(), null).executeAsList().firstOrNull()?.id
} else {
null
}

val (_, findDuration) = measureTimedValue {
if (firstArticleID != null) {
Expand Down
18 changes: 12 additions & 6 deletions capy/src/main/java/com/jocmp/capy/AccountLatestArticlesExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,22 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow

fun Account.latestArticles(limit: Long = 30): Flow<List<Article>> {
return articleRecords
val boundariesProvider = articleRecords
.byStatus
.all(
.pageBoundaries(status = ArticleStatus.UNREAD)

val queryProvider = articleRecords
.byStatus
.keyed(
status = ArticleStatus.UNREAD,
query = null,
since = null,
limit = limit,
sortOrder = SortOrder.NEWEST_FIRST,
offset = 0,
)

val boundaries = boundariesProvider(null, limit).executeAsList()
val begin = boundaries.firstOrNull() ?: return kotlinx.coroutines.flow.flowOf(emptyList())
val end = boundaries.getOrNull(1)

return queryProvider(begin, end)
.asFlow()
.mapToList(Dispatchers.IO)
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,68 @@ class ByArticleStatus(private val database: Database) {
return database.articlesQueries.lastUpdatedAt().executeAsOne().MAX
}

fun pageBoundaries(
status: ArticleStatus,
query: String? = null,
since: OffsetDateTime? = null,
): (anchor: Long?, limit: Long) -> Query<Long> {
val (read, starred) = status.toStatusPair

return { anchor, limit ->
database.articlesByStatusQueries.pageBoundaries(
read = read,
starred = starred,
lastReadAt = mapLastRead(read, since),
lastUnstarredAt = mapLastUnstarred(starred, since),
publishedSince = null,
query = query,
anchor = anchor,
limit = limit,
mapper = { publishedAt -> publishedAt ?: 0L }
)
}
}

fun keyed(
status: ArticleStatus,
query: String? = null,
sortOrder: SortOrder,
since: OffsetDateTime? = null,
): (beginInclusive: Long, endExclusive: Long?) -> Query<Article> {
val (read, starred) = status.toStatusPair
val queries = database.articlesByStatusQueries

return if (isNewestFirst(sortOrder)) {
{ begin, end ->
queries.keyedNewestFirst(
read = read,
starred = starred,
lastReadAt = mapLastRead(read, since),
lastUnstarredAt = mapLastUnstarred(starred, since),
publishedSince = null,
query = query,
beginInclusive = begin,
endExclusive = end,
mapper = ::listMapper,
)
}
} else {
{ begin, end ->
queries.keyedOldestFirst(
read = read,
starred = starred,
lastReadAt = mapLastRead(read, since),
lastUnstarredAt = mapLastUnstarred(starred, since),
publishedSince = null,
query = query,
beginInclusive = begin,
endExclusive = end,
mapper = ::listMapper,
)
}
}
}

fun count(
status: ArticleStatus,
query: String? = null,
Expand Down
Loading
Loading