diff --git a/src/main/java/ru/redrise/marinesco/RainbowDump.java b/src/main/java/ru/redrise/marinesco/RainbowDump.java new file mode 100644 index 0000000..37d9d97 --- /dev/null +++ b/src/main/java/ru/redrise/marinesco/RainbowDump.java @@ -0,0 +1,139 @@ +package ru.redrise.marinesco; + +import java.nio.charset.StandardCharsets; + +import lombok.extern.slf4j.Slf4j; + +/* ANSI_BLACK = "\u001B[30m"; + ANSI_YELLOW = "\u001B[33m"; + ANSI_PURPLE = "\u001B[35m"; + ANSI_CYAN = "\u001B[36m"; + ANSI_WHITE = "\u001B[37m"; */ +/** + * Debug tool like hexdump <3 + */ +@Slf4j +public class RainbowDump { + + private static final String ANSI_RESET = "\u001B[0m"; + private static final String ANSI_RED = "\u001B[31m"; + private static final String ANSI_GREEN = "\u001B[32m"; + private static final String ANSI_BLUE = "\u001B[34m"; + + private static StringBuilder stringBuilder; + + public static void hexDumpUTF8(byte[] byteArray) { + stringBuilder = new StringBuilder(" -- RainbowDump --\n"); + if (byteArray == null || byteArray.length == 0) + return; + + int k = 0; + boolean lastCharCyrillic = false; + stringBuilder.append(String.format("%s%08x %s", ANSI_BLUE, 0, ANSI_RESET)); + for (int i = 0; i < byteArray.length; i++) { + if (k == 8) + stringBuilder.append(" "); + if (k == 16) { + stringBuilder.append(ANSI_GREEN + "| " + ANSI_RESET); + lastCharCyrillic = printChars(byteArray, i, lastCharCyrillic); + stringBuilder.append("\n") + .append(String.format("%s%08x %s", ANSI_BLUE, i, ANSI_RESET)); + k = 0; + } + stringBuilder.append(String.format("%02x ", byteArray[i])); + k++; + } + int paddingSize = 16 - (byteArray.length % 16); + if (paddingSize != 16) { + for (int i = 0; i < paddingSize; i++) { + stringBuilder.append(" "); + } + if (paddingSize > 7) { + stringBuilder.append(" "); + } + } + stringBuilder.append(ANSI_GREEN + "| " + ANSI_RESET); + printChars(byteArray, byteArray.length); + stringBuilder.append("\n") + .append(ANSI_RESET) + .append(new String(byteArray, StandardCharsets.UTF_8)) + .append("\n"); + + log.info(stringBuilder.toString()); + } + + private static void printChars(byte[] byteArray, int pointer){ + printChars(byteArray, pointer, false); + } + private static boolean printChars(byte[] byteArray, int pointer, boolean skipFirstByte){ + int j; + if (pointer < 16) + j = 0; + else + j = pointer-16; + + int utf8val = 0; + if (skipFirstByte){ + ++j; + stringBuilder.append(" "); + } + + for (; j < pointer; j++){ + utf8val = 0; + + if (byteArray.length > (j+1)) + utf8val = ((byteArray[j] & 0xff) << 8) | (byteArray[j+1] & 0xff); + + if ((byteArray[j] > 21) && (byteArray[j] < 126)) // man ascii + stringBuilder.append((char) byteArray[j]); + else if (byteArray[j] == 0x0a) + stringBuilder.append("↲"); //"␤" + else if (byteArray[j] == 0x0d) + stringBuilder.append("←"); // "␍" + else if (utf8val >= 0xd080 && utf8val <= 0xd3bf){ + byte[] arr = new byte[0x2]; + System.arraycopy(byteArray, j, arr, 0, 2); + stringBuilder.append(new String(arr, StandardCharsets.UTF_8)+" "); + ++j; + } + else + stringBuilder.append("."); + } + + return (utf8val >= 0xd080 && utf8val <= 0xd3bf && j > pointer); + } + + public static void hexDumpUTF8Legacy(byte[] byteArray) { + StringBuilder stringBuilderLegacy = new StringBuilder("HexDumpUTF8Legacy"); + stringBuilderLegacy.append(ANSI_BLUE); + + if (byteArray == null || byteArray.length == 0) + return; + + for (int i = 0; i < byteArray.length; i++) + stringBuilderLegacy.append(String.format("%02d-", i % 100)); + stringBuilderLegacy.append(">" + ANSI_RED).append(byteArray.length).append(ANSI_RESET).append("\n"); + for (byte b : byteArray) + stringBuilderLegacy.append(String.format("%02x ", b)); + stringBuilderLegacy.append("\n") + .append(new String(byteArray, StandardCharsets.UTF_8)) + .append("\n"); + log.info(stringBuilderLegacy.toString()); + } +/* + public static void binDumpInt(int value) { + log.info(Converter.intToBinaryString(value)); + } + */ + public static void binDumpLong(long value) { + log.info(String.format("%64s", Long.toBinaryString(value)).replace(' ', '0') + " | " + value); + } + + public static String formatDecHexString(long value) { + return String.format("%-20d 0x%x", value, value); + } + + public static String formatDecHexString(int value) { + return String.format("%-20d 0x%x", value, value); + } +} diff --git a/src/main/java/ru/redrise/marinesco/data/AuthorRepository.java b/src/main/java/ru/redrise/marinesco/data/AuthorRepository.java new file mode 100644 index 0000000..f540035 --- /dev/null +++ b/src/main/java/ru/redrise/marinesco/data/AuthorRepository.java @@ -0,0 +1,11 @@ +package ru.redrise.marinesco.data; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import ru.redrise.marinesco.library.Author; + + +@Repository +public interface AuthorRepository extends CrudRepository{ +} diff --git a/src/main/java/ru/redrise/marinesco/data/GenreRepository.java b/src/main/java/ru/redrise/marinesco/data/GenreRepository.java new file mode 100644 index 0000000..27bcc3b --- /dev/null +++ b/src/main/java/ru/redrise/marinesco/data/GenreRepository.java @@ -0,0 +1,11 @@ +package ru.redrise.marinesco.data; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import ru.redrise.marinesco.library.Genre; + + +@Repository +public interface GenreRepository extends CrudRepository{ +} diff --git a/src/main/java/ru/redrise/marinesco/data/InpEntryRepository.java b/src/main/java/ru/redrise/marinesco/data/InpEntryRepository.java new file mode 100644 index 0000000..b54001e --- /dev/null +++ b/src/main/java/ru/redrise/marinesco/data/InpEntryRepository.java @@ -0,0 +1,10 @@ +package ru.redrise.marinesco.data; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import ru.redrise.marinesco.library.InpEntry; + +@Repository +public interface InpEntryRepository extends CrudRepository{ +} \ No newline at end of file diff --git a/src/main/java/ru/redrise/marinesco/data/LibraryMetadataRepository.java b/src/main/java/ru/redrise/marinesco/data/LibraryMetadataRepository.java new file mode 100644 index 0000000..851d00e --- /dev/null +++ b/src/main/java/ru/redrise/marinesco/data/LibraryMetadataRepository.java @@ -0,0 +1,11 @@ +package ru.redrise.marinesco.data; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import ru.redrise.marinesco.library.LibraryMetadata; + +@Repository +public interface LibraryMetadataRepository extends CrudRepository{ + +} diff --git a/src/main/java/ru/redrise/marinesco/library/Author.java b/src/main/java/ru/redrise/marinesco/library/Author.java new file mode 100644 index 0000000..72a5368 --- /dev/null +++ b/src/main/java/ru/redrise/marinesco/library/Author.java @@ -0,0 +1,30 @@ +package ru.redrise.marinesco.library; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Entity +@NoArgsConstructor(access = AccessLevel.PRIVATE, force = true) +@AllArgsConstructor +public class Author { +// private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Column(unique=true) + private String authorName; + + public Author(String name){ + this.authorName = name; + } +} diff --git a/src/main/java/ru/redrise/marinesco/library/Genre.java b/src/main/java/ru/redrise/marinesco/library/Genre.java new file mode 100644 index 0000000..0d4f13d --- /dev/null +++ b/src/main/java/ru/redrise/marinesco/library/Genre.java @@ -0,0 +1,17 @@ +package ru.redrise.marinesco.library; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import lombok.Data; + +@Data +@Entity +public class Genre { + @Id + private String genreId; + private String humanReadableDescription; + + public Genre(String genreId){ + this.genreId = genreId; + } +} diff --git a/src/main/java/ru/redrise/marinesco/library/InpEntry.java b/src/main/java/ru/redrise/marinesco/library/InpEntry.java index 440275d..7fe8d95 100644 --- a/src/main/java/ru/redrise/marinesco/library/InpEntry.java +++ b/src/main/java/ru/redrise/marinesco/library/InpEntry.java @@ -3,31 +3,49 @@ package ru.redrise.marinesco.library; import java.nio.charset.StandardCharsets; import java.time.LocalDate; import java.util.ArrayList; -import java.util.Arrays; 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.Data; import lombok.extern.slf4j.Slf4j; +import ru.redrise.marinesco.RainbowDump; @Slf4j +@Entity @Data public class InpEntry { - private List authors = new ArrayList<>(); // Surname,name,by-father - private List generes = new ArrayList<>(); + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @ManyToMany + private List authors; // Surname,name,by-father + @ManyToMany + private List genres; private String title; private String series; private String serNo; - private String fsFileName; - private String fileSize; // extracted + private String fsFileName; // inside zip + private String 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; - public InpEntry(byte[] line) throws Exception{ + public InpEntry(byte[] line, String container) throws Exception{ // AUTHOR;GENRE;TITLE;SERIES;SERNO;FILE;SIZE;LIBID;DEL;EXT;DATE; + this.container = container; + this.authors = new ArrayList<>(); + this.genres = new ArrayList<>(); parseAuthors(line); parseGenere(line); this.title = parseNextString(line); @@ -40,10 +58,11 @@ public class InpEntry { this.fileExtension = parseNextString(line); this.addedDate = LocalDate.parse(parseNextString(line)); - for (String author : authors) - log.info(author); - for (String gen : generes) - log.info(gen); + /* + for (Author author : authors) + log.info(author.getAuthorName()); + for (Genre gen : genres) + log.info(gen.getGenreId()); log.info(title); log.info(series); @@ -56,10 +75,11 @@ public class InpEntry { log.info(addedDate.toString()); log.info("-----------------"); + //*/ } private void parseAuthors(byte[] line) throws Exception{ for (int i = 0; i < line.length; i++){ - if (line[i] == ':' && i+1 < line.length && line[i+1] == 0x04){ + if (line[i] == 0x04){ splitAuthors(new String(line, 0, i, StandardCharsets.UTF_8)); position = i+1; return; @@ -69,23 +89,27 @@ public class InpEntry { throw new Exception("Invalid 'inp' file format (parse Authors)"); } private void splitAuthors(String allAuthors){ - authors = Arrays.asList(allAuthors.split(":")); + for (String author : allAuthors.split(":")){ + authors.add(new Author(author)); + } } private void parseGenere(byte[] line) throws Exception{ for (int i = position; i < line.length; i++){ - if (line[i] == ':'){ - if (line[i] == ':' && i+1 < line.length && line[i+1] == 0x04){ - generes.add(new String(line, position, i-position, StandardCharsets.UTF_8)); - position = i+2; - return; - } - generes.add(new String(line, position, i-position, StandardCharsets.UTF_8)); + if (line[i] == 0x04){ + splitGenres(new String(line, 0, i, StandardCharsets.UTF_8)); position = i+1; + return; } } + RainbowDump.hexDumpUTF8(line); throw new Exception("Invalid 'inp' file format (parse Genere)"); } + private void splitGenres(String allGenres){ + for (String genre : allGenres.split(":")){ + genres.add(new Genre(genre)); + } + } private String parseNextString(byte[] line) throws Exception{ for (int i = position; i < line.length; i++){ @@ -95,7 +119,7 @@ public class InpEntry { return resultingString; } } - + RainbowDump.hexDumpUTF8(line); throw new Exception("Invalid 'inp' file format (parse Title)"); } } diff --git a/src/main/java/ru/redrise/marinesco/library/InpFile.java b/src/main/java/ru/redrise/marinesco/library/InpFileScanner.java similarity index 73% rename from src/main/java/ru/redrise/marinesco/library/InpFile.java rename to src/main/java/ru/redrise/marinesco/library/InpFileScanner.java index af5415b..36c41be 100644 --- a/src/main/java/ru/redrise/marinesco/library/InpFile.java +++ b/src/main/java/ru/redrise/marinesco/library/InpFileScanner.java @@ -1,22 +1,13 @@ package ru.redrise.marinesco.library; -import java.util.ArrayList; -import java.util.List; - -import lombok.Data; import lombok.extern.slf4j.Slf4j; @Slf4j -//@Entity -@Data -public class InpFile { +public class InpFileScanner { + private String name; - private final String name; - private final List inpEntries; - - public InpFile(byte[] content, String name) throws Exception{ + public InpFileScanner(byte[] content, String name) throws Exception{ this.name = name.substring(0, name.lastIndexOf('.')); - this.inpEntries = new ArrayList<>(); log.info("FILE RELATED "+this.name); parseContent(content); } @@ -27,7 +18,7 @@ public class InpFile { if (content[i] == '\n'){ byte[] line = new byte[i-lastIndex]; System.arraycopy(content, lastIndex, line, 0, i-lastIndex-1); - inpEntries.add(new InpEntry(line)); + new InpEntry(line, name); //RainbowDump.hexDumpUTF8(line); if (isNextCarriageReturn(i, content)){ diff --git a/src/main/java/ru/redrise/marinesco/library/InpxScanner.java b/src/main/java/ru/redrise/marinesco/library/InpxScanner.java index 9703c8f..0b687c2 100644 --- a/src/main/java/ru/redrise/marinesco/library/InpxScanner.java +++ b/src/main/java/ru/redrise/marinesco/library/InpxScanner.java @@ -13,6 +13,10 @@ import org.springframework.core.io.FileSystemResource; import org.springframework.stereotype.Component; import lombok.extern.slf4j.Slf4j; +import ru.redrise.marinesco.data.AuthorRepository; +import ru.redrise.marinesco.data.GenreRepository; +import ru.redrise.marinesco.data.InpEntryRepository; +import ru.redrise.marinesco.data.LibraryMetadataRepository; @Slf4j @Component @@ -21,13 +25,23 @@ public class InpxScanner { private String filesLocation = ""; - private FileSystemResource libraryLocation; + private LibraryMetadataRepository libraryMetadataRepository; + private AuthorRepository authorRepository; + private GenreRepository genreRepository; + private InpEntryRepository inpEntryRepository; - private LibraryMetadata collectionFileMeta; - private LibraryMetadata versionFileMeta; + public InpxScanner(AuthorRepository authorRepository, + GenreRepository genreRepository, + InpEntryRepository inpEntryRepository, + LibraryMetadataRepository libraryMetadataRepository) { + this.authorRepository = authorRepository; + this.genreRepository = genreRepository; + this.libraryMetadataRepository = libraryMetadataRepository; + } public void reScan() throws Exception { - libraryLocation = new FileSystemResource(filesLocation); + LibraryMetadata libraryMetadata = new LibraryMetadata(); + final FileSystemResource libraryLocation = new FileSystemResource(filesLocation); File inpxFile = Stream.of(libraryLocation.getFile().listFiles()) .filter(file -> file.getName().endsWith(".inpx")) @@ -35,44 +49,38 @@ public class InpxScanner { .get(); log.info("INPX file found as " + inpxFile.getName()); - + boolean breaker = false; try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(inpxFile))) { ZipEntry zipEntry = zipInputStream.getNextEntry(); while (zipEntry != null) { - //log.info("Now parsing: " + zipEntry.getName()); if (zipEntry.isDirectory()) { zipEntry = zipInputStream.getNextEntry(); continue; } - /* Lines: - * 1 - Collection Display Name - * 2 - Collection file name - * 3 - num: 0 - fb2, 1 - non-FB2 - * 4 - description - */ - if (zipEntry.getName().toLowerCase().equals("collection.info")) - setMetadata("collection.info", zipInputStream); - // version.info contains only 1 string - if (zipEntry.getName().toLowerCase().equals("version.info")) - setMetadata("version.info", zipInputStream); + if (zipEntry.getName().toLowerCase().contains("collection.info")) + libraryMetadata.setCollectionInfo(readPlainText(zipInputStream)); - if (zipEntry.getName().toLowerCase().endsWith(".inp")){ - if (breaker) - break; - breaker = true; + if (zipEntry.getName().toLowerCase().contains("version.info")) + libraryMetadata.setVersionInfo(readPlainText(zipInputStream)); + + if (zipEntry.getName().toLowerCase().endsWith(".inp")) { + /* + if (breaker) { + zipEntry = zipInputStream.getNextEntry(); + continue; + } + breaker = true;// */ parseInp(zipInputStream, zipEntry.getSize(), zipEntry.getName()); } zipEntry = zipInputStream.getNextEntry(); } } - } - private void setMetadata(String filename, ZipInputStream zipInputStream) throws Exception { - collectionFileMeta = new LibraryMetadata(filename, readPlainText(zipInputStream)); + libraryMetadataRepository.save(libraryMetadata); } private String readPlainText(ZipInputStream zipInputStream) throws Exception { @@ -105,7 +113,9 @@ public class InpxScanner { block = new byte[blockSize]; } } - new InpFile(inpByteBuffer.array(), fileName); + // TODO : FIX! + //inpFileRepository.save(new InpFile(inpByteBuffer.array(), fileName)); + new InpFileScanner(inpByteBuffer.array(), fileName); } public String getFilesLocation() { diff --git a/src/main/java/ru/redrise/marinesco/library/LibraryMetadata.java b/src/main/java/ru/redrise/marinesco/library/LibraryMetadata.java index 78d145d..926b478 100644 --- a/src/main/java/ru/redrise/marinesco/library/LibraryMetadata.java +++ b/src/main/java/ru/redrise/marinesco/library/LibraryMetadata.java @@ -1,15 +1,48 @@ package ru.redrise.marinesco.library; import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; +/* +* collection.info lines: +* 1 - Collection Display Name +* 2 - Collection file name +* 3 - num: 0 - fb2, 1 - non-FB2 +* 4 - description +// version.info contains only 1 string +*/ @Entity @Data @AllArgsConstructor +@NoArgsConstructor public class LibraryMetadata { @Id - private String filename; - private String content; + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String libraryName; + private String libraryFileName; // makes no sense, fuck it either way + private Integer libraryType; // used to be good idea but still nobody bother to fill it right so ignore this shit + private String description; + + private String version; + + public void setVersionInfo(String content) throws Exception { + this.version = content.trim(); + } + + public void setCollectionInfo(String content) throws Exception { + String[] lines = content.split("\n"); + if (lines.length < 4) + throw new Exception("Invalid 'collection.info' file. It contains only "+lines.length+" lines!"); + libraryName = lines[0].trim(); + libraryFileName = lines[1].trim(); + libraryType = Integer.parseInt(lines[2].trim()); + description = lines[3].trim(); + } }