diff --git a/cache/src/main/kotlin/org/openrs2/cache/FlatFileStoreZipWriter.kt b/cache/src/main/kotlin/org/openrs2/cache/FlatFileStoreZipWriter.kt new file mode 100644 index 00000000..3a1b6125 --- /dev/null +++ b/cache/src/main/kotlin/org/openrs2/cache/FlatFileStoreZipWriter.kt @@ -0,0 +1,91 @@ +package org.openrs2.cache + +import io.netty.buffer.ByteBuf +import java.nio.file.attribute.FileTime +import java.time.Instant +import java.util.zip.Deflater +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream + +/** + * A specialised [Store] implementation that writes a cache in the + * [FlatFileStore] format to a [ZipOutputStream]. + * + * The cache is not buffered to disk. + * + * This implementation only supports the [create] and [write] methods. All + * other methods throw [UnsupportedOperationException]. + * + * It is only intended for use by the cache archiving service's web interface. + */ +public class FlatFileStoreZipWriter( + private val out: ZipOutputStream, + private val prefix: String = "cache/", + level: Int = Deflater.BEST_COMPRESSION, + timestamp: Instant = Instant.EPOCH +) : Store { + private val timestamp = FileTime.from(timestamp) + + init { + out.setLevel(level) + } + + private fun createZipEntry(name: String): ZipEntry { + val entry = ZipEntry(prefix + name) + entry.creationTime = timestamp + entry.lastAccessTime = timestamp + entry.lastModifiedTime = timestamp + return entry + } + + override fun exists(archive: Int): Boolean { + throw UnsupportedOperationException() + } + + override fun exists(archive: Int, group: Int): Boolean { + throw UnsupportedOperationException() + } + + override fun list(): List { + throw UnsupportedOperationException() + } + + override fun list(archive: Int): List { + throw UnsupportedOperationException() + } + + override fun create(archive: Int) { + require(archive in 0..Store.MAX_ARCHIVE) + + out.putNextEntry(createZipEntry("$archive/")) + } + + override fun read(archive: Int, group: Int): ByteBuf { + throw UnsupportedOperationException() + } + + override fun write(archive: Int, group: Int, buf: ByteBuf) { + require(archive in 0..Store.MAX_ARCHIVE) + require(group >= 0) + require(buf.readableBytes() <= Store.MAX_GROUP_SIZE) + + out.putNextEntry(createZipEntry("$archive/$group.dat")) + buf.readBytes(out, buf.readableBytes()) + } + + override fun remove(archive: Int) { + throw UnsupportedOperationException() + } + + override fun remove(archive: Int, group: Int) { + throw UnsupportedOperationException() + } + + override fun flush() { + out.flush() + } + + override fun close() { + out.close() + } +} diff --git a/cache/src/test/kotlin/org/openrs2/cache/DiskStoreZipWriterTest.kt b/cache/src/test/kotlin/org/openrs2/cache/DiskStoreZipWriterTest.kt index b95d61e8..a970b83e 100644 --- a/cache/src/test/kotlin/org/openrs2/cache/DiskStoreZipWriterTest.kt +++ b/cache/src/test/kotlin/org/openrs2/cache/DiskStoreZipWriterTest.kt @@ -118,6 +118,6 @@ class DiskStoreZipWriterTest { } private companion object { - private val ROOT = Path.of(DiskStoreZipWriterTest::class.java.getResource("zip").toURI()) + private val ROOT = Path.of(DiskStoreZipWriterTest::class.java.getResource("disk-store-zip").toURI()) } } diff --git a/cache/src/test/kotlin/org/openrs2/cache/FlatFileStoreZipWriterTest.kt b/cache/src/test/kotlin/org/openrs2/cache/FlatFileStoreZipWriterTest.kt new file mode 100644 index 00000000..4e371c90 --- /dev/null +++ b/cache/src/test/kotlin/org/openrs2/cache/FlatFileStoreZipWriterTest.kt @@ -0,0 +1,125 @@ +package org.openrs2.cache + +import com.google.common.jimfs.Configuration +import com.google.common.jimfs.Jimfs +import io.netty.buffer.Unpooled +import org.openrs2.buffer.copiedBuffer +import org.openrs2.buffer.use +import org.openrs2.util.io.recursiveEquals +import java.io.OutputStream +import java.nio.file.Files +import java.nio.file.Path +import java.util.zip.ZipOutputStream +import kotlin.test.Test +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +public class FlatFileStoreZipWriterTest { + @Test + fun testBounds() { + FlatFileStoreZipWriter(ZipOutputStream(OutputStream.nullOutputStream())).use { store -> + // create + assertFailsWith { + store.create(-1) + } + + store.create(0) + store.create(1) + store.create(254) + store.create(255) + + assertFailsWith { + store.create(256) + } + + // write archive + assertFailsWith { + store.write(-1, 0, Unpooled.EMPTY_BUFFER) + } + + store.write(0, 0, Unpooled.EMPTY_BUFFER) + store.write(1, 0, Unpooled.EMPTY_BUFFER) + store.write(254, 0, Unpooled.EMPTY_BUFFER) + store.write(255, 0, Unpooled.EMPTY_BUFFER) + + assertFailsWith { + store.write(256, 0, Unpooled.EMPTY_BUFFER) + } + + // write group + assertFailsWith { + store.write(0, -1, Unpooled.EMPTY_BUFFER) + } + + store.write(2, 0, Unpooled.EMPTY_BUFFER) + store.write(2, 1, Unpooled.EMPTY_BUFFER) + } + } + + @Test + fun testUnsupported() { + FlatFileStoreZipWriter(ZipOutputStream(OutputStream.nullOutputStream())).use { store -> + assertFailsWith { + store.exists(0) + } + + assertFailsWith { + store.exists(0, 0) + } + + assertFailsWith { + store.list() + } + + assertFailsWith { + store.list(0) + } + + assertFailsWith { + store.read(0, 0) + } + + assertFailsWith { + store.remove(0) + } + + assertFailsWith { + store.remove(0, 0) + } + } + } + + @Test + fun testWrite() { + Jimfs.newFileSystem(Configuration.forCurrentPlatform()).use { fs -> + val actual = fs.rootDirectories.first().resolve("zip") + Files.createDirectories(actual) + + Files.newOutputStream(actual.resolve("cache.zip")).use { out -> + FlatFileStoreZipWriter(ZipOutputStream(out)).use { store -> + store.create(0) + + copiedBuffer("OpenRS2").use { buf -> + store.write(2, 0, buf) + } + + copiedBuffer("OpenRS2".repeat(100)).use { buf -> + store.write(2, 65535, buf) + } + + copiedBuffer("OpenRS2".repeat(100)).use { buf -> + store.write(2, 65536, buf) + } + } + } + + assertTrue(ROOT.recursiveEquals(actual)) + } + } + + private companion object { + private val ROOT = Path.of( + FlatFileStoreZipWriterTest::class.java.getResource("flat-file-store-zip").toURI() + ) + } +} diff --git a/cache/src/test/resources/org/openrs2/cache/zip/cache.zip b/cache/src/test/resources/org/openrs2/cache/disk-store-zip/cache.zip similarity index 100% rename from cache/src/test/resources/org/openrs2/cache/zip/cache.zip rename to cache/src/test/resources/org/openrs2/cache/disk-store-zip/cache.zip diff --git a/cache/src/test/resources/org/openrs2/cache/flat-file-store-zip/cache.zip b/cache/src/test/resources/org/openrs2/cache/flat-file-store-zip/cache.zip new file mode 100644 index 00000000..3655fd74 Binary files /dev/null and b/cache/src/test/resources/org/openrs2/cache/flat-file-store-zip/cache.zip differ