Finishing basic part of INPX file parsing

This commit is contained in:
Dmitry Isaenko 2024-01-10 02:48:47 +03:00
parent 5a79d038eb
commit ae52c1cd49
6 changed files with 134 additions and 53 deletions

View file

@ -6,12 +6,12 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToMany;
import jakarta.persistence.Transient; import jakarta.persistence.Transient;
import lombok.AccessLevel;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import ru.redrise.marinesco.RainbowDump; import ru.redrise.marinesco.RainbowDump;
import ru.redrise.marinesco.data.AuthorRepository; import ru.redrise.marinesco.data.AuthorRepository;
@ -20,10 +20,12 @@ import ru.redrise.marinesco.data.GenreRepository;
@Slf4j @Slf4j
@Entity @Entity
@Data @Data
@NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
public class InpEntry { public class InpEntry {
@Id @Id
@GeneratedValue(strategy = GenerationType.AUTO) private Integer id;
private Long id; private Long libraryId;
private String libraryVersion;
@ManyToMany @ManyToMany
private List<Author> authors; // Surname,name,by-father private List<Author> authors; // Surname,name,by-father
@ -33,13 +35,14 @@ public class InpEntry {
private String series; private String series;
private String serNo; private String serNo;
private String fsFileName; // inside zip 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 libId; // same to filename
private String deleted; // is deleted flag private String deleted; // is deleted flag
private String fileExtension; // - concatenate to fsFileName private String fileExtension; // - concatenate to fsFileName
private LocalDate addedDate; private LocalDate addedDate;
private String container; private String container;
@Transient @Transient
private int position = 0; private int position = 0;
@Transient @Transient
@ -48,9 +51,14 @@ public class InpEntry {
public InpEntry(byte[] line, public InpEntry(byte[] line,
String container, String container,
AuthorRepository authorRepository, 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; // AUTHOR;GENRE;TITLE;SERIES;SERNO;FILE;SIZE;LIBID;DEL;EXT;DATE;
this.line = line; this.line = line;
this.libraryId = libraryId;
this.libraryVersion = libraryVersion;
this.id = new String(line).hashCode();
this.container = container + ".zip"; this.container = container + ".zip";
this.authors = new ArrayList<>(); this.authors = new ArrayList<>();
this.genres = new ArrayList<>(); this.genres = new ArrayList<>();
@ -60,7 +68,7 @@ public class InpEntry {
this.series = parseNextString(); this.series = parseNextString();
this.serNo = parseNextString(); this.serNo = parseNextString();
this.fsFileName = parseNextString(); this.fsFileName = parseNextString();
this.fileSize = parseNextString(); this.fileSize = Long.valueOf(parseNextString());
this.libId = parseNextString(); this.libId = parseNextString();
this.deleted = parseNextString(); this.deleted = parseNextString();
this.fileExtension = parseNextString(); this.fileExtension = parseNextString();

View file

@ -21,10 +21,14 @@ import ru.redrise.marinesco.data.LibraryMetadataRepository;
@Slf4j @Slf4j
@Component @Component
@ConfigurationProperties(prefix = "marinesco.library") @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 String filesLocation = "";
private LibraryMetadata libraryMetadata;
private LibraryMetadataRepository libraryMetadataRepository; private LibraryMetadataRepository libraryMetadataRepository;
private AuthorRepository authorRepository; private AuthorRepository authorRepository;
private GenreRepository genreRepository; private GenreRepository genreRepository;
@ -36,54 +40,63 @@ public class InpxScanner {
LibraryMetadataRepository libraryMetadataRepository) { LibraryMetadataRepository libraryMetadataRepository) {
this.authorRepository = authorRepository; this.authorRepository = authorRepository;
this.genreRepository = genreRepository; this.genreRepository = genreRepository;
this.inpEntryRepository = inpEntryRepository;
this.libraryMetadataRepository = libraryMetadataRepository; this.libraryMetadataRepository = libraryMetadataRepository;
} }
public void reScan() throws Exception { /*
LibraryMetadata libraryMetadata = new LibraryMetadata(); * @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;
}
@Override
public void run() {
try {
final FileSystemResource libraryLocation = new FileSystemResource(filesLocation); final FileSystemResource libraryLocation = new FileSystemResource(filesLocation);
File inpxFile = Stream.of(libraryLocation.getFile().listFiles()) final File inpxFile = Stream.of(libraryLocation.getFile().listFiles())
.filter(file -> file.getName().endsWith(".inpx")) .filter(file -> file.getName().endsWith(".inpx"))
.findFirst() .findFirst()
.get(); .get();
log.info("INPX file found as " + inpxFile.getName()); log.debug("INPX file found as " + inpxFile.getName());
boolean breaker = false; 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))) { try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(inpxFile))) {
ZipEntry zipEntry = zipInputStream.getNextEntry(); ZipEntry zipEntry = zipInputStream.getNextEntry();
while (zipEntry != null) { while (zipEntry != null) {
if (zipEntry.isDirectory()) {
zipEntry = zipInputStream.getNextEntry();
continue;
}
if (zipEntry.getName().toLowerCase().contains("collection.info")) if (zipEntry.getName().toLowerCase().contains("collection.info"))
libraryMetadata.setCollectionInfo(readPlainText(zipInputStream)); libraryMetadata.setCollectionInfo(readPlainText(zipInputStream));
else if (zipEntry.getName().toLowerCase().contains("version.info")) else if (zipEntry.getName().toLowerCase().contains("version.info"))
libraryMetadata.setVersionInfo(readPlainText(zipInputStream)); 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(); zipEntry = zipInputStream.getNextEntry();
} }
} }
libraryMetadataRepository.save(libraryMetadata); libraryMetadata = libraryMetadataRepository.save(libraryMetadata);
} }
private String readPlainText(ZipInputStream zipInputStream) throws Exception { private String readPlainText(ZipInputStream zipInputStream) throws Exception {
@ -119,6 +132,29 @@ public class InpxScanner {
return inpByteBuffer.array(); 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 { private void parseInpContent(byte[] content, String name) throws Exception {
name = name.substring(0, name.lastIndexOf('.')); name = name.substring(0, name.lastIndexOf('.'));
@ -129,10 +165,14 @@ public class InpxScanner {
byte[] line = new byte[i - lastIndex]; byte[] line = new byte[i - lastIndex];
System.arraycopy(content, lastIndex, line, 0, i - lastIndex - 1); System.arraycopy(content, lastIndex, line, 0, i - lastIndex - 1);
InpEntry book = new InpEntry(line, name, authorRepository, genreRepository); InpEntry book = new InpEntry(line,
//inpEntryRepository.save(book); name,
authorRepository,
genreRepository,
libraryMetadata.getId(),
libraryMetadata.getVersion());
// RainbowDump.hexDumpUTF8(line); inpEntryRepository.save(book);
if (isNextCarriageReturn(i, content)) { if (isNextCarriageReturn(i, content)) {
i += 2; i += 2;
@ -154,4 +194,8 @@ public class InpxScanner {
public void setFilesLocation(String location) { public void setFilesLocation(String location) {
filesLocation = location; filesLocation = location;
} }
public static String getLastRunErrors() {
return lastRunErrors;
}
} }

View file

@ -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;
}
}

View file

@ -9,10 +9,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes; import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.view.RedirectView; import org.springframework.web.servlet.view.RedirectView;
import lombok.extern.slf4j.Slf4j;
import ru.redrise.marinesco.library.InpxScanner; import ru.redrise.marinesco.library.InpxScanner;
@Slf4j
@Controller @Controller
@RequestMapping("/settings") @RequestMapping("/settings")
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
@ -28,7 +26,7 @@ public class SettingsController {
} }
@GetMapping @GetMapping
public String getPage(@ModelAttribute("rescanError") String error) { public String getPage(@ModelAttribute("rescanError") String err, @ModelAttribute("rescanOk") String passStatus) {
return "settings"; return "settings";
} }
@ -37,6 +35,13 @@ public class SettingsController {
return applicationSettings.isRegistrationAllowed(); 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}") @GetMapping("/allow_registration/{sw}")
public String switchRegistration(@PathVariable("sw") Boolean sw) { public String switchRegistration(@PathVariable("sw") Boolean sw) {
applicationSettings.setAllowRegistraion(sw); applicationSettings.setAllowRegistraion(sw);
@ -47,13 +52,11 @@ public class SettingsController {
@GetMapping("/rescan") @GetMapping("/rescan")
public RedirectView rescan(RedirectAttributes redirectAttributes) { public RedirectView rescan(RedirectAttributes redirectAttributes) {
final RedirectView redirectView = new RedirectView("/settings", true); final RedirectView redirectView = new RedirectView("/settings", true);
try {
inpxScanner.reScan(); if (inpxScanner.reScan())
} catch (Exception e) { redirectAttributes.addAttribute("rescanOk", "Rescan started");
e.printStackTrace(); else
redirectAttributes.addAttribute("rescanError", redirectAttributes.addAttribute("rescanError", "Rescan is currently in progress");
"Rescan failed. No '.inpx' file at '"+inpxScanner.getFilesLocation()+"'?");
}
return redirectView; return redirectView;
} }

View file

@ -29,6 +29,10 @@ body {
color: red; color: red;
} }
.validationPass {
color: #00b185;
}
.header-container { .header-container {
text-align: left; text-align: left;
position: relative; position: relative;

View file

@ -17,10 +17,13 @@
<a th:href="${'/settings/allow_registration/' + !allowRegistration }" <a th:href="${'/settings/allow_registration/' + !allowRegistration }"
th:text="${'Click here to ' + (allowRegistration ? 'disable' : 'enable' )}"></a> th:text="${'Click here to ' + (allowRegistration ? 'disable' : 'enable' )}"></a>
<p> <p>
<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>
<br /> <br />
<a href="/settings/rescan" >Click to rescan library</a> <a href="/settings/rescan" >Click to rescan library</a>
<br />
<span class="validationError" th:if="${lastScanErrors} != ''" th:text="${lastScanErrors}"></span>
</p> </p>
</div> </div>
</div> </div>