Skip to content

Commit 3bcf336

Browse files
committed
refactor: improve repository platform detection and data merging
- Refactor `CachedRepositoriesDataSourceImpl` to simplify the fetching logic by merging platform-specific requests and filtering results based on the requested platform. - Update `CachedGithubRepoSummary` and its mapper to include and propagate `availablePlatforms`. - Ensure that merging duplicate repositories from different platform-specific caches correctly aggregates their `availablePlatforms`. - Refactor platform detection from asset names in `AppHeader.kt` to use sequential checks instead of a `when` block to support multiple platforms. - Update `SearchRepositoryImpl` to exclude `DiscoveryPlatform.All` when detecting available platforms from asset names. - Enhance the repository merging logic to preserve the highest trending scores, popularity scores, and latest release dates across duplicate entries.
1 parent 6ce1837 commit 3bcf336

5 files changed

Lines changed: 143 additions & 164 deletions

File tree

feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/AppHeader.kt

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,11 @@ fun AppHeader(
211211

212212
if (installedApp != null && installedApp.installedVersion != release?.tagName) {
213213
Text(
214-
text = stringResource(Res.string.installed_version, installedApp.installedVersion),
214+
text =
215+
stringResource(
216+
Res.string.installed_version,
217+
installedApp.installedVersion,
218+
),
215219
style = MaterialTheme.typography.bodySmall,
216220
color = MaterialTheme.colorScheme.onSurfaceVariant,
217221
)
@@ -257,18 +261,13 @@ private fun derivePlatformsFromAssets(release: GithubRelease?): List<DiscoveryPl
257261
if (release == null) return emptyList()
258262
val names = release.assets.map { it.name.lowercase() }
259263
return buildList {
260-
when {
261-
names.any { it.endsWith(".apk") } -> add(DiscoveryPlatform.Android)
262-
263-
names.any { it.endsWith(".exe") || it.endsWith(".msi") } -> add(DiscoveryPlatform.Windows)
264-
265-
names.any { it.endsWith(".dmg") || it.endsWith(".pkg") } -> add(DiscoveryPlatform.Macos)
266-
267-
names.any {
268-
it.endsWith(".appimage") ||
269-
it.endsWith(".deb") ||
270-
it.endsWith(".rpm")
271-
} -> add(DiscoveryPlatform.Linux)
264+
if (names.any { it.endsWith(".apk") }) add(DiscoveryPlatform.Android)
265+
if (names.any { it.endsWith(".exe") || it.endsWith(".msi") }) add(DiscoveryPlatform.Windows)
266+
if (names.any { it.endsWith(".dmg") || it.endsWith(".pkg") }) add(DiscoveryPlatform.Macos)
267+
if (names.any { it.endsWith(".appimage") || it.endsWith(".deb") || it.endsWith(".rpm") }) {
268+
add(
269+
DiscoveryPlatform.Linux,
270+
)
272271
}
273272
}
274273
}

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

Lines changed: 122 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -82,168 +82,143 @@ class CachedRepositoriesDataSourceImpl(
8282
}
8383

8484
return withContext(Dispatchers.IO) {
85-
if (platform == DiscoveryPlatform.All) {
86-
val paths =
87-
when (category) {
88-
HomeCategory.TRENDING -> {
89-
listOf(
90-
"cached-data/trending/android.json",
91-
"cached-data/trending/windows.json",
92-
"cached-data/trending/macos.json",
93-
"cached-data/trending/linux.json",
94-
)
95-
}
85+
val paths =
86+
when (category) {
87+
HomeCategory.TRENDING -> {
88+
listOf(
89+
"cached-data/trending/android.json",
90+
"cached-data/trending/windows.json",
91+
"cached-data/trending/macos.json",
92+
"cached-data/trending/linux.json",
93+
)
94+
}
9695

97-
HomeCategory.HOT_RELEASE -> {
98-
listOf(
99-
"cached-data/new-releases/android.json",
100-
"cached-data/new-releases/windows.json",
101-
"cached-data/new-releases/macos.json",
102-
"cached-data/new-releases/linux.json",
103-
)
104-
}
96+
HomeCategory.HOT_RELEASE -> {
97+
listOf(
98+
"cached-data/new-releases/android.json",
99+
"cached-data/new-releases/windows.json",
100+
"cached-data/new-releases/macos.json",
101+
"cached-data/new-releases/linux.json",
102+
)
103+
}
105104

106-
HomeCategory.MOST_POPULAR -> {
107-
listOf(
108-
"cached-data/most-popular/android.json",
109-
"cached-data/most-popular/windows.json",
110-
"cached-data/most-popular/macos.json",
111-
"cached-data/most-popular/linux.json",
112-
)
113-
}
105+
HomeCategory.MOST_POPULAR -> {
106+
listOf(
107+
"cached-data/most-popular/android.json",
108+
"cached-data/most-popular/windows.json",
109+
"cached-data/most-popular/macos.json",
110+
"cached-data/most-popular/linux.json",
111+
)
114112
}
113+
}
115114

116-
val responses =
117-
coroutineScope {
118-
paths
119-
.map { path ->
120-
async {
121-
val url =
122-
"https://raw.githubusercontent.com/OpenHub-Store/api/main/$path"
123-
try {
124-
logger.debug("Fetching from: $url")
125-
val response: HttpResponse = httpClient.get(url)
126-
if (response.status.isSuccess()) {
127-
json.decodeFromString<CachedRepoResponse>(response.bodyAsText())
128-
} else {
129-
logger.error("HTTP ${response.status.value} from $url")
130-
null
131-
}
132-
} catch (e: SerializationException) {
133-
logger.error("Parse error from $url: ${e.message}")
134-
null
135-
} catch (e: CancellationException) {
136-
throw e
137-
} catch (e: Exception) {
138-
logger.error("Error with $url: ${e.message}")
115+
val responses =
116+
coroutineScope {
117+
paths
118+
.map { path ->
119+
async {
120+
val url = "https://raw.githubusercontent.com/OpenHub-Store/api/main/$path"
121+
val filePlatform =
122+
when {
123+
path.contains("/android") -> DiscoveryPlatform.Android
124+
path.contains("/windows") -> DiscoveryPlatform.Windows
125+
path.contains("/macos") -> DiscoveryPlatform.Macos
126+
path.contains("/linux") -> DiscoveryPlatform.Linux
127+
else -> error("Unknown platform in path: $path")
128+
}
129+
try {
130+
logger.debug("Fetching from: $url")
131+
val response: HttpResponse = httpClient.get(url)
132+
if (response.status.isSuccess()) {
133+
json
134+
.decodeFromString<CachedRepoResponse>(response.bodyAsText())
135+
.let { repoResponse ->
136+
repoResponse.copy(
137+
repositories =
138+
repoResponse.repositories.map {
139+
it.copy(availablePlatforms = listOf(filePlatform))
140+
},
141+
)
142+
}
143+
} else {
144+
logger.error("HTTP ${response.status.value} from $url")
139145
null
140146
}
147+
} catch (e: SerializationException) {
148+
logger.error("Parse error from $url: ${e.message}")
149+
null
150+
} catch (e: CancellationException) {
151+
throw e
152+
} catch (e: Exception) {
153+
logger.error("Error with $url: ${e.message}")
154+
null
141155
}
142-
}.awaitAll()
143-
.filterNotNull()
144-
}
145-
146-
if (responses.isEmpty()) {
147-
logger.error("All mirrors failed for $cacheKey")
148-
return@withContext null
149-
}
150-
151-
val mergedRepos =
152-
responses
153-
.asSequence()
154-
.flatMap { it.repositories.asSequence() }
155-
.groupBy { it.id }
156-
.values
157-
.map { duplicates ->
158-
duplicates.reduce { acc, repo ->
159-
acc.copy(
160-
trendingScore =
161-
listOfNotNull(
162-
acc.trendingScore,
163-
repo.trendingScore,
164-
).maxOrNull(),
165-
popularityScore =
166-
listOfNotNull(
167-
acc.popularityScore,
168-
repo.popularityScore,
169-
).maxOrNull(),
170-
latestReleaseDate =
171-
listOfNotNull(
172-
acc.latestReleaseDate,
173-
repo.latestReleaseDate,
174-
).maxOrNull(),
175-
)
176156
}
177-
}.sortedWith(
178-
compareByDescending<CachedGithubRepoSummary> { it.trendingScore }
179-
.thenByDescending { it.popularityScore }
180-
.thenByDescending { it.latestReleaseDate },
181-
).toList()
182-
183-
val merged =
184-
CachedRepoResponse(
185-
category = responses.first().category,
186-
platform = "all",
187-
lastUpdated = responses.maxOf { it.lastUpdated },
188-
totalCount = mergedRepos.size,
189-
repositories = mergedRepos,
190-
)
191-
192-
if (responses.size == paths.size) {
193-
cacheMutex.withLock {
194-
memoryCache[cacheKey] =
195-
CacheEntry(data = merged, fetchedAt = Clock.System.now())
196-
}
157+
}.awaitAll()
158+
.filterNotNull()
197159
}
198160

199-
merged
200-
} else {
201-
val platformName =
202-
when (platform) {
203-
DiscoveryPlatform.Android -> "android"
204-
DiscoveryPlatform.Windows -> "windows"
205-
DiscoveryPlatform.Macos -> "macos"
206-
DiscoveryPlatform.Linux -> "linux"
207-
DiscoveryPlatform.All -> error("Unreachable: All is handled above")
208-
}
209-
210-
val path =
211-
when (category) {
212-
HomeCategory.TRENDING -> "cached-data/trending/$platformName.json"
213-
HomeCategory.HOT_RELEASE -> "cached-data/new-releases/$platformName.json"
214-
HomeCategory.MOST_POPULAR -> "cached-data/most-popular/$platformName.json"
215-
}
216-
217-
val url = "https://raw.githubusercontent.com/OpenHub-Store/api/main/$path"
218-
219-
try {
220-
logger.debug("Fetching from: $url")
221-
val response: HttpResponse = httpClient.get(url)
222-
223-
if (response.status.isSuccess()) {
224-
val parsed =
225-
json.decodeFromString<CachedRepoResponse>(response.bodyAsText())
161+
if (responses.isEmpty()) {
162+
logger.error("All mirrors failed for $cacheKey")
163+
return@withContext null
164+
}
226165

227-
cacheMutex.withLock {
228-
memoryCache[cacheKey] =
229-
CacheEntry(data = parsed, fetchedAt = Clock.System.now())
166+
val allMergedRepos =
167+
responses
168+
.asSequence()
169+
.flatMap { it.repositories.asSequence() }
170+
.groupBy { it.id }
171+
.values
172+
.map { duplicates ->
173+
duplicates.reduce { acc, repo ->
174+
acc.copy(
175+
availablePlatforms = (acc.availablePlatforms + repo.availablePlatforms).distinct(),
176+
trendingScore =
177+
listOfNotNull(
178+
acc.trendingScore,
179+
repo.trendingScore,
180+
).maxOrNull(),
181+
popularityScore =
182+
listOfNotNull(
183+
acc.popularityScore,
184+
repo.popularityScore,
185+
).maxOrNull(),
186+
latestReleaseDate =
187+
listOfNotNull(
188+
acc.latestReleaseDate,
189+
repo.latestReleaseDate,
190+
).maxOrNull(),
191+
)
230192
}
193+
}.sortedWith(
194+
compareByDescending<CachedGithubRepoSummary> { it.trendingScore }
195+
.thenByDescending { it.popularityScore }
196+
.thenByDescending { it.latestReleaseDate },
197+
)
231198

232-
return@withContext parsed
233-
} else {
234-
logger.error("HTTP ${response.status.value} from $url")
235-
}
236-
} catch (e: SerializationException) {
237-
logger.error("Parse error from $url: ${e.message}")
238-
} catch (e: CancellationException) {
239-
throw e
240-
} catch (e: Exception) {
241-
logger.error("Error with $url: ${e.message}")
199+
val filteredRepos =
200+
when (platform) {
201+
DiscoveryPlatform.All -> allMergedRepos
202+
else -> allMergedRepos.filter { platform in it.availablePlatforms }
203+
}.toList()
204+
205+
val merged =
206+
CachedRepoResponse(
207+
category = responses.first().category,
208+
platform = platform.name.lowercase(),
209+
lastUpdated = responses.maxOf { it.lastUpdated },
210+
totalCount = filteredRepos.size,
211+
repositories = filteredRepos,
212+
)
213+
214+
if (responses.size == paths.size) {
215+
cacheMutex.withLock {
216+
memoryCache[cacheKey] =
217+
CacheEntry(data = merged, fetchedAt = Clock.System.now())
242218
}
243-
244-
logger.error("Fetch failed for $cacheKey")
245-
null
246219
}
220+
221+
merged
247222
}
248223
}
249224

feature/home/data/src/commonMain/kotlin/zed/rainxch/home/data/dto/CachedGithubRepoSummary.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package zed.rainxch.home.data.dto
22

33
import kotlinx.serialization.Serializable
4+
import zed.rainxch.core.domain.model.DiscoveryPlatform
45

56
@Serializable
67
data class CachedGithubRepoSummary(
@@ -20,4 +21,5 @@ data class CachedGithubRepoSummary(
2021
val latestReleaseDate: String? = null,
2122
val trendingScore: Double? = null,
2223
val popularityScore: Int? = null,
24+
val availablePlatforms: List<DiscoveryPlatform> = emptyList(),
2325
)

feature/home/data/src/commonMain/kotlin/zed/rainxch/home/data/mappers/CachedGithubRepoSummaryMappers.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,5 @@ fun CachedGithubRepoSummary.toGithubRepoSummary(): GithubRepoSummary =
2525
topics = topics,
2626
releasesUrl = releasesUrl,
2727
updatedAt = latestReleaseDate ?: updatedAt,
28+
availablePlatforms = availablePlatforms,
2829
)

feature/search/data/src/commonMain/kotlin/zed/rainxch/search/data/repository/SearchRepositoryImpl.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -267,11 +267,13 @@ class SearchRepositoryImpl(
267267

268268
private fun detectAvailablePlatforms(assetNames: List<String>): List<DiscoveryPlatform> =
269269
buildList {
270-
DiscoveryPlatform.entries.forEach { platform ->
271-
if (assetNames.any { assetMatchesPlatform(it, platform) }) {
272-
add(platform)
270+
DiscoveryPlatform.entries
271+
.filter { it != DiscoveryPlatform.All }
272+
.forEach { platform ->
273+
if (assetNames.any { assetMatchesPlatform(it, platform) }) {
274+
add(platform)
275+
}
273276
}
274-
}
275277
}
276278

277279
private suspend fun checkRepoHasInstallers(

0 commit comments

Comments
 (0)