From ae52c1cd492fd3aaa5f577406fc788e54debac70 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko Date: Wed, 10 Jan 2024 02:48:47 +0300 Subject: [PATCH] Finishing basic part of INPX file parsing --- .../redrise/marinesco/library/InpEntry.java | 24 ++-- .../marinesco/library/InpxScanner.java | 106 +++++++++++++----- .../marinesco/library/ParseThread.java | 19 ++++ .../settings/SettingsController.java | 23 ++-- src/main/resources/static/styles/styles.css | 4 + src/main/resources/templates/settings.html | 11 +- 6 files changed, 134 insertions(+), 53 deletions(-) create mode 100644 src/main/java/ru/redrise/marinesco/library/ParseThread.java diff --git a/src/main/java/ru/redrise/marinesco/library/InpEntry.java b/src/main/java/ru/redrise/marinesco/library/InpEntry.java index 559c5c6..306d6ba 100644 --- a/src/main/java/ru/redrise/marinesco/library/InpEntry.java +++ b/src/main/java/ru/redrise/marinesco/library/InpEntry.java @@ -6,12 +6,12 @@ import java.util.ArrayList; import java.util.List; import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.ManyToMany; import jakarta.persistence.Transient; +import lombok.AccessLevel; import lombok.Data; +import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import ru.redrise.marinesco.RainbowDump; import ru.redrise.marinesco.data.AuthorRepository; @@ -20,11 +20,13 @@ import ru.redrise.marinesco.data.GenreRepository; @Slf4j @Entity @Data +@NoArgsConstructor(access = AccessLevel.PRIVATE, force = true) public class InpEntry { @Id - @GeneratedValue(strategy = GenerationType.AUTO) - private Long id; - + private Integer id; + private Long libraryId; + private String libraryVersion; + @ManyToMany private List authors; // Surname,name,by-father @ManyToMany @@ -33,12 +35,13 @@ public class InpEntry { private String series; private String serNo; private String fsFileName; // inside zip - private String fileSize; // extracted, in bytes + private Long fileSize; // extracted, in bytes private String libId; // same to filename private String deleted; // is deleted flag private String fileExtension; // - concatenate to fsFileName private LocalDate addedDate; private String container; + @Transient private int position = 0; @@ -48,9 +51,14 @@ public class InpEntry { public InpEntry(byte[] line, String container, AuthorRepository authorRepository, - GenreRepository genreRepository) throws Exception { + GenreRepository genreRepository, + Long libraryId, + String libraryVersion) throws Exception { // AUTHOR;GENRE;TITLE;SERIES;SERNO;FILE;SIZE;LIBID;DEL;EXT;DATE; this.line = line; + this.libraryId = libraryId; + this.libraryVersion = libraryVersion; + this.id = new String(line).hashCode(); this.container = container + ".zip"; this.authors = new ArrayList<>(); this.genres = new ArrayList<>(); @@ -60,7 +68,7 @@ public class InpEntry { this.series = parseNextString(); this.serNo = parseNextString(); this.fsFileName = parseNextString(); - this.fileSize = parseNextString(); + this.fileSize = Long.valueOf(parseNextString()); this.libId = parseNextString(); this.deleted = parseNextString(); this.fileExtension = parseNextString(); diff --git a/src/main/java/ru/redrise/marinesco/library/InpxScanner.java b/src/main/java/ru/redrise/marinesco/library/InpxScanner.java index fe2812f..f2febcf 100644 --- a/src/main/java/ru/redrise/marinesco/library/InpxScanner.java +++ b/src/main/java/ru/redrise/marinesco/library/InpxScanner.java @@ -21,10 +21,14 @@ import ru.redrise.marinesco.data.LibraryMetadataRepository; @Slf4j @Component @ConfigurationProperties(prefix = "marinesco.library") -public class InpxScanner { +public class InpxScanner implements Runnable { + + private static volatile Thread parser; + private static volatile String lastRunErrors; private String filesLocation = ""; + private LibraryMetadata libraryMetadata; private LibraryMetadataRepository libraryMetadataRepository; private AuthorRepository authorRepository; private GenreRepository genreRepository; @@ -36,54 +40,63 @@ public class InpxScanner { LibraryMetadataRepository libraryMetadataRepository) { this.authorRepository = authorRepository; this.genreRepository = genreRepository; + this.inpEntryRepository = inpEntryRepository; this.libraryMetadataRepository = libraryMetadataRepository; } - public void reScan() throws Exception { - LibraryMetadata libraryMetadata = new LibraryMetadata(); - final FileSystemResource libraryLocation = new FileSystemResource(filesLocation); + /* + * @return true if executed, false if already running + */ + public boolean reScan(){ + if (parser == null || !parser.isAlive()) { + parser = new Thread(this); + parser.start(); + return true; + } + return false; + } - File inpxFile = Stream.of(libraryLocation.getFile().listFiles()) - .filter(file -> file.getName().endsWith(".inpx")) - .findFirst() - .get(); + @Override + public void run() { + try { + final FileSystemResource libraryLocation = new FileSystemResource(filesLocation); - log.info("INPX file found as " + inpxFile.getName()); + final File inpxFile = Stream.of(libraryLocation.getFile().listFiles()) + .filter(file -> file.getName().endsWith(".inpx")) + .findFirst() + .get(); - boolean breaker = false; + log.debug("INPX file found as " + inpxFile.getName()); + + getLibraryMetadata(inpxFile); + parseInp(inpxFile); + // Once multiple libraries imlemented, add here 'delete recrodds with old + // version of the library' + // TODO: fix lirary ID changes on every update: add selector on the front + } catch (Exception e) { + log.error("{}", e); + InpxScanner.lastRunErrors = e.getMessage(); + } + } + + private void getLibraryMetadata(File inpxFile) throws Exception { + libraryMetadata = new LibraryMetadata(); try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(inpxFile))) { ZipEntry zipEntry = zipInputStream.getNextEntry(); while (zipEntry != null) { - if (zipEntry.isDirectory()) { - zipEntry = zipInputStream.getNextEntry(); - continue; - } - if (zipEntry.getName().toLowerCase().contains("collection.info")) libraryMetadata.setCollectionInfo(readPlainText(zipInputStream)); else if (zipEntry.getName().toLowerCase().contains("version.info")) libraryMetadata.setVersionInfo(readPlainText(zipInputStream)); - else if (zipEntry.getName().toLowerCase().endsWith(".inp")) { - //* - if (breaker) { - zipEntry = zipInputStream.getNextEntry(); - continue; - } - breaker = true;// - //*/ - byte[] content = inpToByteArray(zipInputStream, zipEntry.getSize()); - parseInpContent(content, zipEntry.getName()); - } - zipEntry = zipInputStream.getNextEntry(); } } - libraryMetadataRepository.save(libraryMetadata); + libraryMetadata = libraryMetadataRepository.save(libraryMetadata); } private String readPlainText(ZipInputStream zipInputStream) throws Exception { @@ -119,6 +132,29 @@ public class InpxScanner { return inpByteBuffer.array(); } + private void parseInp(File inpxFile) throws Exception { + boolean breaker = false; + + try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(inpxFile))) { + ZipEntry zipEntry = zipInputStream.getNextEntry(); + + while (zipEntry != null) { + if (zipEntry.getName().toLowerCase().endsWith(".inp")) { + // * + if (breaker) { + zipEntry = zipInputStream.getNextEntry(); + continue; + } + breaker = true;// + // */ + byte[] content = inpToByteArray(zipInputStream, zipEntry.getSize()); + parseInpContent(content, zipEntry.getName()); + } + zipEntry = zipInputStream.getNextEntry(); + } + } + } + private void parseInpContent(byte[] content, String name) throws Exception { name = name.substring(0, name.lastIndexOf('.')); @@ -129,10 +165,14 @@ public class InpxScanner { byte[] line = new byte[i - lastIndex]; System.arraycopy(content, lastIndex, line, 0, i - lastIndex - 1); - InpEntry book = new InpEntry(line, name, authorRepository, genreRepository); - //inpEntryRepository.save(book); + InpEntry book = new InpEntry(line, + name, + authorRepository, + genreRepository, + libraryMetadata.getId(), + libraryMetadata.getVersion()); - // RainbowDump.hexDumpUTF8(line); + inpEntryRepository.save(book); if (isNextCarriageReturn(i, content)) { i += 2; @@ -154,4 +194,8 @@ public class InpxScanner { public void setFilesLocation(String location) { filesLocation = location; } + + public static String getLastRunErrors() { + return lastRunErrors; + } } diff --git a/src/main/java/ru/redrise/marinesco/library/ParseThread.java b/src/main/java/ru/redrise/marinesco/library/ParseThread.java new file mode 100644 index 0000000..6298861 --- /dev/null +++ b/src/main/java/ru/redrise/marinesco/library/ParseThread.java @@ -0,0 +1,19 @@ +package ru.redrise.marinesco.library; + +import org.springframework.beans.factory.DisposableBean; + +public class ParseThread implements DisposableBean, Runnable{ + private static volatile boolean running; + + @Override + public void run() { + running = true; + // TODO Auto-generated method stub + + } + + @Override + public void destroy(){ + running = false; + } +} diff --git a/src/main/java/ru/redrise/marinesco/settings/SettingsController.java b/src/main/java/ru/redrise/marinesco/settings/SettingsController.java index 02b83f0..1270d4a 100644 --- a/src/main/java/ru/redrise/marinesco/settings/SettingsController.java +++ b/src/main/java/ru/redrise/marinesco/settings/SettingsController.java @@ -9,10 +9,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import org.springframework.web.servlet.view.RedirectView; -import lombok.extern.slf4j.Slf4j; import ru.redrise.marinesco.library.InpxScanner; -@Slf4j @Controller @RequestMapping("/settings") @PreAuthorize("hasRole('ADMIN')") @@ -28,7 +26,7 @@ public class SettingsController { } @GetMapping - public String getPage(@ModelAttribute("rescanError") String error) { + public String getPage(@ModelAttribute("rescanError") String err, @ModelAttribute("rescanOk") String passStatus) { return "settings"; } @@ -36,6 +34,13 @@ public class SettingsController { public Boolean setRegistrationSetting() { return applicationSettings.isRegistrationAllowed(); } + + @ModelAttribute(name = "lastScanErrors") + public String setLastRunErrors(){ + if (InpxScanner.getLastRunErrors() != null) + return "Last run attempt failed: "+InpxScanner.getLastRunErrors(); + return null; + } @GetMapping("/allow_registration/{sw}") public String switchRegistration(@PathVariable("sw") Boolean sw) { @@ -47,13 +52,11 @@ public class SettingsController { @GetMapping("/rescan") public RedirectView rescan(RedirectAttributes redirectAttributes) { final RedirectView redirectView = new RedirectView("/settings", true); - try { - inpxScanner.reScan(); - } catch (Exception e) { - e.printStackTrace(); - redirectAttributes.addAttribute("rescanError", - "Rescan failed. No '.inpx' file at '"+inpxScanner.getFilesLocation()+"'?"); - } + + if (inpxScanner.reScan()) + redirectAttributes.addAttribute("rescanOk", "Rescan started"); + else + redirectAttributes.addAttribute("rescanError", "Rescan is currently in progress"); return redirectView; } diff --git a/src/main/resources/static/styles/styles.css b/src/main/resources/static/styles/styles.css index 3f4a594..0ebad29 100644 --- a/src/main/resources/static/styles/styles.css +++ b/src/main/resources/static/styles/styles.css @@ -29,6 +29,10 @@ body { color: red; } +.validationPass { + color: #00b185; +} + .header-container { text-align: left; position: relative; diff --git a/src/main/resources/templates/settings.html b/src/main/resources/templates/settings.html index 18369db..9b70f40 100644 --- a/src/main/resources/templates/settings.html +++ b/src/main/resources/templates/settings.html @@ -16,11 +16,14 @@ th:text="${'New users registration is now ' + (allowRegistration ? 'enabled. ' : 'disabled. ' )}"> -

- -
+

+ + + +
Click to rescan library +
+