
apparat.utils.Deflate.scala Maven / Gradle / Ivy
/*
* This file is part of Apparat.
*
* Apparat is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Apparat is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Apparat. If not, see .
*
* Copyright (C) 2010 Joa Ebert
* http://www.joa-ebert.com/
*
*/
package apparat.utils
import java.io.{
OutputStream => JOutputStream,
File => JFile,
FileOutputStream => JFileOutputStream,
FileInputStream => JFileInputStream,
IOException => JIOException,
BufferedReader => JBufferedReader,
ByteArrayOutputStream => JByteArrayOutputStream,
InputStreamReader => JInputStreamReader}
import java.util.zip.{Deflater => JDeflater, Adler32 => JAdler32}
import java.lang.{ProcessBuilder => JProcessBuilder}
import apparat.utils.IO._
import apparat.log.SimpleLog
/**
* @author Joa Ebert
*/
object Deflate extends SimpleLog {
private var _7z = System.getProperty("apparat.7z.enabled", "true").toLowerCase == "true"
private val _7zexe = System.getProperty("apparat.7z.path", "7z" + (System getProperty "os.name" indexOf "Windows" match {
case -1 => "a"
case _ => ".exe"
}))
private lazy val adler32 = new JAdler32()
def compress(bytes: Array[Byte], output: JOutputStream) = {
if(_7z) {
compressUsing7z(bytes, output)
} else {
compressUsingDeflater(bytes, output)
}
}
private def compressUsingDeflater(bytes: Array[Byte], output: JOutputStream) = {
val deflater = new JDeflater(JDeflater.BEST_COMPRESSION)
val buffer = new Array[Byte](0x8000)
var numBytesCompressed = 0
deflater setInput bytes
deflater.finish()
do {
numBytesCompressed = deflater deflate buffer
output.write(buffer, 0, numBytesCompressed)
} while (0 != numBytesCompressed)
output.flush()
}
private def compressUsing7z(bytes: Array[Byte], output: JOutputStream) = {
try {
//if(bytes.length < 0x40000) {
// compress7zInSTDIO(bytes, output)
//} else {
compress7zOnDisk(bytes, output)
//}
} catch {
case ioException: JIOException => {
_7z = false
log.warning("7z is not present on PATH. Fallback to normal compression.")
log ifDebug { ioException.getLocalizedMessage+"\n"+ioException.getStackTraceString }
compressUsingDeflater(bytes, output)
}
case other => {
_7z = false
log.warning("7z failed. Fallback to normal compression.")
log ifDebug { other.getLocalizedMessage+"\n"+other.getStackTraceString }
compressUsingDeflater(bytes, output)
}
}
}
private def compress7zOnDisk(bytes: Array[Byte], output: JOutputStream) = {
val gzInput = JFile.createTempFile("apparat", "input")
val gzOutput = JFile.createTempFile("apparat", "output")
gzInput.deleteOnExit
gzOutput.deleteOnExit
using(new JFileOutputStream(gzInput)) { _ write bytes }
log.debug("Invoking \"%s a %s -tgzip -mx9 %s\"", _7zexe, gzOutput.getAbsolutePath, gzInput.getAbsolutePath)
val builder = new JProcessBuilder(_7zexe, "a", gzOutput.getAbsolutePath, "-tgzip", "-mx9", gzInput.getAbsolutePath)
val process = builder.start()
log.debug("Waiting for 7z to finish.")
assert(0 == process.waitFor())
val sevenZipOutput = new JFile(gzOutput.getAbsolutePath + ".gz")
val fileInputStream = new JFileInputStream(sevenZipOutput)
sevenZipOutput.deleteOnExit()
try {
writeGZIP(bytes, output, byteArrayOf(fileInputStream))
} finally {
try { fileInputStream.close() } catch { case _ => {} }
try { gzInput.delete() } catch { case _ => {} }
try { gzOutput.delete() } catch { case _ => {} }
try { sevenZipOutput.delete() } catch { case _ => {} }
}
}
private def compress7zInSTDIO(bytes: Array[Byte], output: JOutputStream) = {
log.debug("Invoking \"%s a apparat -tgzip -mx9 -siswf -so\"", _7zexe)
val builder = new JProcessBuilder(_7zexe, "a", "apparat", "-tgzip", "-mx9", "-siswf", "-so")
val process = builder.start()
val outputStream = process.getOutputStream
val inputStream = process.getInputStream
outputStream write bytes
outputStream.close()
val buffer = new Array[Byte](0x8000)
var bytesTotal = 0
val byteArrayOutputStream = new JByteArrayOutputStream()
log.debug("Waiting for 7z to finish")
while(inputStream.available != 0) {
val bytesRead = inputStream.read(buffer)
bytesTotal += bytesRead
byteArrayOutputStream.write(buffer, 0, bytesRead)
Thread sleep 8// Can we fix this?
}
inputStream.close()
assert(0 == process.waitFor())
writeGZIP(bytes, output, byteArrayOutputStream.toByteArray)
}
private def writeGZIP(bytes: Array[Byte], output: JOutputStream, gzipBuffer: Array[Byte]) = {
// GZIP specification:
// http://www.ietf.org/rfc/rfc1952
assert((gzipBuffer(0) & 0xff) == 0x1f, "GZip header is corrupt.")
assert((gzipBuffer(1) & 0xff) == 0x8b, "GZip header is corrupt.")
assert((gzipBuffer(2) & 0xff) == 0x08, "Deflate stream required.")
val flags = gzipBuffer(3) & 0xff
assert(0 == (flags & (1 << 0)), "FTEXT must not be set.")
// Skip 4b of modification time
// XFL is unimportant (byte 8)
// OS is unimportant (byte 9)
var bufferPos = 10
if(0 != (flags & (1 << 2))) {
bufferPos += (gzipBuffer(10) & 0xff)
}
if(0 != (flags & (1 << 3))) {
while(gzipBuffer(bufferPos) != 0x00) {
bufferPos += 1
}
bufferPos += 1
}
if(0 != (flags & (1 << 4))) {
while(gzipBuffer(bufferPos) != 0x00) {
bufferPos += 1
}
bufferPos += 1
}
if(0 != (flags & (1 << 1))) {
bufferPos += 2
}
adler32.reset()
adler32 update bytes
val checksum = adler32.getValue().asInstanceOf[Int]
//ZLIB Header
output write 0x78
output write 0xda
// Deflate Stream
output.write(gzipBuffer, bufferPos, gzipBuffer.length - bufferPos - 8)
// Adler32 Checksum
output write ((checksum >> 0x18) & 0xff)
output write ((checksum >> 0x10) & 0xff)
output write ((checksum >> 0x08) & 0xff)
output write (checksum & 0xff)
output.flush()
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy