Skip to content
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
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ jobs:

- uses: gradle/actions/setup-gradle@v5

- name: Run WasmJs browser tests
run: ./gradlew wasmJsBrowserTest
- name: Run WasmJs tests
run: ./gradlew wasmJsBrowserTest wasmJsNodeTest

- name: Upload test results
if: always()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
1 change: 0 additions & 1 deletion library/api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.linroid.kdown.api

import kotlinx.io.files.Path
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient

Expand All @@ -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).
Expand All @@ -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<String, String> = emptyMap(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package com.linroid.kdown.api

import kotlinx.io.files.Path

sealed class DownloadState {
data object Idle : DownloadState()
data class Scheduled(val schedule: DownloadSchedule) : DownloadState()
data object Queued : 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()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand Down Expand Up @@ -60,13 +59,13 @@ interface DownloadTask {
*/
suspend fun remove()

suspend fun await(): Result<Path>
suspend fun await(): Result<String>
}

/**
* Default [await] implementation for [DownloadTask].
*/
suspend fun DownloadTask.awaitDefault(): Result<Path> {
suspend fun DownloadTask.awaitDefault(): Result<String> {
val finalState = state.first { it.isTerminal }
return when (finalState) {
is DownloadState.Completed -> Result.success(finalState.filePath)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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)
Expand All @@ -42,10 +41,10 @@ class DownloadRequestTest {
@Test
fun blankUrl_throws() {
assertFailsWith<IllegalArgumentException> {
DownloadRequest(url = "", directory = Path("/tmp"))
DownloadRequest(url = "", directory = "/tmp")
}
assertFailsWith<IllegalArgumentException> {
DownloadRequest(url = " ", directory = Path("/tmp"))
DownloadRequest(url = " ", directory = "/tmp")
}
}

Expand All @@ -54,7 +53,7 @@ class DownloadRequestTest {
assertFailsWith<IllegalArgumentException> {
DownloadRequest(
url = "https://example.com/file",
directory = Path("/tmp"),
directory = "/tmp",
connections = 0
)
}
Expand All @@ -65,7 +64,7 @@ class DownloadRequestTest {
assertFailsWith<IllegalArgumentException> {
DownloadRequest(
url = "https://example.com/file",
directory = Path("/tmp"),
directory = "/tmp",
connections = -1
)
}
Expand All @@ -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)
}
Expand All @@ -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)
Expand All @@ -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)
}
Expand All @@ -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)
Expand All @@ -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(
Expand All @@ -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
Expand All @@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 2 additions & 6 deletions library/core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -44,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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ class KDown(
)

TaskState.COMPLETED -> DownloadState.Completed(
record.destPath
record.destPath.toString()
)

TaskState.FAILED -> DownloadState.Failed(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,11 @@ internal class DownloadCoordinator(
stateFlow: MutableStateFlow<DownloadState>,
segmentsFlow: MutableStateFlow<List<Segment>>
) {
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(
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.linroid.kdown.api
package com.linroid.kdown.core.file

import kotlinx.io.files.Path
import kotlinx.serialization.KSerializer
Expand All @@ -8,9 +8,11 @@ import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder

object PathSerializer : KSerializer<Path> {
internal object PathSerializer : KSerializer<Path> {
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())
Expand Down
Loading
Loading