Finishing basic part of INPX file parsing

master
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 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<Author> 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();

View File

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

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

View File

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

View File

@ -16,11 +16,14 @@
th:text="${'New users registration is now ' + (allowRegistration ? 'enabled. ' : 'disabled. ' )}"></span>
<a th:href="${'/settings/allow_registration/' + !allowRegistration }"
th:text="${'Click here to ' + (allowRegistration ? 'disable' : 'enable' )}"></a>
<p>
<span class="validationError" th:if="${rescanError} != null"
th:text="${rescanError}"></span>
<br />
<p>
<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 />
<span class="validationError" th:if="${lastScanErrors} != ''" th:text="${lastScanErrors}"></span>
</p>
</div>
</div>