PandaLib/pandalib-core/src/main/java/fr/pandacube/lib/core/backup/ZipCompressor.java

195 lines
5.2 KiB
Java

package fr.pandacube.lib.core.backup;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiPredicate;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import fr.pandacube.lib.chat.Chat;
import fr.pandacube.lib.util.MemoryUtil;
import fr.pandacube.lib.util.TimeUtil;
/**
* Handles the creation of a zip file that will have the content of a provided folder.
*/
public class ZipCompressor {
private static final int BUFFER_SIZE = 16 * 1024;
private final File srcDir, destFile;
private final int compressionLevel;
private final BiPredicate<File, String> filter;
private final List<Entry> entriesToCompress;
private ZipOutputStream zipOutStream;
private final Object stateLock = new Object();
private final long inputByteSize;
private long startTime;
private long elapsedByte = 0;
private Exception exception = null;
private boolean started = false;
private boolean finished = false;
/**
* Creates a new zip compressor.
* @param s the source directory.
* @param d the destination file.
* @param c the compression level, used in {@link ZipOutputStream#setLevel(int)} .
* @param f a filter that returns true for the files to include in the zip file, false to exclude.
*/
public ZipCompressor(File s, File d, int c, BiPredicate<File, String> f) {
srcDir = s;
destFile = d;
compressionLevel = c;
filter = f;
entriesToCompress = new ArrayList<>();
inputByteSize = addEntry("");
}
/**
* Returns a displayable representation of the running compression.
* @return a displayable representation of the running compression.
*/
public Chat getState() {
synchronized (stateLock) {
if (!started) {
return Chat.text("Démarrage...");
}
else if (!finished && exception == null) {
float progress = getProgress();
long elapsedTime = System.nanoTime() - startTime;
long remainingTime = (long)(elapsedTime / progress) - elapsedTime;
return Chat.chat()
.infoColor()
.thenData(Math.round(progress*100*10)/10 + "% ")
.thenText("(")
.thenData(MemoryUtil.humanReadableSize(elapsedByte) + "/" + MemoryUtil.humanReadableSize(inputByteSize))
.thenText(") - Temps restant estimé : ")
.thenData(TimeUtil.durationToString(remainingTime / 1000000));
}
else if (exception != null) {
return Chat.failureText("Erreur lors de l'archivage (voir console pour les détails)");
}
else { // finished
return Chat.successText("Terminé !");
}
}
}
/**
* Gets the progress of the running compression.
* @return the progress of the running compression 0 when it starts and 1 when it finishes.
*/
public float getProgress() {
if (!started)
return 0;
if (finished)
return 1;
return elapsedByte / (float) inputByteSize;
}
/**
* Run the compression on the current thread, and returns after the end of the compression.
* Should be run asynchronously (not on Server Thread).
* @throws Exception if an error occurs during compression.
*/
public void compress() throws Exception {
destFile.getParentFile().mkdirs();
try(ZipOutputStream zipStream = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(destFile), BUFFER_SIZE))) {
zipOutStream = zipStream;
zipOutStream.setLevel(compressionLevel);
synchronized (stateLock) {
startTime = System.nanoTime();
started = true;
}
for (Entry entry : entriesToCompress) {
entry.zip();
}
synchronized (stateLock) {
finished = true;
}
} catch (Exception e) {
synchronized (stateLock) {
exception = e;
}
throw e;
}
}
private long addEntry(String currentEntry) {
final File currentFile = new File(srcDir, currentEntry);
if (!currentFile.exists())
return 0;
if (currentFile.isDirectory()) {
if (!currentEntry.isEmpty()) { // it's not the zip root directory
currentEntry += "/";
entriesToCompress.add(new Entry(currentFile, currentEntry));
}
long sum = 0;
for (String child : currentFile.list()) {
String childEntry = currentEntry + child;
if (filter.test(new File(currentFile, child), childEntry))
sum += addEntry(childEntry);
}
return sum;
}
else { // is a file
entriesToCompress.add(new Entry(currentFile, currentEntry));
return currentFile.length();
}
}
private class Entry {
final File file;
final String entry;
Entry(File f, String e) {
file = f;
entry = e;
}
void zip() throws IOException {
ZipEntry zipEntry = new ZipEntry(entry);
BasicFileAttributes attributes = Files.readAttributes(file.toPath(), BasicFileAttributes.class);
if (attributes.isDirectory()) {
zipOutStream.putNextEntry(zipEntry);
zipOutStream.closeEntry();
}
else {
zipEntry.setTime(attributes.lastModifiedTime().toMillis());
zipOutStream.putNextEntry(zipEntry);
try {
Files.copy(file.toPath(), zipOutStream);
}
finally {
zipOutStream.closeEntry();
}
synchronized (stateLock) {
elapsedByte += attributes.size();
}
}
}
}
}