Add more information to the individual cache pages

Signed-off-by: Graham <gpe@openrs2.org>
Graham 4 years ago
parent d89c45940b
commit e0a2df889b
  1. 134
      archive/src/main/kotlin/org/openrs2/archive/cache/CacheExporter.kt
  2. 104
      archive/src/main/resources/org/openrs2/archive/templates/caches/show.html

@ -5,11 +5,13 @@ import io.netty.buffer.ByteBufAllocator
import io.netty.buffer.Unpooled import io.netty.buffer.Unpooled
import org.openrs2.buffer.use import org.openrs2.buffer.use
import org.openrs2.cache.Js5Archive import org.openrs2.cache.Js5Archive
import org.openrs2.cache.Js5Compression
import org.openrs2.cache.Js5MasterIndex
import org.openrs2.cache.MasterIndexFormat
import org.openrs2.cache.Store import org.openrs2.cache.Store
import org.openrs2.crypto.XteaKey import org.openrs2.crypto.XteaKey
import org.openrs2.db.Database import org.openrs2.db.Database
import java.time.Instant import java.time.Instant
import java.util.Collections
import java.util.SortedSet import java.util.SortedSet
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -50,19 +52,30 @@ public class CacheExporter @Inject constructor(
} }
} }
public data class Cache( public data class CacheSummary(
val id: Int, val id: Int,
val games: SortedSet<String>, val game: String,
val builds: SortedSet<Int>, val builds: SortedSet<Int>,
val timestamp: Instant?, val timestamp: Instant?,
val names: SortedSet<String>, val names: SortedSet<String>,
val descriptions: List<String>,
val urls: SortedSet<String>,
val stats: Stats? val stats: Stats?
) { )
val game: String
get() = games.single() public data class Cache(
} val id: Int,
val sources: List<Source>,
val stats: Stats?,
val masterIndex: Js5MasterIndex
)
public data class Source(
val game: String,
val build: Int?,
val timestamp: Instant?,
val name: String?,
val description: String?,
val url: String?
)
public data class Key( public data class Key(
val archive: Int, val archive: Int,
@ -73,7 +86,7 @@ public class CacheExporter @Inject constructor(
val key: XteaKey val key: XteaKey
) )
public suspend fun list(): List<Cache> { public suspend fun list(): List<CacheSummary> {
return database.execute { connection -> return database.execute { connection ->
connection.prepareStatement( connection.prepareStatement(
""" """
@ -100,7 +113,7 @@ public class CacheExporter @Inject constructor(
""".trimIndent() """.trimIndent()
).use { stmt -> ).use { stmt ->
stmt.executeQuery().use { rows -> stmt.executeQuery().use { rows ->
val caches = mutableListOf<Cache>() val caches = mutableListOf<CacheSummary>()
while (rows.next()) { while (rows.next()) {
val id = rows.getInt(1) val id = rows.getInt(1)
@ -122,14 +135,12 @@ public class CacheExporter @Inject constructor(
null null
} }
caches += Cache( caches += CacheSummary(
id, id,
sortedSetOf(game), game,
builds.toSortedSet(), builds.toSortedSet(),
timestamp, timestamp,
names.toSortedSet(), names.toSortedSet(),
emptyList(),
Collections.emptySortedSet(),
stats stats
) )
} }
@ -142,15 +153,14 @@ public class CacheExporter @Inject constructor(
public suspend fun get(id: Int): Cache? { public suspend fun get(id: Int): Cache? {
return database.execute { connection -> return database.execute { connection ->
val masterIndex: Js5MasterIndex
val stats: Stats?
connection.prepareStatement( connection.prepareStatement(
""" """
SELECT SELECT
array_remove(array_agg(DISTINCT g.name ORDER BY g.name ASC), NULL), m.format,
array_remove(array_agg(DISTINCT s.build ORDER BY s.build ASC), NULL), c.data,
MIN(s.timestamp),
array_remove(array_agg(DISTINCT s.name ORDER BY s.name ASC), NULL),
array_remove(array_agg(DISTINCT s.description), NULL),
array_remove(array_agg(DISTINCT s.url ORDER BY s.url ASC), NULL),
ms.valid_indexes, ms.valid_indexes,
ms.indexes, ms.indexes,
ms.valid_groups, ms.valid_groups,
@ -159,11 +169,9 @@ public class CacheExporter @Inject constructor(
ms.keys, ms.keys,
ms.size ms.size
FROM master_indexes m FROM master_indexes m
JOIN sources s ON s.master_index_id = m.id JOIN containers c ON c.id = m.container_id
JOIN games g ON g.id = s.game_id
LEFT JOIN master_index_stats ms ON ms.master_index_id = m.id LEFT JOIN master_index_stats ms ON ms.master_index_id = m.id
WHERE m.id = ? WHERE m.id = ?
GROUP BY m.id, ms.valid_indexes, ms.indexes, ms.valid_groups, ms.groups, ms.valid_keys, ms.keys, ms.size
""".trimIndent() """.trimIndent()
).use { stmt -> ).use { stmt ->
stmt.setInt(1, id) stmt.setInt(1, id)
@ -173,38 +181,62 @@ public class CacheExporter @Inject constructor(
return@execute null return@execute null
} }
val games = rows.getArray(1).array as Array<String> val format = MasterIndexFormat.valueOf(rows.getString(1).toUpperCase())
val builds = rows.getArray(2).array as Array<Int>
val timestamp = rows.getTimestamp(3)?.toInstant() masterIndex = Unpooled.wrappedBuffer(rows.getBytes(2)).use { compressed ->
val names = rows.getArray(4).array as Array<String> Js5Compression.uncompress(compressed).use { uncompressed ->
val descriptions = rows.getArray(5).array as Array<String> Js5MasterIndex.read(uncompressed, format)
val urls = rows.getArray(6).array as Array<String> }
}
val validIndexes = rows.getLong(7)
val stats = if (!rows.wasNull()) { val validIndexes = rows.getLong(3)
val indexes = rows.getLong(8) stats = if (rows.wasNull()) {
val validGroups = rows.getLong(9)
val groups = rows.getLong(10)
val validKeys = rows.getLong(11)
val keys = rows.getLong(12)
val size = rows.getLong(13)
Stats(validIndexes, indexes, validGroups, groups, validKeys, keys, size)
} else {
null null
} else {
val indexes = rows.getLong(4)
val validGroups = rows.getLong(5)
val groups = rows.getLong(6)
val validKeys = rows.getLong(7)
val keys = rows.getLong(8)
val size = rows.getLong(9)
Stats(validIndexes, indexes, validGroups, groups, validKeys, keys, size)
} }
}
}
val sources = mutableListOf<Source>()
return@execute Cache( connection.prepareStatement(
id, """
games.toSortedSet(), SELECT g.name, s.build, s.timestamp, s.name, s.description, s.url
builds.toSortedSet(), FROM sources s
timestamp, JOIN games g ON g.id = s.game_id
names.toSortedSet(), WHERE s.master_index_id = ?
descriptions.toList(), ORDER BY s.name ASC
urls.toSortedSet(), """.trimIndent()
stats ).use { stmt ->
) stmt.setInt(1, id)
stmt.executeQuery().use { rows ->
while (rows.next()) {
val game = rows.getString(1)
var build: Int? = rows.getInt(2)
if (rows.wasNull()) {
build = null
}
val timestamp = rows.getTimestamp(3)?.toInstant()
val name = rows.getString(4)
val description = rows.getString(5)
val url = rows.getString(6)
sources += Source(game, build, timestamp, name, description, url)
}
} }
} }
Cache(id, sources, stats, masterIndex)
} }
} }

@ -15,34 +15,8 @@
<!--/*@thymesVar id="cache" type="org.openrs2.archive.cache.CacheExporter.Cache"*/--> <!--/*@thymesVar id="cache" type="org.openrs2.archive.cache.CacheExporter.Cache"*/-->
<table class="table table-striped table-bordered table-hover"> <table class="table table-striped table-bordered table-hover">
<tr class="thead-dark"> <tr class="thead-dark">
<th>Game</th> <th>Format</th>
<td th:text="${#strings.setJoin(cache.games, ', ')}">runescape</td> <td th:text="${cache.masterIndex.format}">VERSIONED</td>
</tr>
<tr class="thead-dark">
<th>Build(s)</th>
<td th:text="${#strings.setJoin(cache.builds, ', ')}">550</td>
</tr>
<tr class="thead-dark">
<th>Timestamp</th>
<td th:text="${#temporals.format(cache.timestamp, 'yyyy-MM-dd HH:mm:ss')}"></td>
</tr>
<tr class="thead-dark">
<th>Source(s)</th>
<td th:text="${#strings.setJoin(cache.names, '/')}"></td>
</tr>
<tr class="thead-dark">
<th>Description</th>
<td th:text="${#strings.listJoin(cache.descriptions, ' ')}"></td>
</tr>
<tr class="thead-dark">
<th>URL(s)</th>
<td>
<ul>
<li th:each="url : ${cache.urls}">
<a th:href="${url}" th:text="${url}">https://www.example.com/</a>
</li>
</ul>
</td>
</tr> </tr>
<tr class="thead-dark"> <tr class="thead-dark">
<th>Indexes</th> <th>Indexes</th>
@ -88,6 +62,80 @@
</td> </td>
</tr> </tr>
</table> </table>
<h2>Sources</h2>
<div class="table-responsive">
<table class="table table-striped table-bordered table-hover">
<thead class="thead-dark">
<tr>
<th>Game</th>
<th>Build</th>
<th>Timestamp</th>
<th>Name</th>
<th>Description</th>
<th>URL</th>
</tr>
</thead>
<tbody>
<tr th:each="source : ${cache.sources}">
<td th:text="${source.game}">runescape</td>
<td th:text="${source.build}" class="text-right">550</td>
<td th:text="${#temporals.format(source.timestamp, 'yyyy-MM-dd HH:mm:ss')}"></td>
<td th:text="${source.name}"></td>
<td th:text="${source.description}"></td>
<td>
<a th:href="${source.url}" th:text="${source.url}" th:if="${source.url}">https://www.example.com/</a>
</td>
</tr>
</tbody>
</table>
</div>
<h2>Master index</h2>
<div class="table-responsive">
<table class="table table-striped table-bordered table-hover">
<thead class="thead-dark">
<tr>
<th>Archive</th>
<th>Version</th>
<th>Checksum</th>
<th>Digest</th>
<th>Groups</th>
<th>Total uncompressed length</th>
</tr>
</thead>
<tbody>
<tr th:each="entry, it : ${cache.masterIndex.entries}">
<td th:text="${it.index}" class="text-right">0</td>
<td th:text="${#numbers.formatInteger(entry.version, 1, 'COMMA')}" class="text-right">0</td>
<td class="text-right">
<code th:text="${entry.checksum}">0</code>
</td>
<td>
<code
th:if="${cache.masterIndex.format >= @org.openrs2.cache.MasterIndexFormat@DIGESTS}"><span
th:remove="tag"
th:text="${@io.netty.buffer.ByteBufUtil@hexDump(entry.digest).substring(0, 64)}"></span>&ZeroWidthSpace;<span
th:remove="tag"
th:text="${@io.netty.buffer.ByteBufUtil@hexDump(entry.digest).substring(64)}"></span></code>
</td>
<td class="text-right">
<span
th:if="${cache.masterIndex.format >= @org.openrs2.cache.MasterIndexFormat@LENGTHS}"
th:text="${#numbers.formatInteger(entry.groups, 1, 'COMMA')}"></span>
</td>
<td class="text-right">
<!--/*@thymesVar id="#byteunits" type="org.openrs2.archive.web.ByteUnits"*/-->
<span
th:if="${cache.masterIndex.format >= @org.openrs2.cache.MasterIndexFormat@LENGTHS}"
th:text="${#byteunits.format(@java.lang.Integer@toUnsignedLong(entry.totalUncompressedLength))}"></span>
</td>
</tr>
</tbody>
</table>
</div>
</main> </main>
</body> </body>
</html> </html>

Loading…
Cancel
Save