Add zip files repack tool
This commit is contained in:
parent
57de3bea2f
commit
76e0dcd77a
3 changed files with 181 additions and 6 deletions
135
src/main/java/ru/redrise/marinesco/library/RepackZip.java
Normal file
135
src/main/java/ru/redrise/marinesco/library/RepackZip.java
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
package ru.redrise.marinesco.library;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import ru.redrise.marinesco.settings.ApplicationSettings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens zip file with book files in them, creates folder with name of zip file,
|
||||||
|
* extracts every book file and put it into individual zip archive
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class RepackZip {
|
||||||
|
private final ThreadPoolTaskExecutor executor;
|
||||||
|
private final File folderContainer;
|
||||||
|
private String lastTimeExec = "";
|
||||||
|
|
||||||
|
|
||||||
|
public RepackZip(ThreadPoolTaskExecutor executor, ApplicationSettings applicationSettings) {
|
||||||
|
this.executor = executor;
|
||||||
|
this.folderContainer = new File(applicationSettings.getFilesLocation());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean repack() {
|
||||||
|
if (executor.getActiveCount() > 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
lastTimeExec = LocalDateTime.now().format(DateTimeFormatter.ofPattern("DD.MM.YYYY HH:mm:ss"));
|
||||||
|
/*
|
||||||
|
* if (lastTimeExec != null)
|
||||||
|
return true;
|
||||||
|
*/
|
||||||
|
Thread repackThread = new Thread(() -> {
|
||||||
|
List<File> files = Stream.of(folderContainer.listFiles())
|
||||||
|
.filter(file -> file.getName().toLowerCase().endsWith(".zip"))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
for (File file : files)
|
||||||
|
executor.execute(new InnerRepackZip(file));
|
||||||
|
});
|
||||||
|
|
||||||
|
repackThread.start();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InnerRepackZip implements Runnable {
|
||||||
|
private String dirLocation;
|
||||||
|
private File container;
|
||||||
|
|
||||||
|
private InnerRepackZip(File container) {
|
||||||
|
this.container = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(container))) {
|
||||||
|
dirLocation = container.getParentFile().getAbsolutePath() +
|
||||||
|
File.separator +
|
||||||
|
container.getName().replaceAll("(\\.zip)|(\\.ZIP)", "");
|
||||||
|
|
||||||
|
new File(dirLocation).mkdirs();
|
||||||
|
|
||||||
|
if (!new File(dirLocation).exists())
|
||||||
|
throw new Exception("Dir does not created / exists");
|
||||||
|
|
||||||
|
ZipEntry zipEntry;
|
||||||
|
|
||||||
|
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
|
||||||
|
if (!zipEntry.isDirectory())
|
||||||
|
makeZip(zipEntry, zipInputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("{}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeZip(ZipEntry entry, InputStream inputStream) throws Exception {
|
||||||
|
final String newZipFileLocation = dirLocation + File.separator + entry.getName() + ".zip";
|
||||||
|
final File newZipFile = new File(dirLocation);
|
||||||
|
|
||||||
|
if (newZipFile.exists()) {
|
||||||
|
log.info("Skipping unpacked: {}", newZipFileLocation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (ZipOutputStream outputStream = new ZipOutputStream(
|
||||||
|
new FileOutputStream(newZipFile))) {
|
||||||
|
ZipEntry entryToCompress = new ZipEntry(entry.getName());
|
||||||
|
outputStream.putNextEntry(entryToCompress);
|
||||||
|
|
||||||
|
long fileSize = entry.getSize();
|
||||||
|
|
||||||
|
int blockSize = 2048;
|
||||||
|
|
||||||
|
if (fileSize < 2048)
|
||||||
|
blockSize = (int) fileSize;
|
||||||
|
|
||||||
|
byte[] block = new byte[blockSize];
|
||||||
|
long i = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
int actuallyRead = inputStream.read(block);
|
||||||
|
outputStream.write(block, 0, actuallyRead);
|
||||||
|
i += actuallyRead;
|
||||||
|
if ((i + blockSize) > fileSize) {
|
||||||
|
blockSize = (int) (fileSize - i);
|
||||||
|
if (blockSize == 0)
|
||||||
|
break;
|
||||||
|
block = new byte[blockSize];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastExecTime(){
|
||||||
|
return lastTimeExec;
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||||
import org.springframework.web.servlet.view.RedirectView;
|
import org.springframework.web.servlet.view.RedirectView;
|
||||||
|
|
||||||
import ru.redrise.marinesco.library.InpxScanner;
|
import ru.redrise.marinesco.library.InpxScanner;
|
||||||
|
import ru.redrise.marinesco.library.RepackZip;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
@RequestMapping("/settings")
|
@RequestMapping("/settings")
|
||||||
|
@ -18,15 +19,20 @@ public class SettingsController {
|
||||||
private ApplicationSettings applicationSettings;
|
private ApplicationSettings applicationSettings;
|
||||||
|
|
||||||
private InpxScanner inpxScanner;
|
private InpxScanner inpxScanner;
|
||||||
|
private RepackZip repackZip;
|
||||||
|
|
||||||
public SettingsController(ApplicationSettings applicationSettings,
|
public SettingsController(ApplicationSettings applicationSettings,
|
||||||
InpxScanner inpxScanner) {
|
InpxScanner inpxScanner,
|
||||||
|
RepackZip repackZip) {
|
||||||
this.applicationSettings = applicationSettings;
|
this.applicationSettings = applicationSettings;
|
||||||
this.inpxScanner = inpxScanner;
|
this.inpxScanner = inpxScanner;
|
||||||
|
this.repackZip = repackZip;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public String getPage(@ModelAttribute("rescanError") String err, @ModelAttribute("rescanOk") String passStatus) {
|
public String getPage(@ModelAttribute("rescanError") String err,
|
||||||
|
@ModelAttribute("rescanOk") String passStatus,
|
||||||
|
@ModelAttribute("repack") String repackStatus) {
|
||||||
return "settings";
|
return "settings";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,4 +66,21 @@ public class SettingsController {
|
||||||
|
|
||||||
return redirectView;
|
return redirectView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/repack")
|
||||||
|
public RedirectView repack(RedirectAttributes redirectAttributes) {
|
||||||
|
if (repackZip.repack())
|
||||||
|
redirectAttributes.addAttribute("repack", "Repack process started.");
|
||||||
|
else
|
||||||
|
redirectAttributes.addAttribute("repack", "Repack process could be currently in progress");
|
||||||
|
|
||||||
|
return new RedirectView("/settings", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ModelAttribute(name = "repack_lastrun")
|
||||||
|
public String setLastRunRepackErrors() {
|
||||||
|
if (repackZip.getLastExecTime() != "")
|
||||||
|
return "Last time executed: " + repackZip.getLastExecTime();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,11 +20,18 @@
|
||||||
<br />
|
<br />
|
||||||
<a href="/settings/manage_users">Manage users</a>
|
<a href="/settings/manage_users">Manage users</a>
|
||||||
<br />
|
<br />
|
||||||
|
<br />
|
||||||
<span class="validationError" th:if="${rescanError} != null" th:text="${rescanError}"></span>
|
<span class="validationError" th:if="${rescanError} != null" th:text="${rescanError}"></span>
|
||||||
<span class="validationPass" th:if="${rescanOk} != null" th:text="${rescanOk}"></span>
|
<span class="validationPass" th:if="${rescanOk} != null" th:text="${rescanOk}"></span>
|
||||||
<br />
|
<br />
|
||||||
<a href="/settings/rescan" >Click to rescan library</a>
|
<a href="/settings/rescan" >Click to rescan library</a>
|
||||||
<br />
|
<br />
|
||||||
|
<br />
|
||||||
|
<span class="validationPass" th:if="${repack} != null" th:text="${repack}"></span>
|
||||||
|
<span class="validationPass" th:if="${repack_lastrun} != null" th:text="${repack_lastrun}"></span>
|
||||||
|
<br />
|
||||||
|
<a href="/settings/repack" >Click to repack library zip archives</a>
|
||||||
|
<br />
|
||||||
<span class="validationError" th:if="${lastScanErrors} != ''" th:text="${lastScanErrors}"></span>
|
<span class="validationError" th:if="${lastScanErrors} != ''" th:text="${lastScanErrors}"></span>
|
||||||
</p>
|
</p>
|
||||||
<a href="/settings/genres">Edit genre descriptions</a>
|
<a href="/settings/genres">Edit genre descriptions</a>
|
||||||
|
@ -34,3 +41,13 @@
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Marinesco - Application settings</title>
|
||||||
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||||
|
<link rel="alternate icon" href="/favicon.png" type="image/png">
|
||||||
|
<link rel="stylesheet" th:href="@{/styles/styles.css}" />
|
||||||
|
</head>
|
Loading…
Reference in a new issue