Finishing basic part of INPX file parsing
This commit is contained in:
parent
5a79d038eb
commit
ae52c1cd49
6 changed files with 134 additions and 53 deletions
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
19
src/main/java/ru/redrise/marinesco/library/ParseThread.java
Normal file
19
src/main/java/ru/redrise/marinesco/library/ParseThread.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue