diff --git a/build.gradle.kts b/build.gradle.kts index b1a892d..8336f2f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,10 +5,18 @@ plugins { group = "net.woggioni" version = "0.1" -repositories { - mavenLocal() - jcenter() - mavenCentral() +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +allprojects { + + repositories { + mavenLocal() + jcenter() + mavenCentral() + } } fun property(name : String) = project.property(name).toString() diff --git a/cli/build.gradle.kts b/cli/build.gradle.kts new file mode 100644 index 0000000..5ece4b8 --- /dev/null +++ b/cli/build.gradle.kts @@ -0,0 +1,49 @@ +plugins { + id("org.jetbrains.kotlin.jvm") version "1.3.70" + application +} + +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + + +group = "net.woggioni" +version = "0.1" + +fun property(name : String) = project.property(name).toString() + +dependencies { + // Align versions of all Kotlin components + implementation(platform("org.jetbrains.kotlin:kotlin-bom")) + + // Use the Kotlin JDK 8 standard library. + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + + implementation("org.apache.logging.log4j", "log4j-slf4j-impl", property("log4j.version")) + implementation("org.slf4j", "slf4j-api", property("slf4j.version")) + implementation("com.beust", "jcommander", property("jcommander.version")) + implementation(project(":")) + + testRuntimeOnly("org.junit.jupiter", "junit-jupiter-engine", property("junit.version")) + testImplementation("org.junit.jupiter","junit-jupiter-api", property("junit.version")) + testImplementation("org.junit.jupiter","junit-jupiter-params", property("junit.version")) +} + +application { + mainClassName = "net.woggioni.jzstd.Cli" +} + + +tasks.withType { + useJUnitPlatform { + } +} + + +tasks.withType().configureEach { + kotlinOptions { + languageVersion = "1.3" + apiVersion = "1.3" + jvmTarget = "1.8" + javaParameters = true // Useful for reflection. + } +} \ No newline at end of file diff --git a/cli/src/main/kotlin/net/woggioni/jzstd/Cli.kt b/cli/src/main/kotlin/net/woggioni/jzstd/Cli.kt new file mode 100644 index 0000000..3778971 --- /dev/null +++ b/cli/src/main/kotlin/net/woggioni/jzstd/Cli.kt @@ -0,0 +1,126 @@ +package net.woggioni.jzstd + +import com.beust.jcommander.JCommander +import com.beust.jcommander.Parameter +import org.slf4j.LoggerFactory +import java.io.BufferedInputStream +import java.io.BufferedOutputStream +import java.io.InputStream +import java.io.OutputStream +import java.nio.file.Files +import java.nio.file.Paths + +object Cli { + + private data class Params( + @Parameter + var parameters: List = ArrayList(), + + @Parameter(names = arrayOf("-c", "--stdout"), description = "Output to stdout") + var console: Boolean = false, + + @Parameter(names = arrayOf("-d", "--decompress"), description = "Decompress input") + var decompress: Boolean = false, + + @Parameter(names = arrayOf("-z", "--compress"), description = "Compress input") + var compress: Boolean = false, + + @Parameter(names = arrayOf("-k", "--keep"), description = "Keep input file") + var keep: Boolean = false, + + @Parameter(names = arrayOf("-f", "--overwrite"), description = "Overwrite destination") + var overwrite: Boolean = false, + + @Parameter(names = arrayOf("-l", "--level"), description = "Set compression level") + var level: Int = 3, + + @Parameter(names = arrayOf("-i", "--input"), description = "Set input file, defaults to stdin otherwise") + var input: String? = null, + + @Parameter(names = arrayOf("-o", "--output"), description = "Set output file") + var output: String? = null, + + @Parameter(names = arrayOf("-h", "--help"), help = true) + var help: Boolean = false + ) + + + val log = LoggerFactory.getLogger(Cli::class.java) + + private fun fileNameWithoutExtension(fileName: String) = fileName.let { + when (val dotIndex = it.lastIndexOf('.')) { + -1 -> it + else -> it.substring(0, dotIndex) + } + } + + @JvmStatic + fun main(argv: Array) { + val params = Params() + val jc = JCommander.newBuilder() + .addObject(params) + .build() + jc.parse(*argv) + if (params.help) { + jc.usage() + return + } + if (!params.compress && !params.decompress) params.compress = true + try { + val input: InputStream = (params.input + ?.let { Paths.get(it) } + ?.let { Files.newInputStream(it) } + ?: System.`in`) + .let { + if (params.decompress) ZstdInputStream.from(it) + else it + } + input.use { inputStream -> + val output: OutputStream = (params.output.let { + when (it) { + null -> if (params.input == null || params.console) { + null + } else { + when { + params.compress && params.decompress -> + throw IllegalArgumentException("Only one between --compress or --decompress must be selected") + params.compress -> { + Paths.get(params.input + ".zst") + } + params.decompress -> { + val inputFile = Paths.get(params.input) + val inputDir = inputFile.parent ?: Paths.get(".") + inputDir.resolve(fileNameWithoutExtension(inputFile.fileName.toString())) + } + else -> { + throw NotImplementedError() + } + } + } + else -> Paths.get(it) + } + }?.also { destination -> + if (!params.overwrite && Files.exists(destination)) { + throw IllegalStateException("Destination file $destination already exists") + } + }?.let { + Files.newOutputStream(it) + } ?: System.out).let { + if (params.compress) ZstdOutputStream.from(it, params.level) + else it + } + output.use { outputStream -> + val buffer = ByteArray(0x10000) + while (true) { + val read = inputStream.read(buffer, 0, buffer.size) + if (read < 0) break + outputStream.write(buffer, 0, read) + } + } + } + if (params.input != null && params.output == null && !params.console && !params.keep) Files.delete(Paths.get(params.input)) + } catch (t: Throwable) { + log.error(t.message, t) + } + } +} \ No newline at end of file diff --git a/cli/src/main/resources/log4j2.xml b/cli/src/main/resources/log4j2.xml new file mode 100644 index 0000000..fe06ee1 --- /dev/null +++ b/cli/src/main/resources/log4j2.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cli/src/test/kotlin/InsaneTest.kt b/cli/src/test/kotlin/InsaneTest.kt new file mode 100644 index 0000000..3d2dab5 --- /dev/null +++ b/cli/src/test/kotlin/InsaneTest.kt @@ -0,0 +1,22 @@ +import net.woggioni.jzstd.ZstdOutputStream +import org.junit.jupiter.api.Test +import java.nio.file.Files +import java.nio.file.Paths + +class InsaneTest { + + @Test + fun test2() { + val inputFile = Paths.get("/tmp/Richard_Dawkins_The_Selfish_Gene.pdf") + Files.newInputStream(inputFile).use { `is` -> + ZstdOutputStream.from(Files.newOutputStream(Paths.get("/tmp/Richard_Dawkins_The_Selfish_Gene.pdf.zst"))).use { os -> + val buffer = ByteArray(0x10000) + while (true) { + val read = `is`.read(buffer) + if (read < 0) break + os.write(buffer, 0, read) + } + } + } + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 6d05386..2c5b9b6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,7 @@ junit.version=5.6.2 lombok.version=1.18.12 jna.version=5.5.0 -kotlin.version=1.3.70 \ No newline at end of file +kotlin.version=1.3.70 +jcommander.version=1.78 +slf4j.version=1.7.30 +log4j.version=2.13.2 \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 67242e0..0dd65d5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1,3 @@ rootProject.name = "jzstd" + +include("cli") diff --git a/src/main/java/net/woggioni/jzstd/ZstdOutputStream.java b/src/main/java/net/woggioni/jzstd/ZstdOutputStream.java index fb9d45f..e29a53e 100644 --- a/src/main/java/net/woggioni/jzstd/ZstdOutputStream.java +++ b/src/main/java/net/woggioni/jzstd/ZstdOutputStream.java @@ -87,7 +87,7 @@ public class ZstdOutputStream extends OutputStream { @Override public void write(byte[] arr, int off, int len) { - var written = 0; + int written = 0; while (written < len - off) { int writeSize = Math.min(len, input.src.capacity() - input.src.position()); input.src.put(arr, off, writeSize); diff --git a/src/test/java/net/woggioni/jzstd/BasicTest.java b/src/test/java/net/woggioni/jzstd/BasicTest.java index e96c186..24b5550 100644 --- a/src/test/java/net/woggioni/jzstd/BasicTest.java +++ b/src/test/java/net/woggioni/jzstd/BasicTest.java @@ -2,6 +2,7 @@ package net.woggioni.jzstd; import lombok.SneakyThrows; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -10,9 +11,9 @@ import org.junit.jupiter.params.provider.ArgumentsSource; import java.io.*; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.security.DigestInputStream; -import java.security.DigestOutputStream; import java.security.MessageDigest; import java.util.stream.Stream; @@ -38,9 +39,9 @@ public class BasicTest { MessageDigest md5 = MessageDigest.getInstance("MD5"); ByteArrayInputStream compressedStream; - try (InputStream is = new BufferedInputStream(testStream)) { + try (InputStream is = new DigestInputStream(new BufferedInputStream(testStream), md5)) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try (OutputStream os = new DigestOutputStream(ZstdOutputStream.from(baos), md5)) { + try (OutputStream os = ZstdOutputStream.from(baos)) { byte[] buffer = new byte[0x1000]; while (true) { int read = is.read(buffer); @@ -54,12 +55,29 @@ public class BasicTest { md5.reset(); try (InputStream is = new DigestInputStream( ZstdInputStream.from(compressedStream), md5)) { - byte [] buffer = new byte[0x10000]; + byte [] buffer = new byte[0x100000]; while(true) { int read = is.read(buffer); if(read < 0) break; } } - Assertions.assertArrayEquals(originalDigest, md5.digest()); + byte[] roundTripDigest = md5.digest(); + Assertions.assertArrayEquals(originalDigest, roundTripDigest); + } + + @SneakyThrows + @Test + public void test2() { + Path inputFile = Paths.get("/tmp/Richard_Dawkins_The_Selfish_Gene.pdf"); + try(InputStream is = Files.newInputStream(inputFile)) { + try(OutputStream os = ZstdOutputStream.from(Files.newOutputStream(Paths.get("/tmp/Richard_Dawkins_The_Selfish_Gene.pdf.zst")))) { + byte[] buffer = new byte[0x10000]; + while(true) { + int read = is.read(buffer); + if(read < 0) break; + os.write(buffer, 0, read); + } + } + } } }