diff --git a/archive/src/main/kotlin/org/openrs2/archive/cache/CacheExporter.kt b/archive/src/main/kotlin/org/openrs2/archive/cache/CacheExporter.kt index 646dad44..1e298005 100644 --- a/archive/src/main/kotlin/org/openrs2/archive/cache/CacheExporter.kt +++ b/archive/src/main/kotlin/org/openrs2/archive/cache/CacheExporter.kt @@ -3,6 +3,7 @@ package org.openrs2.archive.cache import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonUnwrapped +import io.netty.buffer.ByteBuf import io.netty.buffer.ByteBufAllocator import io.netty.buffer.Unpooled import org.openrs2.buffer.use @@ -443,6 +444,39 @@ public class CacheExporter @Inject constructor( } } + public suspend fun exportGroup(scope: String, id: Int, archive: Int, group: Int): ByteBuf? { + return database.execute { connection -> + connection.prepareStatement(""" + SELECT g.data + FROM resolved_groups g + JOIN scopes s ON s.id = g.scope_id + WHERE s.name = ? AND g.master_index_id = ? AND g.archive_id = ? AND g.group_id = ? + UNION ALL + SELECT f.data + FROM resolved_files f + WHERE f.crc_table_id = ? AND f.index_id = ? AND f.file_id = ? + """.trimIndent()).use { stmt -> + stmt.setString(1, scope) + stmt.setInt(2, id) + stmt.setInt(3, archive) + stmt.setInt(4, group) + stmt.setInt(5, id) + stmt.setInt(6, archive) + stmt.setInt(7, group) + + stmt.executeQuery().use { rows -> + if (!rows.next()) { + return@execute null + } + + val data = rows.getBytes(1) + + return@execute Unpooled.wrappedBuffer(data) + } + } + } + } + public fun export(scope: String, id: Int, storeFactory: (Boolean) -> Store) { database.executeOnce { connection -> val legacy = connection.prepareStatement( diff --git a/archive/src/main/kotlin/org/openrs2/archive/web/CachesController.kt b/archive/src/main/kotlin/org/openrs2/archive/web/CachesController.kt index 00e0e629..5e75d00f 100644 --- a/archive/src/main/kotlin/org/openrs2/archive/web/CachesController.kt +++ b/archive/src/main/kotlin/org/openrs2/archive/web/CachesController.kt @@ -7,14 +7,18 @@ import io.ktor.http.HttpHeaders import io.ktor.http.HttpStatusCode import io.ktor.response.header import io.ktor.response.respond +import io.ktor.response.respondBytes +import io.ktor.response.respondBytesWriter import io.ktor.response.respondOutputStream import io.ktor.thymeleaf.ThymeleafContent import io.netty.buffer.ByteBufAllocator +import io.netty.buffer.ByteBufUtil import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream import org.openrs2.archive.cache.CacheExporter import org.openrs2.archive.map.MapRenderer +import org.openrs2.buffer.use import org.openrs2.cache.DiskStoreZipWriter import org.openrs2.cache.FlatFileStoreTarWriter import org.openrs2.compress.gzip.GzipLevelOutputStream @@ -69,6 +73,28 @@ public class CachesController @Inject constructor( ) } + public suspend fun exportGroup(call: ApplicationCall) { + val scope = call.parameters["scope"]!! + val id = call.parameters["id"]?.toIntOrNull() + val archiveId = call.parameters["archive"]?.toIntOrNull() + val groupId = call.parameters["group"]?.toIntOrNull() + + if (id == null || archiveId == null || groupId == null) { + call.respond(HttpStatusCode.NotFound) + return + } + + exporter.exportGroup(scope, id, archiveId, groupId).use { buf -> + if (buf == null) { + call.respond(HttpStatusCode.NotFound) + return + } + + val bytes = ByteBufUtil.getBytes(buf, 0, buf.readableBytes(), false) + call.respondBytes(bytes, contentType = ContentType.Application.OctetStream) + } + } + public suspend fun exportDisk(call: ApplicationCall) { val scope = call.parameters["scope"]!! val id = call.parameters["id"]?.toIntOrNull() diff --git a/archive/src/main/kotlin/org/openrs2/archive/web/WebServer.kt b/archive/src/main/kotlin/org/openrs2/archive/web/WebServer.kt index 263f8dea..c2733cb9 100644 --- a/archive/src/main/kotlin/org/openrs2/archive/web/WebServer.kt +++ b/archive/src/main/kotlin/org/openrs2/archive/web/WebServer.kt @@ -57,6 +57,7 @@ public class WebServer @Inject constructor( get("/caches") { cachesController.index(call) } get("/caches.json") { cachesController.indexJson(call) } get("/caches/{scope}/{id}") { cachesController.show(call) } + get("/caches/{scope}/{id}/archives/{archive}/groups/{group}.dat") { cachesController.exportGroup(call) } get("/caches/{scope}/{id}/disk.zip") { cachesController.exportDisk(call) } get("/caches/{scope}/{id}/flat-file.tar.gz") { cachesController.exportFlatFile(call) } get("/caches/{scope}/{id}/keys.json") { cachesController.exportKeysJson(call) }