Skip to content

Commit

Permalink
[commonize] #496 Commonize :data-sources:api and ktor
Browse files Browse the repository at this point in the history
  • Loading branch information
Him188 committed Jul 27, 2024
1 parent f654441 commit ec04017
Show file tree
Hide file tree
Showing 44 changed files with 164 additions and 106 deletions.
44 changes: 29 additions & 15 deletions data-sources/api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
*/

plugins {
kotlin("jvm")
kotlin("multiplatform")
`ani-mpp-lib-targets`
kotlin("plugin.serialization")
kotlin("plugin.compose")

Expand All @@ -26,25 +27,38 @@ plugins {
// See https://developer.android.com/develop/ui/compose/performance/stability/fix#configuration-file
// But for simplicity, we just include compose here.
id("org.jetbrains.compose")
`flatten-source-sets`
idea
}

dependencies {
implementation(libs.kotlinx.serialization.core)
api(libs.kotlinx.coroutines.core)
api(projects.utils.ktorClient)
api(projects.utils.serialization)
api(libs.ktor.client.logging)
api(libs.ktor.client.auth)
api(libs.jsoup)
implementation(projects.utils.logging)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(projects.utils.testing)
kotlin {
sourceSets.commonMain {
dependencies {
implementation(libs.kotlinx.serialization.core)
api(libs.kotlinx.coroutines.core)
api(projects.utils.ktorClient)
api(projects.utils.serialization)
implementation(projects.utils.platform)
api(libs.ktor.client.auth)
implementation(libs.ktor.client.logging)
implementation(projects.utils.logging)

implementation(compose.runtime) // required by the compose compiler
}
implementation(compose.runtime) // required by the compose compiler
}
}

sourceSets.commonTest {
dependencies {
implementation(libs.kotlinx.coroutines.test)
implementation(projects.utils.testing)
}
}

sourceSets.jvmMain {
dependencies {
api(libs.jsoup)
}
}
}
idea {
module.generatedSourceDirs.add(file("test/title/generated"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import me.him188.ani.datasources.api.EpisodeSort.Normal
import me.him188.ani.datasources.api.EpisodeSort.Special
import me.him188.ani.datasources.api.topic.EpisodeRange
import me.him188.ani.utils.serialization.BigNum
import java.math.BigDecimal

/**
* 剧集序号, 例如 "01", "24.5", "OVA".
Expand Down Expand Up @@ -128,11 +127,6 @@ fun EpisodeSort(int: Int): EpisodeSort {
return Normal(int.toFloat())
}

fun EpisodeSort(int: BigDecimal): EpisodeSort {
if (int < BigDecimal.ZERO) return Special(int.toString())
return EpisodeSort(int.toString())
}

fun EpisodeSort(int: BigNum): EpisodeSort {
if (int.isNegative()) return Special(int.toString())
return EpisodeSort(int.toString())
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,21 @@ package me.him188.ani.datasources.api.source

import io.ktor.client.HttpClient
import io.ktor.client.HttpClientConfig
import io.ktor.client.call.body
import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.auth.Auth
import io.ktor.client.plugins.auth.providers.BasicAuthCredentials
import io.ktor.client.plugins.auth.providers.BearerTokens
import io.ktor.client.plugins.auth.providers.basic
import io.ktor.client.plugins.auth.providers.bearer
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.statement.HttpResponse
import io.ktor.http.ContentType
import io.ktor.http.content.OutgoingContent
import io.ktor.serialization.ContentConverter
import io.ktor.util.reflect.TypeInfo
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.charsets.decode
import io.ktor.utils.io.jvm.javaio.toInputStream
import io.ktor.utils.io.streams.asInput
import io.ktor.utils.io.core.Closeable
import me.him188.ani.utils.ktor.createDefaultHttpClient
import me.him188.ani.utils.ktor.registerLogging
import me.him188.ani.utils.logging.logger
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import java.nio.charset.Charset

private fun Closeable.asAutoCloseable() = AutoCloseable { close() }

/**
* 支持执行 HTTP 请求的 [MediaSource]. 封装一些便捷的操作
Expand Down Expand Up @@ -76,37 +68,15 @@ fun HttpMediaSource.useHttpClient(
}
expectSuccess = true
install(ContentNegotiation) {
register(ContentType.Text.Xml, XmlConverter)
register(ContentType.Text.Html, XmlConverter)
val xmlConverter = getXmlConverter()
register(ContentType.Text.Xml, xmlConverter)
register(ContentType.Text.Html, xmlConverter)
}

clientConfig()
}.apply {
registerLogging(logger)
}.also { addCloseable(it) }
}.also { addCloseable(it.asAutoCloseable()) }
}

suspend inline fun HttpResponse.bodyAsDocument(): Document = body()

private object XmlConverter : ContentConverter {
override suspend fun deserialize(
charset: Charset,
typeInfo: TypeInfo,
content: ByteReadChannel
): Any? {
if (typeInfo.type.qualifiedName != Document::class.qualifiedName) return null
content.awaitContent()
val decoder = Charsets.UTF_8.newDecoder()
val string = decoder.decode(content.toInputStream().asInput())
return Jsoup.parse(string, charset.name())
}

override suspend fun serializeNullable(
contentType: ContentType,
charset: Charset,
typeInfo: TypeInfo,
value: Any?
): OutgoingContent? {
return null
}
}
internal expect fun getXmlConverter(): ContentConverter
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ import me.him188.ani.datasources.api.Media
import me.him188.ani.datasources.api.paging.SizedSource
import me.him188.ani.datasources.api.topic.EpisodeRange
import me.him188.ani.datasources.api.topic.ResourceLocation
import java.io.Closeable
import java.io.File

/**
* 一个查询单个剧集的可下载的资源 [Media] 的服务, 称为数据源 [MediaSource].
Expand Down Expand Up @@ -79,7 +77,7 @@ import java.io.File
*
* @see MediaSourceFactory
*/
interface MediaSource : Closeable {
interface MediaSource : AutoCloseable {
/**
* 全局唯一的 ID. 可用于保存用户偏好, 识别缓存资源的来源等.
*/
Expand Down Expand Up @@ -186,7 +184,7 @@ sealed class MediaSourceLocation {
data object Lan : MediaSourceLocation()

/**
* 资源位于本地文件系统. 必须是能通过 [File] 直接访问的.
* 资源位于本地文件系统. 必须是能通过 `File` 直接访问的.
*/
data object Local : MediaSourceLocation()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ package me.him188.ani.datasources.api.source

import me.him188.ani.datasources.api.paging.SizedSource
import me.him188.ani.datasources.api.paging.emptySizedSource
import java.util.UUID
import me.him188.ani.utils.platform.Uuid
import kotlin.random.Random


open class TestHttpMediaSource(
override val mediaSourceId: String = UUID.randomUUID().toString(),
override val mediaSourceId: String = Uuid.randomString(),
override val kind: MediaSourceKind = MediaSourceKind.BitTorrent,
private val randomConnectivity: Boolean = false,
private val fetch: suspend (MediaFetchRequest) -> SizedSource<MediaMatch> = { emptySizedSource() }
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import me.him188.ani.datasources.api.topic.EpisodeRange.Combined
import me.him188.ani.datasources.api.topic.EpisodeRange.Range
import me.him188.ani.datasources.api.topic.EpisodeRange.Season
import me.him188.ani.datasources.api.topic.EpisodeRange.Single
import kotlin.jvm.JvmName

/**
* 剧集范围:
Expand Down Expand Up @@ -69,8 +70,8 @@ sealed class EpisodeRange {
if (other is Range && other.start == other.end) {
return value == other.start
}
if (javaClass != other?.javaClass) return false
other as Single
if (other !is Single) return false
other
return value == other.value
}
}
Expand Down Expand Up @@ -104,8 +105,8 @@ sealed class EpisodeRange {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other is Single && other.value == start && other.value == end) return true
if (javaClass != other?.javaClass) return false
other as Range
if (other !is Range) return false
other
return start == other.start && end == other.end
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

@file:Suppress("NOTHING_TO_INLINE")
@file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")

package me.him188.ani.datasources.api.topic

Expand All @@ -27,6 +27,8 @@ import kotlinx.coroutines.flow.reduce
import kotlinx.serialization.Serializable
import me.him188.ani.datasources.api.topic.FileSize.Companion.Unspecified
import me.him188.ani.datasources.api.topic.FileSize.Companion.Zero
import kotlin.jvm.JvmInline
import kotlin.math.round

/**
* ```
Expand Down Expand Up @@ -111,24 +113,28 @@ value class FileSize(
if (gigaBytes == this.inGigaBytes.toDouble()) {
return "${gigaBytes.toLong()} GB"
}
return "${String.format("%.1f", gigaBytes)} GB"
return "${format1f(gigaBytes)} GB"
}
val megaBytes = this.inMegaBytesDouble
if (megaBytes >= 1) {
if (megaBytes == this.inMegaBytes.toDouble()) {
return "${megaBytes.toLong()} MB"
}
return "${String.format("%.1f", megaBytes)} MB"
return "${format1f(megaBytes)} MB"
}
val kiloBytes = this.inKiloBytesDouble
if (kiloBytes >= 1) {
if (kiloBytes == this.inKiloBytes.toDouble()) {
return "${kiloBytes.toLong()} KB"
}
return "${String.format("%.1f", kiloBytes)} KB"
return "${format1f(kiloBytes)} KB"
}
return "${this.inBytes} B"
}

private fun format1f(value: Double): String { // equivalent to `String.format("%.1f", value)`
return (round(value * 10) / 10.0).toString()
}
}

@PublishedApi
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package me.him188.ani.datasources.api.topic

import kotlinx.serialization.Serializable
import java.nio.file.Paths


@Serializable
Expand Down Expand Up @@ -77,7 +76,7 @@ sealed class ResourceLocation {
* `file://`
*/
override val uri: String by lazy {
Paths.get(filePath).toUri().toString()
"file://${filePath}"
}
}
}
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@ package me.him188.ani.datasources.api.topic
import kotlinx.serialization.Serializable
import me.him188.ani.datasources.api.source.DownloadSearchQuery
import me.him188.ani.datasources.api.source.MediaSource
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter

/**
* An item search from a [MediaSource]
Expand Down Expand Up @@ -63,17 +59,6 @@ class Topic(
}
}

private val DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm")

fun Topic.publishedTimeString(): String? {
return publishedTimeMillis?.let {
LocalDateTime.ofInstant(
Instant.ofEpochMilli(it),
ZoneId.systemDefault(),
).format(DATE_FORMAT)
}
}

@Serializable
class TopicDetails(
val tags: List<String>,
Expand Down
File renamed without changes.
9 changes: 9 additions & 0 deletions data-sources/api/src/jvmMain/kotlin/Episode.jvm.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package me.him188.ani.datasources.api

import me.him188.ani.datasources.api.EpisodeSort.Special
import java.math.BigDecimal

fun EpisodeSort(int: BigDecimal): EpisodeSort {
if (int < BigDecimal.ZERO) return Special(int.toString())
return EpisodeSort(int.toString())
}
42 changes: 42 additions & 0 deletions data-sources/api/src/jvmMain/kotlin/source/HttpMediaSource.jvm.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package me.him188.ani.datasources.api.source

import io.ktor.client.call.body
import io.ktor.client.statement.HttpResponse
import io.ktor.http.ContentType
import io.ktor.http.content.OutgoingContent
import io.ktor.serialization.ContentConverter
import io.ktor.util.reflect.TypeInfo
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.charsets.decode
import io.ktor.utils.io.jvm.javaio.toInputStream
import io.ktor.utils.io.streams.asInput
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import java.nio.charset.Charset

suspend inline fun HttpResponse.bodyAsDocument(): Document = body()

internal actual fun getXmlConverter(): ContentConverter = XmlConverter

private object XmlConverter : ContentConverter {
override suspend fun deserialize(
charset: Charset,
typeInfo: TypeInfo,
content: ByteReadChannel
): Any? {
if (typeInfo.type.qualifiedName != Document::class.qualifiedName) return null
content.awaitContent()
val decoder = Charsets.UTF_8.newDecoder()
val string = decoder.decode(content.toInputStream().asInput())
return Jsoup.parse(string, charset.name())
}

override suspend fun serializeNullable(
contentType: ContentType,
charset: Charset,
typeInfo: TypeInfo,
value: Any?
): OutgoingContent? {
return null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package me.him188.ani.datasources.api.source

import io.ktor.serialization.ContentConverter

internal actual fun getXmlConverter(): ContentConverter {
TODO("Not yet implemented")
}
Loading

0 comments on commit ec04017

Please sign in to comment.