Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add download requests #117

Merged
merged 2 commits into from
Aug 8, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,20 @@ package dev.ja.marketplace.client
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.utils.io.*
import io.ktor.utils.io.core.*
import io.ktor.utils.io.jvm.nio.*
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.withContext
import java.nio.channels.WritableByteChannel
import java.nio.file.Files
import java.nio.file.Path
import kotlin.math.min

@OptIn(ExperimentalCoroutinesApi::class)
Expand Down Expand Up @@ -230,10 +237,12 @@ open class KtorMarketplaceClient(
var pendingResultSize = request.maxResults ?: Int.MAX_VALUE
var offset = request.offset
while (pendingResultSize > 0) {
val resultPage = marketplacePluginsSearchSinglePage(request.copy(
offset = offset,
maxResults = min(pageSize, pendingResultSize)
))
val resultPage = marketplacePluginsSearchSinglePage(
request.copy(
offset = offset,
maxResults = min(pageSize, pendingResultSize)
)
)
if (resultPage.searchResult.isEmpty()) {
break
}
Expand Down Expand Up @@ -294,14 +303,41 @@ open class KtorMarketplaceClient(
httpClient.get("$apiPath/updates/$pluginReleaseId/dependencies").body()
}

override suspend fun unsupportedReleaseProducts(pluginReleaseId: PluginReleaseId): List<PluginUnsupportedProduct> = withContext(dispatcher) {
httpClient.get("$apiPath/products-dependencies/updates/$pluginReleaseId/unsupported").body()
}
override suspend fun unsupportedReleaseProducts(pluginReleaseId: PluginReleaseId): List<PluginUnsupportedProduct> =
withContext(dispatcher) {
httpClient.get("$apiPath/products-dependencies/updates/$pluginReleaseId/unsupported").body()
}

override suspend fun pluginDevelopers(plugin: PluginId): List<JetBrainsAccountInfo> = withContext(dispatcher) {
httpClient.get("$apiPath/plugins/$plugin/developers").body()
}

override suspend fun downloadRelease(target: Path, update: PluginReleaseInfo): Unit = withContext(dispatcher) {
Files.newByteChannel(target).use {
it.streamingDownload(update.getMarketplaceDownloadLink())
}
}

override suspend fun downloadRelease(target: Path, update: PluginReleaseId) = withContext(dispatcher) {
Files.newByteChannel(target).use {
it.streamingDownload(URLBuilder(assetUrl("/plugin/download")).apply {
parameters.append("updateId", update.toString())
}.build())
}
}

override suspend fun downloadRelease(target: Path, plugin: PluginId, version: String, channel: String?) = withContext(dispatcher) {
Files.newByteChannel(target).use {
it.streamingDownload(URLBuilder(assetUrl("/plugin/download")).apply {
parameters.append("pluginId", plugin.toString())
parameters.append("version", version)
if (channel != null) {
parameters.append("channel", channel)
}
}.build())
}
}

override suspend fun priceInfo(plugin: PluginId, isoCountryCode: String): PluginPriceInfo = withContext(dispatcher) {
httpClient.get("${apiPath}/marketplace/plugin/${plugin}/prices") {
parameter("countryCode", isoCountryCode)
Expand All @@ -312,6 +348,12 @@ open class KtorMarketplaceClient(
httpClient.get("$apiPath/marketplace/plugin/$plugin/programs").body()
}

private suspend fun WritableByteChannel.streamingDownload(url: Url) {
httpClient.prepareGet(url).execute { httpResponse ->
httpResponse.body<ByteReadChannel>().copyTo(this@streamingDownload)
}
}

protected open suspend fun loadSalesInfo(plugin: PluginId, range: YearMonthDayRange): List<PluginSale> = withContext(dispatcher) {
httpClient.get("$apiPath/marketplace/plugin/$plugin/sales-info") {
parameter("beginDate", range.start.asIsoString)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

package dev.ja.marketplace.client

import java.nio.file.Path

/**
* API available without an API key.
*/
Expand Down Expand Up @@ -77,6 +79,12 @@ interface MarketplaceClientPublic {
*/
suspend fun pluginDevelopers(plugin: PluginId): List<JetBrainsAccountInfo>

suspend fun downloadRelease(target: Path, update: PluginReleaseInfo)

suspend fun downloadRelease(target: Path, update: PluginReleaseId)

suspend fun downloadRelease(target: Path, plugin: PluginId, version: String, channel: String?)

/**
* @param plugin ID of a paid plugin
* @param fullInfo If `true`, then the result will contain the major release versions. This parameter can only be used for paid plugins of the API key's user.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,20 @@ data class PluginInfo(
val isMonetizationAvailable: Boolean? = null,
) : PluginInfoExtended {
override val tagNames: List<PluginTagName>
get() = this.tags.map { it.name }
get() = tags.map(PluginTag::name)

fun getLinkUrl(frontendUrl: Url = Marketplace.MarketplaceFrontendUrl): Url {
return URLBuilder(frontendUrl).also {
it.encodedPath = this.link
}.build()
}

fun getIconUrl(frontendUrl: Url = Marketplace.MarketplaceFrontendUrl): Url? {
this.iconUrlPath ?: return null
return URLBuilder(frontendUrl).also {
it.encodedPath = this.iconUrlPath
}.build()
}
}

@Serializable
Expand Down