-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d24f628
commit c7b5c80
Showing
5 changed files
with
214 additions
and
161 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
package org.virtuslab.zipops | ||
|
||
import java.io.{ RandomAccessFile, File } | ||
|
||
import net.lingala.zip4j.core.{ HeaderReader, HeaderWriter } | ||
import java.nio.channels.{ FileChannel, Channels } | ||
import java.nio.file.{ Files, Path } | ||
import java.util.ArrayList | ||
|
||
import scala.collection.JavaConverters._ | ||
import net.lingala.zip4j.model.{ ZipModel, FileHeader } | ||
|
||
object Zip4jZipOps extends ZipOps { | ||
|
||
override def removeEntries(jarFile: File, classes: Iterable[String]): Unit = { | ||
removeEntries(jarFile.toPath, classes.toSet) | ||
} | ||
|
||
override def mergeArchives(into: File, from: File): Unit = { | ||
mergeArchives(into.toPath, from.toPath) | ||
} | ||
|
||
private type CentralDirectory = ZipModel | ||
|
||
private def removeEntries(path: Path, toRemove: Set[String]): Unit = { | ||
val centralDir = readCentralDir(path) | ||
removeEntriesFromCentralDir(centralDir, toRemove) | ||
val file = openFile(path) | ||
truncateCentralDir(centralDir, file) | ||
val writeOffset = startOfCentralDir(centralDir) | ||
finalizeZip(centralDir, file, writeOffset) | ||
} | ||
|
||
private def removeEntriesFromCentralDir(centralDir: CentralDirectory, toRemove: Set[String]): Unit = { | ||
val headers = getHeaders(centralDir) | ||
val sanitizedToRemove = toRemove.map(_.stripPrefix("/")) | ||
val clearedHeaders = headers.filterNot(header => sanitizedToRemove.contains(header.getFileName)) | ||
centralDir.getCentralDirectory.setFileHeaders(asArrayList(clearedHeaders)) | ||
} | ||
|
||
private def mergeArchives(target: Path, source: Path): Unit = { | ||
val targetCentralDir = readCentralDir(target) | ||
val sourceCentralDir = readCentralDir(source) | ||
|
||
val targetFile = openFile(target) | ||
val sourceFile = openFile(source) | ||
|
||
truncateCentralDir(targetCentralDir, targetFile) | ||
|
||
// "source" starts where "target" ends | ||
val sourceStart = targetFile.size() | ||
// "source" is as long as from its beginning till the start of central dir | ||
val sourceLength = startOfCentralDir(sourceCentralDir) | ||
|
||
transferAll(from = sourceFile, | ||
to = targetFile, | ||
startPos = sourceStart, | ||
bytesToTransfer = sourceLength) | ||
sourceFile.close() | ||
|
||
val mergedHeaders = mergeHeaders(targetCentralDir, sourceCentralDir, sourceStart) | ||
targetCentralDir.getCentralDirectory.setFileHeaders(asArrayList(mergedHeaders)) | ||
|
||
val centralDirStart = sourceStart + sourceLength | ||
targetCentralDir.getEndCentralDirRecord.setOffsetOfStartOfCentralDir(centralDirStart) | ||
|
||
finalizeZip(targetCentralDir, targetFile, centralDirStart) | ||
|
||
Files.delete(source) | ||
} | ||
|
||
private def mergeHeaders( | ||
targetModel: CentralDirectory, | ||
sourceModel: CentralDirectory, | ||
sourceStart: Long | ||
): Seq[FileHeader] = { | ||
val sourceHeaders = getHeaders(sourceModel) | ||
sourceHeaders.foreach { header => | ||
// potentially offsets should be updated for each header | ||
// not only in central directory but a valid zip tool | ||
// should not rely on that unless the file is corrupted | ||
val currentOffset = header.getOffsetLocalHeader | ||
val newOffset = currentOffset + sourceStart | ||
header.setOffsetLocalHeader(newOffset) | ||
} | ||
|
||
// override files from target with files from source | ||
val sourceNames = sourceHeaders.map(_.getFileName).toSet | ||
val targetHeaders = getHeaders(targetModel).filterNot(h => sourceNames.contains(h.getFileName)) | ||
|
||
targetHeaders ++ sourceHeaders | ||
} | ||
|
||
private def readCentralDir(path: Path): CentralDirectory = { | ||
val file = new RandomAccessFile(path.toFile, "rw") | ||
val headerReader = new HeaderReader(file) | ||
val centralDir = headerReader.readAllHeaders() | ||
file.close() | ||
centralDir | ||
} | ||
|
||
private def startOfCentralDir(centralDir: CentralDirectory) = { | ||
centralDir.getEndCentralDirRecord.getOffsetOfStartOfCentralDir | ||
} | ||
|
||
private def getHeaders(centralDir: CentralDirectory): Seq[FileHeader] = { | ||
centralDir.getCentralDirectory.getFileHeaders.asInstanceOf[ArrayList[FileHeader]].asScala | ||
} | ||
|
||
private def truncateCentralDir(centralDir: CentralDirectory, channel: FileChannel): FileChannel = { | ||
channel.truncate(startOfCentralDir(centralDir)) | ||
} | ||
|
||
private def finalizeZip( | ||
centralDir: CentralDirectory, | ||
channel: FileChannel, | ||
centralDirStart: Long | ||
): Unit = { | ||
val outputStream = Channels.newOutputStream(channel.position(centralDirStart)) | ||
val headerWriter = new HeaderWriter | ||
headerWriter.finalizeZipFile(centralDir, outputStream) | ||
channel.close() | ||
} | ||
|
||
private def openFile(path: Path) = { | ||
new RandomAccessFile(path.toFile, "rw").getChannel | ||
} | ||
|
||
private def transferAll( | ||
from: FileChannel, | ||
to: FileChannel, | ||
startPos: Long, | ||
bytesToTransfer: Long | ||
): Unit = { | ||
var remaining = bytesToTransfer | ||
var offset = startPos | ||
while (remaining > 0) { | ||
val transferred = to.transferFrom(from, /*position =*/ offset, /*count = */ remaining) | ||
offset += transferred | ||
remaining -= transferred | ||
} | ||
} | ||
|
||
private def asArrayList[A](clearedHeaders: Seq[A]): ArrayList[A] = { | ||
new ArrayList[A](clearedHeaders.asJava) | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package org.virtuslab.zipops | ||
|
||
import java.net.URI | ||
import java.io.File | ||
import java.nio.file._ | ||
import java.util.function.Consumer | ||
|
||
object ZipFsZipOps extends ZipOps { | ||
|
||
def removeEntries(jarFile: File, classes: Iterable[String]): Unit = { | ||
withZipFs(jarFile) { fs => | ||
classes.foreach { cls => | ||
Files.deleteIfExists(fs.getPath(cls)) | ||
} | ||
} | ||
} | ||
|
||
def mergeArchives(into: File, from: File): Unit = { | ||
withZipFs(into) { intoFs => | ||
withZipFs(from) { fromFs => | ||
Files | ||
.walk(fromFs.getPath("/")) | ||
.forEachOrdered(new Consumer[Path] { | ||
override def accept(t: Path): Unit = { | ||
if (Files.isDirectory(t)) { | ||
Files.createDirectories(intoFs.getPath(t.toString)) | ||
} else { | ||
Files.copy(t, | ||
intoFs.getPath(t.toString), | ||
StandardCopyOption.COPY_ATTRIBUTES, | ||
StandardCopyOption.REPLACE_EXISTING) | ||
} | ||
} | ||
}) | ||
} | ||
} | ||
from.delete() | ||
} | ||
|
||
private def withZipFs[A](file: File, create: Boolean = false)(action: FileSystem => A): A = { | ||
withZipFs(fileToJarUri(file), create)(action) | ||
} | ||
|
||
private def fileToJarUri(jarFile: File): URI = { | ||
new URI("jar:" + jarFile.toURI.toString) | ||
} | ||
|
||
private def withZipFs[A](uri: URI, create: Boolean)(action: FileSystem => A): A = { | ||
val env = new java.util.HashMap[String, String] | ||
if (create) env.put("create", "true") | ||
val fs = FileSystems.newFileSystem(uri, env) | ||
try action(fs) | ||
finally { | ||
fs.close() | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package org.virtuslab.zipops | ||
|
||
import java.io.File | ||
|
||
trait ZipOps { | ||
def removeEntries(jarFile: File, classes: Iterable[String]): Unit | ||
def mergeArchives(into: File, from: File): Unit | ||
} |