Skip to content

Commit 44a46e3

Browse files
committed
feat: enhance repository discovery and platform-specific filtering
- Update `HomeRepositoryImpl` to support filtering by `HomePlatform` in repository queries, replacing the previous reliance on the device's local platform. - Refactor cache keys in `HomeRepositoryImpl` to include the requested platform, ensuring correct data is cached and retrieved for different platform views. - Improve repository merging logic in `CachedRepositoriesDataSourceImpl` to handle duplicates by taking the maximum value of `trendingScore`, `popularityScore`, and `latestReleaseDate`. - Increase `CACHE_TTL` for cached repositories from 5 minutes to 1 hour. - Rename `platform` to `devicePlatform` in `HomeRepositoryImpl` to clearly distinguish the host device from user-requested platforms. - Update `buildSimplifiedQuery` to handle `HomePlatform.All` by omitting the topic filter. - Fix various formatting issues and add missing imports across the data layer.
1 parent e786d68 commit 44a46e3

3 files changed

Lines changed: 113 additions & 34 deletions

File tree

feature/home/data/src/commonMain/kotlin/zed/rainxch/home/data/data_source/impl/CachedRepositoriesDataSourceImpl.kt

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,10 @@ import io.ktor.client.request.get
66
import io.ktor.client.statement.HttpResponse
77
import io.ktor.client.statement.bodyAsText
88
import io.ktor.http.isSuccess
9-
import kotlinx.coroutines.Deferred
109
import kotlinx.coroutines.Dispatchers
11-
import kotlinx.coroutines.Job
1210
import kotlinx.coroutines.async
1311
import kotlinx.coroutines.awaitAll
1412
import kotlinx.coroutines.coroutineScope
15-
import kotlinx.coroutines.launch
1613
import kotlinx.coroutines.sync.Mutex
1714
import kotlinx.coroutines.sync.withLock
1815
import kotlinx.coroutines.withContext
@@ -24,9 +21,8 @@ import zed.rainxch.home.data.dto.CachedGithubRepoSummary
2421
import zed.rainxch.home.data.dto.CachedRepoResponse
2522
import zed.rainxch.home.domain.model.HomeCategory
2623
import zed.rainxch.home.domain.model.HomePlatform
27-
import kotlin.let
2824
import kotlin.time.Clock
29-
import kotlin.time.Duration.Companion.minutes
25+
import kotlin.time.Duration.Companion.hours
3026
import kotlin.time.ExperimentalTime
3127
import kotlin.time.Instant
3228

@@ -121,7 +117,8 @@ class CachedRepositoriesDataSourceImpl(
121117
paths
122118
.map { path ->
123119
async {
124-
val url = "https://raw.githubusercontent.com/OpenHub-Store/api/main/$path"
120+
val url =
121+
"https://raw.githubusercontent.com/OpenHub-Store/api/main/$path"
125122
try {
126123
logger.debug("Fetching from: $url")
127124
val response: HttpResponse = httpClient.get(url)
@@ -151,9 +148,30 @@ class CachedRepositoriesDataSourceImpl(
151148
val mergedRepos =
152149
responses
153150
.asSequence()
154-
.flatMap { it.repositories }
155-
.distinctBy { it.id }
156-
.sortedWith(
151+
.flatMap { it.repositories.asSequence() }
152+
.groupBy { it.id }
153+
.values
154+
.map { duplicates ->
155+
duplicates.reduce { acc, repo ->
156+
acc.copy(
157+
trendingScore =
158+
listOfNotNull(
159+
acc.trendingScore,
160+
repo.trendingScore,
161+
).maxOrNull(),
162+
popularityScore =
163+
listOfNotNull(
164+
acc.popularityScore,
165+
repo.popularityScore,
166+
).maxOrNull(),
167+
latestReleaseDate =
168+
listOfNotNull(
169+
acc.latestReleaseDate,
170+
repo.latestReleaseDate,
171+
).maxOrNull(),
172+
)
173+
}
174+
}.sortedWith(
157175
compareByDescending<CachedGithubRepoSummary> { it.trendingScore }
158176
.thenByDescending { it.popularityScore }
159177
.thenByDescending { it.latestReleaseDate },
@@ -169,7 +187,8 @@ class CachedRepositoriesDataSourceImpl(
169187
)
170188

171189
cacheMutex.withLock {
172-
memoryCache[cacheKey] = CacheEntry(data = merged, fetchedAt = Clock.System.now())
190+
memoryCache[cacheKey] =
191+
CacheEntry(data = merged, fetchedAt = Clock.System.now())
173192
}
174193

175194
merged
@@ -197,10 +216,12 @@ class CachedRepositoriesDataSourceImpl(
197216
val response: HttpResponse = httpClient.get(url)
198217

199218
if (response.status.isSuccess()) {
200-
val parsed = json.decodeFromString<CachedRepoResponse>(response.bodyAsText())
219+
val parsed =
220+
json.decodeFromString<CachedRepoResponse>(response.bodyAsText())
201221

202222
cacheMutex.withLock {
203-
memoryCache[cacheKey] = CacheEntry(data = parsed, fetchedAt = Clock.System.now())
223+
memoryCache[cacheKey] =
224+
CacheEntry(data = parsed, fetchedAt = Clock.System.now())
204225
}
205226

206227
return@withContext parsed
@@ -220,7 +241,7 @@ class CachedRepositoriesDataSourceImpl(
220241
}
221242

222243
private companion object {
223-
private val CACHE_TTL = 5.minutes
244+
private val CACHE_TTL = 1.hours
224245
}
225246

226247
private data class CacheKey(

feature/home/data/src/commonMain/kotlin/zed/rainxch/home/data/di/SharedModule.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ val homeModule =
1212
HomeRepositoryImpl(
1313
cachedDataSource = get(),
1414
httpClient = get(),
15-
platform = get(),
15+
devicePlatform = get(),
1616
logger = get(),
1717
cacheManager = get(),
1818
)

feature/home/data/src/commonMain/kotlin/zed/rainxch/home/data/repository/HomeRepositoryImpl.kt

Lines changed: 78 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,16 @@ import kotlin.time.ExperimentalTime
4444

4545
class HomeRepositoryImpl(
4646
private val httpClient: HttpClient,
47-
private val platform: Platform,
47+
private val devicePlatform: Platform,
4848
private val cachedDataSource: CachedRepositoriesDataSource,
4949
private val logger: GitHubStoreLogger,
5050
private val cacheManager: CacheManager,
5151
) : HomeRepository {
5252
private fun cacheKey(
5353
category: String,
54+
requestedPlatform: HomePlatform,
5455
page: Int,
55-
): String = "home:$category:${platform.name}:page$page"
56+
): String = "home:$category:${requestedPlatform.name}:page$page"
5657

5758
@OptIn(ExperimentalTime::class)
5859
override fun getTrendingRepositories(
@@ -76,15 +77,31 @@ class HomeRepositoryImpl(
7677
hasMore = false,
7778
nextPageIndex = 2,
7879
)
79-
cacheManager.put(cacheKey("trending", page), result, CacheManager.CacheTtl.HOME_REPOS)
80+
cacheManager.put(
81+
key =
82+
cacheKey(
83+
category = "trending",
84+
requestedPlatform = platform,
85+
page = page,
86+
),
87+
value = result,
88+
ttlMillis = HOME_REPOS,
89+
)
8090
emit(result)
8191
return@flow
8292
} else {
8393
logger.debug("No mirror data, checking local cache...")
8494
}
8595
}
8696

87-
val localCached = cacheManager.get<PaginatedDiscoveryRepositories>(cacheKey("trending", page))
97+
val localCached =
98+
cacheManager.get<PaginatedDiscoveryRepositories>(
99+
cacheKey(
100+
category = "trending",
101+
requestedPlatform = platform,
102+
page = page,
103+
),
104+
)
88105
if (localCached != null && localCached.repos.isNotEmpty()) {
89106
logger.debug("Using locally cached trending repos: ${localCached.repos.size}")
90107
emit(localCached)
@@ -100,6 +117,7 @@ class HomeRepositoryImpl(
100117

101118
emitAll(
102119
searchReposWithInstallersFlow(
120+
platform = platform,
103121
baseQuery = "stars:>50 archived:false pushed:>=$thirtyDaysAgo",
104122
sort = "stars",
105123
order = "desc",
@@ -131,15 +149,31 @@ class HomeRepositoryImpl(
131149
hasMore = false,
132150
nextPageIndex = 2,
133151
)
134-
cacheManager.put(cacheKey("hot_release", page), result, CacheManager.HOME_REPOS)
152+
cacheManager.put(
153+
key =
154+
cacheKey(
155+
category = "hot_release",
156+
requestedPlatform = platform,
157+
page = page,
158+
),
159+
value = result,
160+
ttlMillis = HOME_REPOS,
161+
)
135162
emit(result)
136163
return@flow
137164
} else {
138165
logger.debug("No mirror data, checking local cache...")
139166
}
140167
}
141168

142-
val localCached = cacheManager.get<PaginatedDiscoveryRepositories>(cacheKey("hot_release", page))
169+
val localCached =
170+
cacheManager.get<PaginatedDiscoveryRepositories>(
171+
cacheKey(
172+
category = "hot_release",
173+
requestedPlatform = platform,
174+
page = page,
175+
),
176+
)
143177
if (localCached != null && localCached.repos.isNotEmpty()) {
144178
logger.debug("Using locally cached hot release repos: ${localCached.repos.size}")
145179
emit(localCached)
@@ -155,6 +189,7 @@ class HomeRepositoryImpl(
155189

156190
emitAll(
157191
searchReposWithInstallersFlow(
192+
platform = platform,
158193
baseQuery = "stars:>10 archived:false pushed:>=$fourteenDaysAgo",
159194
sort = "updated",
160195
order = "desc",
@@ -186,15 +221,22 @@ class HomeRepositoryImpl(
186221
hasMore = false,
187222
nextPageIndex = 2,
188223
)
189-
cacheManager.put(cacheKey("most_popular", page), result, HOME_REPOS)
224+
cacheManager.put(cacheKey("most_popular", platform, page), result, HOME_REPOS)
190225
emit(result)
191226
return@flow
192227
} else {
193228
logger.debug("No mirror data, checking local cache...")
194229
}
195230
}
196231

197-
val localCached = cacheManager.get<PaginatedDiscoveryRepositories>(cacheKey("most_popular", page))
232+
val localCached =
233+
cacheManager.get<PaginatedDiscoveryRepositories>(
234+
cacheKey(
235+
category = "most_popular",
236+
requestedPlatform = platform,
237+
page = page,
238+
),
239+
)
198240
if (localCached != null && localCached.repos.isNotEmpty()) {
199241
logger.debug("Using locally cached most popular repos: ${localCached.repos.size}")
200242
emit(localCached)
@@ -217,6 +259,7 @@ class HomeRepositoryImpl(
217259

218260
emitAll(
219261
searchReposWithInstallersFlow(
262+
platform = platform,
220263
baseQuery = "stars:>1000 archived:false created:<$sixMonthsAgo pushed:>=$oneYearAgo",
221264
sort = "stars",
222265
order = "desc",
@@ -227,6 +270,7 @@ class HomeRepositoryImpl(
227270
}.flowOn(Dispatchers.IO)
228271

229272
private fun searchReposWithInstallersFlow(
273+
platform: HomePlatform,
230274
baseQuery: String,
231275
sort: String,
232276
order: String,
@@ -243,7 +287,7 @@ class HomeRepositoryImpl(
243287
var pagesFetchedCount = 0
244288
var lastEmittedCount = 0
245289

246-
val query = buildSimplifiedQuery(baseQuery)
290+
val query = buildSimplifiedQuery(baseQuery, platform)
247291
logger.debug("Query: $query | Sort: $sort | Page: $startPage")
248292

249293
while (results.size < desiredCount && pagesFetchedCount < maxPagesToFetch) {
@@ -346,7 +390,8 @@ class HomeRepositoryImpl(
346390

347391
if (results.size > lastEmittedCount) {
348392
val finalBatch = results.subList(lastEmittedCount, results.size)
349-
val finalHasMore = pagesFetchedCount < maxPagesToFetch && results.size >= desiredCount
393+
val finalHasMore =
394+
pagesFetchedCount < maxPagesToFetch && results.size >= desiredCount
350395
val finalResult =
351396
PaginatedDiscoveryRepositories(
352397
repos = finalBatch.toList(),
@@ -373,21 +418,34 @@ class HomeRepositoryImpl(
373418
hasMore = pagesFetchedCount < maxPagesToFetch && results.size >= desiredCount,
374419
nextPageIndex = currentApiPage + 1,
375420
)
376-
cacheManager.put(cacheKey(category, startPage), allResults, HOME_REPOS)
421+
cacheManager.put(
422+
key =
423+
cacheKey(
424+
category = category,
425+
requestedPlatform = platform,
426+
page = startPage,
427+
),
428+
value = allResults,
429+
ttlMillis = HOME_REPOS,
430+
)
377431
logger.debug("Cached ${results.size} repos for $category page $startPage")
378432
}
379433
}.flowOn(Dispatchers.IO)
380434

381-
private fun buildSimplifiedQuery(baseQuery: String): String {
435+
private fun buildSimplifiedQuery(
436+
baseQuery: String,
437+
requestedPlatform: HomePlatform,
438+
): String {
382439
val topic =
383-
when (platform) {
384-
Platform.ANDROID -> "android"
385-
Platform.WINDOWS -> "desktop"
386-
Platform.MACOS -> "macos"
387-
Platform.LINUX -> "linux"
440+
when (requestedPlatform) {
441+
HomePlatform.All -> null
442+
HomePlatform.Android -> "android"
443+
HomePlatform.Windows -> "desktop"
444+
HomePlatform.Macos -> "macos"
445+
HomePlatform.Linux -> "linux"
388446
}
389447

390-
return "$baseQuery topic:$topic"
448+
return if (topic == null) baseQuery else "$baseQuery topic:$topic"
391449
}
392450

393451
private fun calculatePlatformScore(repo: GithubRepoNetworkModel): Int {
@@ -396,7 +454,7 @@ class HomeRepositoryImpl(
396454
val language = repo.language?.lowercase()
397455
val desc = repo.description?.lowercase() ?: ""
398456

399-
when (platform) {
457+
when (devicePlatform) {
400458
Platform.ANDROID -> {
401459
if (topics.contains("android")) score += 10
402460
if (topics.contains("mobile")) score += 5
@@ -450,7 +508,7 @@ class HomeRepositoryImpl(
450508
val relevantAssets =
451509
stableRelease.assets.filter { asset ->
452510
val name = asset.name.lowercase()
453-
when (platform) {
511+
when (devicePlatform) {
454512
Platform.ANDROID -> {
455513
name.endsWith(".apk")
456514
}

0 commit comments

Comments
 (0)