From 83f8149896f7ae1a2615fff9c21c7576e22b8726 Mon Sep 17 00:00:00 2001 From: Lin Zhang Date: Wed, 11 Feb 2026 17:31:36 +0800 Subject: [PATCH 1/4] Switch core wasmJs tests to Node.js and add CI step kotlinx.io.files.Path requires Node.js APIs (require('os')) that are unavailable in ChromeHeadless browser tests. Switch core module from disabled browser tests to nodejs() and add wasmJsNodeTest CI step. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/tests.yml | 3 +++ library/core/build.gradle.kts | 7 +------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d33d2a1d..f675a218 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -101,6 +101,9 @@ jobs: - name: Run WasmJs browser tests run: ./gradlew wasmJsBrowserTest + - name: Run WasmJs Node.js tests + run: ./gradlew wasmJsNodeTest + - name: Upload test results if: always() uses: actions/upload-artifact@v6 diff --git a/library/core/build.gradle.kts b/library/core/build.gradle.kts index 3a54a343..cf610f05 100644 --- a/library/core/build.gradle.kts +++ b/library/core/build.gradle.kts @@ -30,12 +30,7 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { - browser { - testTask { - // kotlinx.io.files.Path requires Node.js APIs unavailable in browser tests - enabled = false - } - } + nodejs() } sourceSets { From ca7deb8916d8dd6c6c9a4e9315a318471fbe57c7 Mon Sep 17 00:00:00 2001 From: Lin Zhang Date: Wed, 11 Feb 2026 17:51:54 +0800 Subject: [PATCH 2/4] Replace Path with String in api module to fix wasmJs browser tests DownloadRequest.directory is now String? (nullable) and DownloadState.Completed.filePath is now String, removing the kotlinx.io dependency from the api module. PathSerializer moved to core module (internal) for TaskRecord persistence. Co-Authored-By: Claude Opus 4.6 --- library/api/build.gradle.kts | 1 - .../com/linroid/kdown/api/DownloadRequest.kt | 5 ++- .../com/linroid/kdown/api/DownloadState.kt | 4 +-- .../com/linroid/kdown/api/DownloadTask.kt | 5 ++- .../linroid/kdown/api/DownloadPriorityTest.kt | 4 +-- .../linroid/kdown/api/DownloadRequestTest.kt | 35 +++++++++++-------- .../linroid/kdown/api/DownloadScheduleTest.kt | 3 +- .../linroid/kdown/api/DownloadStateTest.kt | 5 ++- library/core/build.gradle.kts | 1 + .../kotlin/com/linroid/kdown/core/KDown.kt | 2 +- .../kdown/core/engine/DownloadCoordinator.kt | 16 ++++++--- .../kdown/core/file}/PathSerializer.kt | 8 +++-- .../kdown/core/task/DownloadTaskImpl.kt | 3 +- .../com/linroid/kdown/core/task/TaskRecord.kt | 2 +- .../linroid/kdown/engine/DownloadQueueTest.kt | 3 +- .../kdown/engine/DownloadSchedulerTest.kt | 3 +- .../kdown/engine/ScheduleManagerTest.kt | 3 +- .../kdown/file/DefaultFileNameResolverTest.kt | 3 +- .../kdown/task/InMemoryTaskStoreTest.kt | 4 +-- .../com/linroid/kdown/task/TaskRecordTest.kt | 2 +- .../kdown/remote/RemoteDownloadTask.kt | 3 +- .../com/linroid/kdown/remote/WireMapper.kt | 7 ++-- library/sqlite/build.gradle.kts | 1 + .../com/linroid/kdown/server/TaskMapper.kt | 4 +-- .../kdown/server/api/DownloadRoutes.kt | 3 +- .../linroid/kdown/server/TaskMapperTest.kt | 3 +- 26 files changed, 67 insertions(+), 66 deletions(-) rename library/{api/src/commonMain/kotlin/com/linroid/kdown/api => core/src/commonMain/kotlin/com/linroid/kdown/core/file}/PathSerializer.kt (77%) diff --git a/library/api/build.gradle.kts b/library/api/build.gradle.kts index 2b0f76de..7865964d 100644 --- a/library/api/build.gradle.kts +++ b/library/api/build.gradle.kts @@ -28,7 +28,6 @@ kotlin { commonMain.dependencies { implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.serialization.json) - api(libs.kotlinx.io.core) implementation(libs.kotlinx.datetime) } commonTest.dependencies { diff --git a/library/api/src/commonMain/kotlin/com/linroid/kdown/api/DownloadRequest.kt b/library/api/src/commonMain/kotlin/com/linroid/kdown/api/DownloadRequest.kt index 35debf0c..ac3a0413 100644 --- a/library/api/src/commonMain/kotlin/com/linroid/kdown/api/DownloadRequest.kt +++ b/library/api/src/commonMain/kotlin/com/linroid/kdown/api/DownloadRequest.kt @@ -1,6 +1,5 @@ package com.linroid.kdown.api -import kotlinx.io.files.Path import kotlinx.serialization.Serializable import kotlinx.serialization.Transient @@ -9,6 +8,7 @@ import kotlinx.serialization.Transient * * @property url the HTTP(S) URL to download from. Must not be blank. * @property directory the local directory where the file will be saved. + * When `null`, the implementation chooses a default location. * @property fileName explicit file name to save as. When `null`, the * file name is determined from the server response * (Content-Disposition header, URL path, or a fallback). @@ -33,8 +33,7 @@ import kotlinx.serialization.Transient @Serializable data class DownloadRequest( val url: String, - @Serializable(with = PathSerializer::class) - val directory: Path, + val directory: String? = null, val fileName: String? = null, val connections: Int = 1, val headers: Map = emptyMap(), diff --git a/library/api/src/commonMain/kotlin/com/linroid/kdown/api/DownloadState.kt b/library/api/src/commonMain/kotlin/com/linroid/kdown/api/DownloadState.kt index 93e59e84..bb22ee3a 100644 --- a/library/api/src/commonMain/kotlin/com/linroid/kdown/api/DownloadState.kt +++ b/library/api/src/commonMain/kotlin/com/linroid/kdown/api/DownloadState.kt @@ -1,7 +1,5 @@ package com.linroid.kdown.api -import kotlinx.io.files.Path - sealed class DownloadState { data object Idle : DownloadState() data class Scheduled(val schedule: DownloadSchedule) : DownloadState() @@ -9,7 +7,7 @@ sealed class DownloadState { data object Pending : DownloadState() data class Downloading(val progress: DownloadProgress) : DownloadState() data class Paused(val progress: DownloadProgress) : DownloadState() - data class Completed(val filePath: Path) : DownloadState() + data class Completed(val filePath: String) : DownloadState() data class Failed(val error: KDownError) : DownloadState() data object Canceled : DownloadState() diff --git a/library/api/src/commonMain/kotlin/com/linroid/kdown/api/DownloadTask.kt b/library/api/src/commonMain/kotlin/com/linroid/kdown/api/DownloadTask.kt index cbcacba9..3e76fe98 100644 --- a/library/api/src/commonMain/kotlin/com/linroid/kdown/api/DownloadTask.kt +++ b/library/api/src/commonMain/kotlin/com/linroid/kdown/api/DownloadTask.kt @@ -2,7 +2,6 @@ package com.linroid.kdown.api import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.first -import kotlinx.io.files.Path import kotlin.time.Instant /** @@ -60,13 +59,13 @@ interface DownloadTask { */ suspend fun remove() - suspend fun await(): Result + suspend fun await(): Result } /** * Default [await] implementation for [DownloadTask]. */ -suspend fun DownloadTask.awaitDefault(): Result { +suspend fun DownloadTask.awaitDefault(): Result { val finalState = state.first { it.isTerminal } return when (finalState) { is DownloadState.Completed -> Result.success(finalState.filePath) diff --git a/library/api/src/commonTest/kotlin/com/linroid/kdown/api/DownloadPriorityTest.kt b/library/api/src/commonTest/kotlin/com/linroid/kdown/api/DownloadPriorityTest.kt index dca606f0..50c2a334 100644 --- a/library/api/src/commonTest/kotlin/com/linroid/kdown/api/DownloadPriorityTest.kt +++ b/library/api/src/commonTest/kotlin/com/linroid/kdown/api/DownloadPriorityTest.kt @@ -51,7 +51,7 @@ class DownloadPriorityTest { fun defaultPriority_inRequest_isNormal() { val request = DownloadRequest( url = "https://example.com/file.zip", - directory = kotlinx.io.files.Path("/tmp") + directory = "/tmp" ) assertEquals(DownloadPriority.NORMAL, request.priority) } @@ -60,7 +60,7 @@ class DownloadPriorityTest { fun customPriority_inRequest_isPreserved() { val request = DownloadRequest( url = "https://example.com/file.zip", - directory = kotlinx.io.files.Path("/tmp"), + directory = "/tmp", priority = DownloadPriority.URGENT ) assertEquals(DownloadPriority.URGENT, request.priority) diff --git a/library/api/src/commonTest/kotlin/com/linroid/kdown/api/DownloadRequestTest.kt b/library/api/src/commonTest/kotlin/com/linroid/kdown/api/DownloadRequestTest.kt index 9079df6f..4f6bc18c 100644 --- a/library/api/src/commonTest/kotlin/com/linroid/kdown/api/DownloadRequestTest.kt +++ b/library/api/src/commonTest/kotlin/com/linroid/kdown/api/DownloadRequestTest.kt @@ -1,6 +1,5 @@ package com.linroid.kdown.api -import kotlinx.io.files.Path import kotlinx.serialization.json.Json import kotlin.test.Test import kotlin.test.assertEquals @@ -15,7 +14,7 @@ class DownloadRequestTest { fun defaultConnections_isOne() { val request = DownloadRequest( url = "https://example.com/file", - directory = Path("/tmp") + directory = "/tmp" ) assertEquals(1, request.connections) } @@ -24,7 +23,7 @@ class DownloadRequestTest { fun defaultFileName_isNull() { val request = DownloadRequest( url = "https://example.com/file", - directory = Path("/tmp") + directory = "/tmp" ) assertNull(request.fileName) } @@ -33,7 +32,7 @@ class DownloadRequestTest { fun customFileName_preserved() { val request = DownloadRequest( url = "https://example.com/file", - directory = Path("/tmp"), + directory = "/tmp", fileName = "custom.zip" ) assertEquals("custom.zip", request.fileName) @@ -42,10 +41,10 @@ class DownloadRequestTest { @Test fun blankUrl_throws() { assertFailsWith { - DownloadRequest(url = "", directory = Path("/tmp")) + DownloadRequest(url = "", directory = "/tmp") } assertFailsWith { - DownloadRequest(url = " ", directory = Path("/tmp")) + DownloadRequest(url = " ", directory = "/tmp") } } @@ -54,7 +53,7 @@ class DownloadRequestTest { assertFailsWith { DownloadRequest( url = "https://example.com/file", - directory = Path("/tmp"), + directory = "/tmp", connections = 0 ) } @@ -65,7 +64,7 @@ class DownloadRequestTest { assertFailsWith { DownloadRequest( url = "https://example.com/file", - directory = Path("/tmp"), + directory = "/tmp", connections = -1 ) } @@ -75,7 +74,7 @@ class DownloadRequestTest { fun defaultHeaders_isEmpty() { val request = DownloadRequest( url = "https://example.com/file", - directory = Path("/tmp") + directory = "/tmp" ) assertEquals(emptyMap(), request.headers) } @@ -88,7 +87,7 @@ class DownloadRequestTest { ) val request = DownloadRequest( url = "https://example.com/file", - directory = Path("/tmp"), + directory = "/tmp", headers = headers ) assertEquals(headers, request.headers) @@ -100,7 +99,7 @@ class DownloadRequestTest { fun defaultSpeedLimit_isUnlimited() { val request = DownloadRequest( url = "https://example.com/file", - directory = Path("/tmp") + directory = "/tmp" ) assertTrue(request.speedLimit.isUnlimited) } @@ -110,7 +109,7 @@ class DownloadRequestTest { val limit = SpeedLimit.mbps(10) val request = DownloadRequest( url = "https://example.com/file", - directory = Path("/tmp"), + directory = "/tmp", speedLimit = limit ) assertEquals(limit, request.speedLimit) @@ -122,7 +121,7 @@ class DownloadRequestTest { val json = Json { ignoreUnknownKeys = true } val request = DownloadRequest( url = "https://example.com/file", - directory = Path("/tmp"), + directory = "/tmp", speedLimit = SpeedLimit.kbps(512) ) val serialized = json.encodeToString( @@ -143,7 +142,7 @@ class DownloadRequestTest { val json = Json { ignoreUnknownKeys = true } val request = DownloadRequest( url = "https://example.com/file", - directory = Path("/tmp") + directory = "/tmp" ) val serialized = json.encodeToString( DownloadRequest.serializer(), request @@ -153,4 +152,12 @@ class DownloadRequestTest { ) assertTrue(deserialized.speedLimit.isUnlimited) } + + @Test + fun defaultDirectory_isNull() { + val request = DownloadRequest( + url = "https://example.com/file" + ) + assertNull(request.directory) + } } diff --git a/library/api/src/commonTest/kotlin/com/linroid/kdown/api/DownloadScheduleTest.kt b/library/api/src/commonTest/kotlin/com/linroid/kdown/api/DownloadScheduleTest.kt index a970282a..c42d8cff 100644 --- a/library/api/src/commonTest/kotlin/com/linroid/kdown/api/DownloadScheduleTest.kt +++ b/library/api/src/commonTest/kotlin/com/linroid/kdown/api/DownloadScheduleTest.kt @@ -1,6 +1,5 @@ package com.linroid.kdown.api -import kotlinx.io.files.Path import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs @@ -16,7 +15,7 @@ class DownloadScheduleTest { fun immediate_isDefaultSchedule() { val request = DownloadRequest( url = "https://example.com/file.zip", - directory = Path("/tmp") + directory = "/tmp" ) assertEquals(DownloadSchedule.Immediate, request.schedule) } diff --git a/library/api/src/commonTest/kotlin/com/linroid/kdown/api/DownloadStateTest.kt b/library/api/src/commonTest/kotlin/com/linroid/kdown/api/DownloadStateTest.kt index 498d1672..4b1fb6b3 100644 --- a/library/api/src/commonTest/kotlin/com/linroid/kdown/api/DownloadStateTest.kt +++ b/library/api/src/commonTest/kotlin/com/linroid/kdown/api/DownloadStateTest.kt @@ -1,6 +1,5 @@ package com.linroid.kdown.api -import kotlinx.io.files.Path import kotlin.test.Test import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -73,12 +72,12 @@ class DownloadStateTest { @Test fun completed_isTerminal() { - assertTrue(DownloadState.Completed(Path("/path/file")).isTerminal) + assertTrue(DownloadState.Completed("/path/file").isTerminal) } @Test fun completed_isNotActive() { - assertFalse(DownloadState.Completed(Path("/path/file")).isActive) + assertFalse(DownloadState.Completed("/path/file").isActive) } @Test diff --git a/library/core/build.gradle.kts b/library/core/build.gradle.kts index cf610f05..7024167c 100644 --- a/library/core/build.gradle.kts +++ b/library/core/build.gradle.kts @@ -39,6 +39,7 @@ kotlin { implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.datetime) + implementation(libs.kotlinx.io.core) } commonTest.dependencies { implementation(libs.kotlin.test) diff --git a/library/core/src/commonMain/kotlin/com/linroid/kdown/core/KDown.kt b/library/core/src/commonMain/kotlin/com/linroid/kdown/core/KDown.kt index 815c7b5e..ebbe9700 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/kdown/core/KDown.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/kdown/core/KDown.kt @@ -409,7 +409,7 @@ class KDown( ) TaskState.COMPLETED -> DownloadState.Completed( - record.destPath + record.destPath.toString() ) TaskState.FAILED -> DownloadState.Failed( diff --git a/library/core/src/commonMain/kotlin/com/linroid/kdown/core/engine/DownloadCoordinator.kt b/library/core/src/commonMain/kotlin/com/linroid/kdown/core/engine/DownloadCoordinator.kt index 8c4a965d..63b0e43e 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/kdown/core/engine/DownloadCoordinator.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/kdown/core/engine/DownloadCoordinator.kt @@ -55,9 +55,11 @@ internal class DownloadCoordinator( stateFlow: MutableStateFlow, segmentsFlow: MutableStateFlow> ) { + val directory = request.directory?.let { Path(it) } + ?: error("directory is required for download") val initialDestPath = request.fileName?.let { - Path(request.directory, it) - } ?: request.directory + Path(directory, it) + } ?: directory val now = Clock.System.now() taskStore.save( @@ -186,7 +188,9 @@ internal class DownloadCoordinator( val fileName = sourceInfo.suggestedFileName ?: fileNameResolver.resolve(request, toServerInfo(sourceInfo)) - val destPath = deduplicatePath(request.directory, fileName) + val dir = request.directory?.let { Path(it) } + ?: error("directory is required for download") + val destPath = deduplicatePath(dir, fileName) val now = Clock.System.now() updateTaskRecord(taskId) { @@ -249,7 +253,8 @@ internal class DownloadCoordinator( KDownLogger.i("Coordinator") { "Download completed successfully for taskId=$taskId" } - stateFlow.value = DownloadState.Completed(destPath) + stateFlow.value = + DownloadState.Completed(destPath.toString()) } finally { fileAccessor.close() withContext(NonCancellable) { @@ -440,7 +445,8 @@ internal class DownloadCoordinator( ) } - stateFlow.value = DownloadState.Completed(taskRecord.destPath) + stateFlow.value = + DownloadState.Completed(taskRecord.destPath.toString()) } finally { fileAccessor.close() withContext(NonCancellable) { diff --git a/library/api/src/commonMain/kotlin/com/linroid/kdown/api/PathSerializer.kt b/library/core/src/commonMain/kotlin/com/linroid/kdown/core/file/PathSerializer.kt similarity index 77% rename from library/api/src/commonMain/kotlin/com/linroid/kdown/api/PathSerializer.kt rename to library/core/src/commonMain/kotlin/com/linroid/kdown/core/file/PathSerializer.kt index 9b61eeaf..d307b068 100644 --- a/library/api/src/commonMain/kotlin/com/linroid/kdown/api/PathSerializer.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/kdown/core/file/PathSerializer.kt @@ -1,4 +1,4 @@ -package com.linroid.kdown.api +package com.linroid.kdown.core.file import kotlinx.io.files.Path import kotlinx.serialization.KSerializer @@ -8,9 +8,11 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -object PathSerializer : KSerializer { +internal object PathSerializer : KSerializer { override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("kotlinx.io.files.Path", PrimitiveKind.STRING) + PrimitiveSerialDescriptor( + "kotlinx.io.files.Path", PrimitiveKind.STRING + ) override fun serialize(encoder: Encoder, value: Path) { encoder.encodeString(value.toString()) diff --git a/library/core/src/commonMain/kotlin/com/linroid/kdown/core/task/DownloadTaskImpl.kt b/library/core/src/commonMain/kotlin/com/linroid/kdown/core/task/DownloadTaskImpl.kt index 95ceb8ee..d260acc9 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/kdown/core/task/DownloadTaskImpl.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/kdown/core/task/DownloadTaskImpl.kt @@ -11,7 +11,6 @@ import com.linroid.kdown.api.Segment import com.linroid.kdown.api.DownloadTask import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.first -import kotlinx.io.files.Path import kotlin.time.Instant internal class DownloadTaskImpl( @@ -60,7 +59,7 @@ internal class DownloadTaskImpl( removeAction() } - override suspend fun await(): Result { + override suspend fun await(): Result { val finalState = state.first { it.isTerminal } return when (finalState) { is DownloadState.Completed -> Result.success(finalState.filePath) diff --git a/library/core/src/commonMain/kotlin/com/linroid/kdown/core/task/TaskRecord.kt b/library/core/src/commonMain/kotlin/com/linroid/kdown/core/task/TaskRecord.kt index 381c270b..369403c0 100644 --- a/library/core/src/commonMain/kotlin/com/linroid/kdown/core/task/TaskRecord.kt +++ b/library/core/src/commonMain/kotlin/com/linroid/kdown/core/task/TaskRecord.kt @@ -2,7 +2,7 @@ package com.linroid.kdown.core.task import com.linroid.kdown.api.DownloadRequest import com.linroid.kdown.core.engine.SourceResumeState -import com.linroid.kdown.api.PathSerializer +import com.linroid.kdown.core.file.PathSerializer import com.linroid.kdown.api.Segment import kotlinx.io.files.Path import kotlinx.serialization.Serializable diff --git a/library/core/src/commonTest/kotlin/com/linroid/kdown/engine/DownloadQueueTest.kt b/library/core/src/commonTest/kotlin/com/linroid/kdown/engine/DownloadQueueTest.kt index 462cd530..f81d30fe 100644 --- a/library/core/src/commonTest/kotlin/com/linroid/kdown/engine/DownloadQueueTest.kt +++ b/library/core/src/commonTest/kotlin/com/linroid/kdown/engine/DownloadQueueTest.kt @@ -22,7 +22,6 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout -import kotlinx.io.files.Path import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs @@ -41,7 +40,7 @@ class DownloadQueueTest { priority: DownloadPriority = DownloadPriority.NORMAL ) = DownloadRequest( url = url, - directory = Path("/tmp"), + directory = "/tmp", priority = priority ) diff --git a/library/core/src/commonTest/kotlin/com/linroid/kdown/engine/DownloadSchedulerTest.kt b/library/core/src/commonTest/kotlin/com/linroid/kdown/engine/DownloadSchedulerTest.kt index 11fbfe27..f978bb18 100644 --- a/library/core/src/commonTest/kotlin/com/linroid/kdown/engine/DownloadSchedulerTest.kt +++ b/library/core/src/commonTest/kotlin/com/linroid/kdown/engine/DownloadSchedulerTest.kt @@ -22,7 +22,6 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout -import kotlinx.io.files.Path import kotlin.test.Test import kotlin.test.assertIs import kotlin.test.assertTrue @@ -35,7 +34,7 @@ class DownloadSchedulerTest { priority: DownloadPriority = DownloadPriority.NORMAL ) = DownloadRequest( url = "https://example.com/file.zip", - directory = Path("/tmp"), + directory = "/tmp", priority = priority ) diff --git a/library/core/src/commonTest/kotlin/com/linroid/kdown/engine/ScheduleManagerTest.kt b/library/core/src/commonTest/kotlin/com/linroid/kdown/engine/ScheduleManagerTest.kt index f72ffe76..1222c383 100644 --- a/library/core/src/commonTest/kotlin/com/linroid/kdown/engine/ScheduleManagerTest.kt +++ b/library/core/src/commonTest/kotlin/com/linroid/kdown/engine/ScheduleManagerTest.kt @@ -26,7 +26,6 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout -import kotlinx.io.files.Path import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -43,7 +42,7 @@ class ScheduleManagerTest { conditions: List = emptyList() ) = DownloadRequest( url = "https://example.com/file.zip", - directory = Path("/tmp"), + directory = "/tmp", schedule = schedule, conditions = conditions, priority = DownloadPriority.NORMAL diff --git a/library/core/src/commonTest/kotlin/com/linroid/kdown/file/DefaultFileNameResolverTest.kt b/library/core/src/commonTest/kotlin/com/linroid/kdown/file/DefaultFileNameResolverTest.kt index 3aff14ac..ffc2ebb0 100644 --- a/library/core/src/commonTest/kotlin/com/linroid/kdown/file/DefaultFileNameResolverTest.kt +++ b/library/core/src/commonTest/kotlin/com/linroid/kdown/file/DefaultFileNameResolverTest.kt @@ -3,14 +3,13 @@ package com.linroid.kdown.file import com.linroid.kdown.api.DownloadRequest import com.linroid.kdown.core.engine.ServerInfo import com.linroid.kdown.core.file.DefaultFileNameResolver -import kotlinx.io.files.Path import kotlin.test.Test import kotlin.test.assertEquals class DefaultFileNameResolverTest { private val resolver = DefaultFileNameResolver() - private val dir = Path("/tmp") + private val dir = "/tmp" private fun serverInfo(contentDisposition: String? = null) = ServerInfo( contentLength = 1000, diff --git a/library/core/src/commonTest/kotlin/com/linroid/kdown/task/InMemoryTaskStoreTest.kt b/library/core/src/commonTest/kotlin/com/linroid/kdown/task/InMemoryTaskStoreTest.kt index 9f2aad8a..2fd640bc 100644 --- a/library/core/src/commonTest/kotlin/com/linroid/kdown/task/InMemoryTaskStoreTest.kt +++ b/library/core/src/commonTest/kotlin/com/linroid/kdown/task/InMemoryTaskStoreTest.kt @@ -7,10 +7,10 @@ import com.linroid.kdown.core.task.TaskState import kotlinx.coroutines.test.runTest import kotlinx.io.files.Path import kotlin.test.Test -import kotlin.time.Instant import kotlin.test.assertEquals import kotlin.test.assertNull import kotlin.test.assertTrue +import kotlin.time.Instant class InMemoryTaskStoreTest { @@ -21,7 +21,7 @@ class InMemoryTaskStoreTest { taskId = taskId, request = DownloadRequest( url = "https://example.com/file.bin", - directory = Path("/tmp"), + directory = "/tmp", connections = 4, headers = mapOf("Authorization" to "Bearer token") ), diff --git a/library/core/src/commonTest/kotlin/com/linroid/kdown/task/TaskRecordTest.kt b/library/core/src/commonTest/kotlin/com/linroid/kdown/task/TaskRecordTest.kt index a3d53a8d..88e62843 100644 --- a/library/core/src/commonTest/kotlin/com/linroid/kdown/task/TaskRecordTest.kt +++ b/library/core/src/commonTest/kotlin/com/linroid/kdown/task/TaskRecordTest.kt @@ -22,7 +22,7 @@ class TaskRecordTest { headers: Map = emptyMap() ) = DownloadRequest( url = url, - directory = Path("/tmp"), + directory = "/tmp", connections = connections, headers = headers ) diff --git a/library/remote/src/commonMain/kotlin/com/linroid/kdown/remote/RemoteDownloadTask.kt b/library/remote/src/commonMain/kotlin/com/linroid/kdown/remote/RemoteDownloadTask.kt index 8dd26035..31a244fe 100644 --- a/library/remote/src/commonMain/kotlin/com/linroid/kdown/remote/RemoteDownloadTask.kt +++ b/library/remote/src/commonMain/kotlin/com/linroid/kdown/remote/RemoteDownloadTask.kt @@ -24,7 +24,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.first -import kotlinx.io.files.Path import kotlinx.serialization.json.Json import kotlin.time.Instant @@ -127,7 +126,7 @@ internal class RemoteDownloadTask( ) } - override suspend fun await(): Result { + override suspend fun await(): Result { val finalState = state.first { it.isTerminal } return when (finalState) { is DownloadState.Completed -> diff --git a/library/remote/src/commonMain/kotlin/com/linroid/kdown/remote/WireMapper.kt b/library/remote/src/commonMain/kotlin/com/linroid/kdown/remote/WireMapper.kt index d4a8c167..1a020487 100644 --- a/library/remote/src/commonMain/kotlin/com/linroid/kdown/remote/WireMapper.kt +++ b/library/remote/src/commonMain/kotlin/com/linroid/kdown/remote/WireMapper.kt @@ -7,7 +7,6 @@ import com.linroid.kdown.api.DownloadState import com.linroid.kdown.api.KDownError import com.linroid.kdown.api.Segment import com.linroid.kdown.api.SpeedLimit -import kotlinx.io.files.Path import kotlin.time.Instant internal object WireMapper { @@ -15,7 +14,7 @@ internal object WireMapper { fun toDownloadRequest(wire: WireTaskResponse): DownloadRequest { return DownloadRequest( url = wire.url, - directory = Path(wire.directory), + directory = wire.directory, fileName = wire.fileName, connections = 1, speedLimit = if (wire.speedLimitBytesPerSecond > 0) { @@ -30,7 +29,7 @@ internal object WireMapper { fun toCreateWire(request: DownloadRequest): WireCreateDownloadRequest { return WireCreateDownloadRequest( url = request.url, - directory = request.directory.toString(), + directory = request.directory ?: "", fileName = request.fileName, connections = request.connections, headers = request.headers, @@ -69,7 +68,7 @@ internal object WireMapper { ?: DownloadProgress(0, 0) ) "completed" -> DownloadState.Completed( - Path(filePath ?: "") + filePath ?: "" ) "failed" -> DownloadState.Failed( KDownError.Unknown(cause = Exception(error ?: "Unknown")) diff --git a/library/sqlite/build.gradle.kts b/library/sqlite/build.gradle.kts index 8a8d59a2..49252c64 100644 --- a/library/sqlite/build.gradle.kts +++ b/library/sqlite/build.gradle.kts @@ -30,6 +30,7 @@ kotlin { implementation(libs.sqldelight.coroutines) implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.serialization.json) + implementation(libs.kotlinx.io.core) } androidMain.dependencies { implementation(libs.sqldelight.android.driver) diff --git a/server/src/main/kotlin/com/linroid/kdown/server/TaskMapper.kt b/server/src/main/kotlin/com/linroid/kdown/server/TaskMapper.kt index 3240d1a8..f25e0a1a 100644 --- a/server/src/main/kotlin/com/linroid/kdown/server/TaskMapper.kt +++ b/server/src/main/kotlin/com/linroid/kdown/server/TaskMapper.kt @@ -17,7 +17,7 @@ internal object TaskMapper { return TaskResponse( taskId = task.taskId, url = task.request.url, - directory = task.request.directory.toString(), + directory = task.request.directory ?: "", fileName = task.request.fileName, state = stateToString(state), progress = extractProgress(state)?.let(::toProgressResponse), @@ -100,7 +100,7 @@ internal object TaskMapper { private fun extractFilePath(state: DownloadState): String? { return when (state) { - is DownloadState.Completed -> state.filePath.toString() + is DownloadState.Completed -> state.filePath else -> null } } diff --git a/server/src/main/kotlin/com/linroid/kdown/server/api/DownloadRoutes.kt b/server/src/main/kotlin/com/linroid/kdown/server/api/DownloadRoutes.kt index a6e84fe0..0d091fce 100644 --- a/server/src/main/kotlin/com/linroid/kdown/server/api/DownloadRoutes.kt +++ b/server/src/main/kotlin/com/linroid/kdown/server/api/DownloadRoutes.kt @@ -18,7 +18,6 @@ import io.ktor.server.routing.get import io.ktor.server.routing.post import io.ktor.server.routing.put import io.ktor.server.routing.route -import kotlinx.io.files.Path /** * Installs the `/api/downloads` REST routes for managing tasks. @@ -51,7 +50,7 @@ internal fun Route.downloadRoutes(kdown: KDownApi) { } val request = DownloadRequest( url = body.url, - directory = Path(body.directory), + directory = body.directory, fileName = body.fileName, connections = body.connections, headers = body.headers, diff --git a/server/src/test/kotlin/com/linroid/kdown/server/TaskMapperTest.kt b/server/src/test/kotlin/com/linroid/kdown/server/TaskMapperTest.kt index cac5a1a0..72ac0d94 100644 --- a/server/src/test/kotlin/com/linroid/kdown/server/TaskMapperTest.kt +++ b/server/src/test/kotlin/com/linroid/kdown/server/TaskMapperTest.kt @@ -5,7 +5,6 @@ import com.linroid.kdown.api.DownloadSchedule import com.linroid.kdown.api.DownloadState import com.linroid.kdown.api.KDownError import com.linroid.kdown.api.Segment -import kotlinx.io.files.Path import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNull @@ -36,7 +35,7 @@ class TaskMapperTest { DownloadState.Paused(DownloadProgress(500, 1000)) )) assertEquals("completed", TaskMapper.stateToString( - DownloadState.Completed(Path("/tmp/file.zip")) + DownloadState.Completed("/tmp/file.zip") )) assertEquals("failed", TaskMapper.stateToString( DownloadState.Failed( From 3471c4146cc206c0eb1617b9e53b459319ff61b1 Mon Sep 17 00:00:00 2001 From: Lin Zhang Date: Wed, 11 Feb 2026 17:54:48 +0800 Subject: [PATCH 3/4] Fix build error --- .../src/commonMain/kotlin/com/linroid/kdown/examples/App.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/App.kt b/examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/App.kt index b35fd32e..731aefdd 100644 --- a/examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/App.kt +++ b/examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/App.kt @@ -211,7 +211,7 @@ fun App() { scope = scope, kdown = kdown, url = url, - directory = Path("downloads"), + directory = "downloads", fileName = fileName.ifBlank { null }, speedLimit = speedLimit, priority = priority, @@ -879,7 +879,7 @@ private fun startDownload( scope: CoroutineScope, kdown: KDownApi, url: String, - directory: Path, + directory: String, fileName: String?, speedLimit: SpeedLimit = SpeedLimit.Unlimited, priority: DownloadPriority = DownloadPriority.NORMAL, From 4f537284c9a7f1b6bfad4ec21056ca9b1b7d4ec5 Mon Sep 17 00:00:00 2001 From: Lin Zhang Date: Wed, 11 Feb 2026 17:57:47 +0800 Subject: [PATCH 4/4] Run both browser and node in single step --- .github/workflows/tests.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f675a218..f8e0763a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -98,11 +98,8 @@ jobs: - uses: gradle/actions/setup-gradle@v5 - - name: Run WasmJs browser tests - run: ./gradlew wasmJsBrowserTest - - - name: Run WasmJs Node.js tests - run: ./gradlew wasmJsNodeTest + - name: Run WasmJs tests + run: ./gradlew wasmJsBrowserTest wasmJsNodeTest - name: Upload test results if: always()