Skip to content

Commit

Permalink
Added /files/{uuids} endpoint (#188)
Browse files Browse the repository at this point in the history
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
ArnyminerZ authored Nov 22, 2024
1 parent e81504f commit addd554
Showing 8 changed files with 189 additions and 24 deletions.
26 changes: 15 additions & 11 deletions src/main/kotlin/server/endpoints/files/RequestFileEndpoint.kt
Original file line number Diff line number Diff line change
@@ -11,26 +11,28 @@ import kotlinx.coroutines.withContext
import server.endpoints.EndpointBase
import server.error.Errors
import server.response.files.RequestFileResponseData
import server.response.files.RequestFilesResponseData
import server.response.respondFailure
import server.response.respondSuccess
import storage.HashUtils
import storage.MessageDigestAlgorithm
import storage.Storage

@Deprecated("This endpoint shall be removed once the new client is deployed")
object RequestFileEndpoint : EndpointBase("/file/{uuids}") {
private const val DEFAULT_HTTP_PORT = 80

private val digest = MessageDigest.getInstance(MessageDigestAlgorithm.SHA_256)

private suspend fun RoutingContext.getDataFor(uuid: String): RequestFileResponseData.Data {
private suspend fun RoutingContext.getDataFor(uuid: String): RequestFilesResponseData.Data {
val file = Storage.find(uuid) ?: throw FileNotFoundException("Could not find file with uuid $uuid")
val downloadAddress = call.request.origin.let { p ->
val port = p.serverPort.takeIf { it != DEFAULT_HTTP_PORT }?.let { ":$it" } ?: ""
"${p.scheme}://${p.serverHost}$port/download/$uuid"
}
val size = withContext(Dispatchers.IO) { Files.size(file.toPath()) }

return RequestFileResponseData.Data(
return RequestFilesResponseData.Data(
uuid = uuid,
hash = HashUtils.getCheckSumFromFile(digest, file),
filename = file.name,
@@ -45,17 +47,19 @@ object RequestFileEndpoint : EndpointBase("/file/{uuids}") {

// It's impossible that "list" has size 0
try {
respondSuccess(
data = if (list.size <= 1) {
RequestFileResponseData(
listOf(getDataFor(uuids))
if (list.size <= 1) {
respondSuccess(
data = RequestFileResponseData(
getDataFor(uuids)
)
} else {
RequestFileResponseData(
list.map { getDataFor(it) }
)
} else {
respondSuccess(
data = RequestFilesResponseData(
files = list.map { getDataFor(it) }
)
}
)
)
}
} catch (_: FileNotFoundException) {
respondFailure(Errors.FileNotFound)
}
63 changes: 63 additions & 0 deletions src/main/kotlin/server/endpoints/files/RequestFilesEndpoint.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package server.endpoints.files

import io.ktor.server.plugins.origin
import io.ktor.server.routing.RoutingContext
import io.ktor.server.util.getValue
import java.io.FileNotFoundException
import java.nio.file.Files
import java.security.MessageDigest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import server.endpoints.EndpointBase
import server.error.Errors
import server.response.files.RequestFilesResponseData
import server.response.respondFailure
import server.response.respondSuccess
import storage.HashUtils
import storage.MessageDigestAlgorithm
import storage.Storage

object RequestFilesEndpoint : EndpointBase("/files/{uuids}") {
private const val DEFAULT_HTTP_PORT = 80

private val digest = MessageDigest.getInstance(MessageDigestAlgorithm.SHA_256)

private suspend fun RoutingContext.getDataFor(uuid: String): RequestFilesResponseData.Data {
val file = Storage.find(uuid) ?: throw FileNotFoundException("Could not find file with uuid $uuid")
val downloadAddress = call.request.origin.let { p ->
val port = p.serverPort.takeIf { it != DEFAULT_HTTP_PORT }?.let { ":$it" } ?: ""
"${p.scheme}://${p.serverHost}$port/download/$uuid"
}
val size = withContext(Dispatchers.IO) { Files.size(file.toPath()) }

return RequestFilesResponseData.Data(
uuid = uuid,
hash = HashUtils.getCheckSumFromFile(digest, file),
filename = file.name,
download = downloadAddress,
size = size
)
}

override suspend fun RoutingContext.endpoint() {
val uuids: String by call.parameters
val list = uuids.split(",")

// It's impossible that "list" has size 0
try {
respondSuccess(
data = if (list.size <= 1) {
RequestFilesResponseData(
listOf(getDataFor(uuids))
)
} else {
RequestFilesResponseData(
list.map { getDataFor(it) }
)
}
)
} catch (_: FileNotFoundException) {
respondFailure(Errors.FileNotFound)
}
}
}
2 changes: 2 additions & 0 deletions src/main/kotlin/server/plugins/Routing.kt
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ import server.endpoints.delete.DeleteSectorEndpoint
import server.endpoints.delete.DeleteZoneEndpoint
import server.endpoints.files.DownloadFileEndpoint
import server.endpoints.files.RequestFileEndpoint
import server.endpoints.files.RequestFilesEndpoint
import server.endpoints.info.ServerInfoEndpoint
import server.endpoints.patch.PatchAreaEndpoint
import server.endpoints.patch.PatchPathEndpoint
@@ -84,6 +85,7 @@ fun Application.configureEndpoints() {
patch(PatchBlockEndpoint)

get(RequestFileEndpoint)
get(RequestFilesEndpoint)
get(DownloadFileEndpoint)
}
}
21 changes: 12 additions & 9 deletions src/main/kotlin/server/response/files/RequestFileResponseData.kt
Original file line number Diff line number Diff line change
@@ -6,16 +6,19 @@ import server.response.ResponseData

@KoverIgnore
@Serializable
@Deprecated("This response shall be removed once the new client is deployed. See RequestFileEndpoint.")
data class RequestFileResponseData(
val files: List<Data>
val uuid: String,
val hash: String,
val filename: String,
val download: String,
val size: Long
): ResponseData {
@KoverIgnore
@Serializable
data class Data(
val uuid: String,
val hash: String,
val filename: String,
val download: String,
val size: Long
constructor(data: RequestFilesResponseData.Data) : this(
data.uuid,
data.hash,
data.filename,
data.download,
data.size
)
}
21 changes: 21 additions & 0 deletions src/main/kotlin/server/response/files/RequestFilesResponseData.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package server.response.files

import KoverIgnore
import kotlinx.serialization.Serializable
import server.response.ResponseData

@KoverIgnore
@Serializable
data class RequestFilesResponseData(
val files: List<Data>
): ResponseData {
@KoverIgnore
@Serializable
data class Data(
val uuid: String,
val hash: String,
val filename: String,
val download: String,
val size: Long
)
}
6 changes: 3 additions & 3 deletions src/test/kotlin/server/base/Patching.kt
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import server.base.ApplicationTestBase.Companion.AUTH_TOKEN
import server.base.patch.PropertyValuePair
import server.response.files.RequestFileResponseData
import server.response.files.RequestFilesResponseData
import storage.HashUtils
import storage.MessageDigestAlgorithm
import storage.Storage
@@ -155,8 +155,8 @@ fun <Type: BaseEntity> ApplicationTestBase.testPatchingFile(

// Only fetch file if the request was not a removal
if (resourcePath != null) {
get("/file/$elementFile").apply {
assertSuccess<RequestFileResponseData> { response ->
get("/files/$elementFile").apply {
assertSuccess<RequestFilesResponseData> { response ->
val data = response?.files?.first()
assertNotNull(data)
val serverHash = data.hash
3 changes: 2 additions & 1 deletion src/test/kotlin/server/endpoints/files/TestFileFetching.kt
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ import server.DataProvider
import server.base.ApplicationTestBase
import server.error.Errors
import server.response.files.RequestFileResponseData
import server.response.files.RequestFilesResponseData

class TestFileFetching : ApplicationTestBase() {
@Test
@@ -60,7 +61,7 @@ class TestFileFetching : ApplicationTestBase() {
val zone: Zone = ServerDatabase.instance.query { Zone[zoneId] }

get("/file/${area.image.name},${zone.image.name}").apply {
assertSuccess<RequestFileResponseData> { data ->
assertSuccess<RequestFilesResponseData> { data ->
assertNotNull(data)

val files = data.files
71 changes: 71 additions & 0 deletions src/test/kotlin/server/endpoints/files/TestFilesFetching.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package server.endpoints.files

import ServerDatabase
import assertions.assertFailure
import assertions.assertSuccess
import database.entity.Area
import database.entity.Zone
import server.DataProvider
import server.base.ApplicationTestBase
import server.error.Errors
import server.response.files.RequestFilesResponseData
import kotlin.test.Test
import kotlin.test.assertNotNull
import kotlin.test.assertTrue

class TestFilesFetching : ApplicationTestBase() {
@Test
fun `test data`() = test {
val areaId = DataProvider.provideSampleArea(this)
assertNotNull(areaId)

val area: Area = ServerDatabase.instance.query { Area[areaId] }

get("/files/${area.image.name}").apply {
assertSuccess<RequestFilesResponseData> { data ->
assertNotNull(data)
}
}
}

@Test
fun `test data no extension`() = test {
val areaId = DataProvider.provideSampleArea(this)
assertNotNull(areaId)

val area: Area = ServerDatabase.instance.query { Area[areaId] }

get("/files/${area.image.nameWithoutExtension}").apply {
assertSuccess<RequestFilesResponseData> { data ->
assertNotNull(data)
}
}
}

@Test
fun `test doesn't exist`() = test {
get("/files/unknown").apply {
assertFailure(Errors.FileNotFound)
}
}

@Test
fun `test data multiple`() = test {
val areaId = DataProvider.provideSampleArea(this)
assertNotNull(areaId)
val zoneId = DataProvider.provideSampleZone(this, areaId)
assertNotNull(zoneId)

val area: Area = ServerDatabase.instance.query { Area[areaId] }
val zone: Zone = ServerDatabase.instance.query { Zone[zoneId] }

get("/files/${area.image.name},${zone.image.name}").apply {
assertSuccess<RequestFilesResponseData> { data ->
assertNotNull(data)

val files = data.files
assertTrue(files.isNotEmpty())
}
}
}
}

0 comments on commit addd554

Please sign in to comment.