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 ru.redrise.marinesco.library.InpxScanner; | ||||
| import ru.redrise.marinesco.library.RepackZip; | ||||
| 
 | ||||
| @Controller | ||||
| @RequestMapping("/settings") | ||||
|  | @ -18,15 +19,20 @@ public class SettingsController { | |||
|     private ApplicationSettings applicationSettings; | ||||
| 
 | ||||
|     private InpxScanner inpxScanner; | ||||
|     private RepackZip repackZip; | ||||
| 
 | ||||
|     public SettingsController(ApplicationSettings applicationSettings, | ||||
|             InpxScanner inpxScanner) { | ||||
|             InpxScanner inpxScanner, | ||||
|             RepackZip repackZip) { | ||||
|         this.applicationSettings = applicationSettings; | ||||
|         this.inpxScanner = inpxScanner; | ||||
|         this.repackZip = repackZip; | ||||
|     } | ||||
| 
 | ||||
|     @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"; | ||||
|     } | ||||
| 
 | ||||
|  | @ -34,11 +40,11 @@ public class SettingsController { | |||
|     public Boolean setRegistrationSetting() { | ||||
|         return applicationSettings.isRegistrationAllowed(); | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     @ModelAttribute(name = "lastScanErrors") | ||||
|     public String setLastRunErrors(){ | ||||
|     public String setLastRunErrors() { | ||||
|         if (InpxScanner.getLastRunErrors() != "") | ||||
|             return "Last run attempt failed: "+InpxScanner.getLastRunErrors(); | ||||
|             return "Last run attempt failed: " + InpxScanner.getLastRunErrors(); | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|  | @ -60,4 +66,21 @@ public class SettingsController { | |||
| 
 | ||||
|         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 /> | ||||
|                 <a href="/settings/manage_users">Manage users</a> | ||||
|                 <br /> | ||||
|                 <br /> | ||||
|                 <span class="validationError" th:if="${rescanError} != null" th:text="${rescanError}"></span> | ||||
|                 <span class="validationPass" th:if="${rescanOk} != null" th:text="${rescanOk}"></span> | ||||
|                 <br /> | ||||
|                 <a href="/settings/rescan" >Click to rescan library</a> | ||||
|                 <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> | ||||
|             </p> | ||||
|             <a href="/settings/genres">Edit genre descriptions</a> | ||||
|  | @ -33,4 +40,14 @@ | |||
|     <div th:replace="~{fragments/footer :: 'footer'}"></div> | ||||
| </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
	
	 Dmitry Isaenko
						Dmitry Isaenko