Compare commits
	
		
			No commits in common. "1791fd909d6cc3493207f4bb582792c6e877e878" and "ae52c1cd492fd3aaa5f577406fc788e54debac70" have entirely different histories.
		
	
	
		
			1791fd909d
			...
			ae52c1cd49
		
	
		
					 11 changed files with 17 additions and 248 deletions
				
			
		| 
						 | 
					@ -1,31 +0,0 @@
 | 
				
			||||||
package ru.redrise.marinesco;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.springframework.stereotype.Controller;
 | 
					 | 
				
			||||||
import org.springframework.ui.Model;
 | 
					 | 
				
			||||||
import org.springframework.web.bind.annotation.GetMapping;
 | 
					 | 
				
			||||||
import org.springframework.web.bind.annotation.PathVariable;
 | 
					 | 
				
			||||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import ru.redrise.marinesco.data.InpEntryRepository;
 | 
					 | 
				
			||||||
import ru.redrise.marinesco.library.InpEntry;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Controller
 | 
					 | 
				
			||||||
@RequestMapping("/book")
 | 
					 | 
				
			||||||
public class BookController {
 | 
					 | 
				
			||||||
    InpEntryRepository inpEntryRepository;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public BookController(InpEntryRepository inpEntryRepository){
 | 
					 | 
				
			||||||
        this.inpEntryRepository = inpEntryRepository;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @GetMapping("/{bookId}")
 | 
					 | 
				
			||||||
    public String getPage(@PathVariable("bookId") Long bookId, Model model) {
 | 
					 | 
				
			||||||
        InpEntry book = inpEntryRepository.findById(bookId).orElse(null);
 | 
					 | 
				
			||||||
        if (book == null){
 | 
					 | 
				
			||||||
            model.addAttribute("Error", "Not found");
 | 
					 | 
				
			||||||
            return "book";
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        model.addAttribute("book", book);
 | 
					 | 
				
			||||||
        return "book";
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,89 +0,0 @@
 | 
				
			||||||
package ru.redrise.marinesco;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.io.File;
 | 
					 | 
				
			||||||
import java.io.FileOutputStream;
 | 
					 | 
				
			||||||
import java.nio.charset.StandardCharsets;
 | 
					 | 
				
			||||||
import java.util.zip.ZipEntry;
 | 
					 | 
				
			||||||
import java.util.zip.ZipInputStream;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.springframework.core.io.FileSystemResource;
 | 
					 | 
				
			||||||
import org.springframework.http.ContentDisposition;
 | 
					 | 
				
			||||||
import org.springframework.http.HttpHeaders;
 | 
					 | 
				
			||||||
import org.springframework.http.MediaType;
 | 
					 | 
				
			||||||
import org.springframework.stereotype.Controller;
 | 
					 | 
				
			||||||
import org.springframework.web.bind.annotation.GetMapping;
 | 
					 | 
				
			||||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
					 | 
				
			||||||
import org.springframework.web.bind.annotation.RequestParam;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import jakarta.servlet.ServletOutputStream;
 | 
					 | 
				
			||||||
import jakarta.servlet.http.HttpServletResponse;
 | 
					 | 
				
			||||||
import lombok.extern.slf4j.Slf4j;
 | 
					 | 
				
			||||||
import ru.redrise.marinesco.settings.ApplicationSettings;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Slf4j
 | 
					 | 
				
			||||||
@Controller
 | 
					 | 
				
			||||||
@RequestMapping("/download")
 | 
					 | 
				
			||||||
public class DownloadController {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private String filesLocation;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public DownloadController(ApplicationSettings applicationSettings) {
 | 
					 | 
				
			||||||
        this.filesLocation = applicationSettings.getFilesLocation();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @GetMapping(value = "/")
 | 
					 | 
				
			||||||
    public void getMethodName(@RequestParam String container,
 | 
					 | 
				
			||||||
            @RequestParam String file,
 | 
					 | 
				
			||||||
            HttpServletResponse response) throws Exception {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        final FileSystemResource libraryLocation = new FileSystemResource(filesLocation + File.separator + container);
 | 
					 | 
				
			||||||
        try (ZipInputStream zipInputStream = new ZipInputStream(libraryLocation.getInputStream())) {
 | 
					 | 
				
			||||||
            ZipEntry zipEntry = zipInputStream.getNextEntry();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            while (zipEntry != null) {
 | 
					 | 
				
			||||||
                if (zipEntry.getName().contains(file)) {
 | 
					 | 
				
			||||||
                    response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
 | 
					 | 
				
			||||||
                    response.setHeader(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment()
 | 
					 | 
				
			||||||
                            .filename(file+".fb2", StandardCharsets.UTF_8)  //TODO: fix
 | 
					 | 
				
			||||||
                            .build()
 | 
					 | 
				
			||||||
                            .toString());
 | 
					 | 
				
			||||||
                    ServletOutputStream outStream = response.getOutputStream();
 | 
					 | 
				
			||||||
                    sendFile(zipEntry.getSize(), zipInputStream, outStream);
 | 
					 | 
				
			||||||
                    outStream.flush();
 | 
					 | 
				
			||||||
                    outStream.close();
 | 
					 | 
				
			||||||
                    return;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                zipEntry = zipInputStream.getNextEntry();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        throw new Exception("file not found " +
 | 
					 | 
				
			||||||
                filesLocation + File.separator + container + " → " + file);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private void sendFile(long fileSize,
 | 
					 | 
				
			||||||
            ZipInputStream zipInputStream,
 | 
					 | 
				
			||||||
            ServletOutputStream outStream) throws Exception {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        int blockSize = 0x200;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (fileSize < 0x200)
 | 
					 | 
				
			||||||
            blockSize = (int) fileSize;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        byte[] block = new byte[blockSize];
 | 
					 | 
				
			||||||
        long i = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        while (true) {
 | 
					 | 
				
			||||||
            int actuallyRead = zipInputStream.read(block);
 | 
					 | 
				
			||||||
            outStream.write(block, 0, actuallyRead);
 | 
					 | 
				
			||||||
            i += actuallyRead;
 | 
					 | 
				
			||||||
            if ((i + blockSize) > fileSize) {
 | 
					 | 
				
			||||||
                blockSize = (int) (fileSize - i);
 | 
					 | 
				
			||||||
                if (blockSize == 0)
 | 
					 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
                block = new byte[blockSize];
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -2,20 +2,12 @@ package ru.redrise.marinesco;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.springframework.stereotype.Controller;
 | 
					import org.springframework.stereotype.Controller;
 | 
				
			||||||
import org.springframework.web.bind.annotation.GetMapping;
 | 
					import org.springframework.web.bind.annotation.GetMapping;
 | 
				
			||||||
import org.springframework.web.bind.annotation.ModelAttribute;
 | 
					 | 
				
			||||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import lombok.extern.slf4j.Slf4j;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Slf4j
 | 
					 | 
				
			||||||
@Controller
 | 
					@Controller
 | 
				
			||||||
@RequestMapping("/")
 | 
					 | 
				
			||||||
public class RootController {
 | 
					public class RootController {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @GetMapping
 | 
					    @GetMapping("/")
 | 
				
			||||||
    public String getPage(@ModelAttribute("search") String text) {
 | 
					    public String home(){
 | 
				
			||||||
        // TODO: SEARCH PAGE + CONTROLLER
 | 
					 | 
				
			||||||
        log.info(text);
 | 
					 | 
				
			||||||
        return "root";
 | 
					        return "root";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,21 +0,0 @@
 | 
				
			||||||
package ru.redrise.marinesco.library;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.springframework.core.convert.converter.Converter;
 | 
					 | 
				
			||||||
import org.springframework.stereotype.Component;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import ru.redrise.marinesco.data.AuthorRepository;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Component
 | 
					 | 
				
			||||||
public class AthorByIdConverter implements Converter<Long, Author>{
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private AuthorRepository authorRepository;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public AthorByIdConverter(AuthorRepository authorRepository){
 | 
					 | 
				
			||||||
        this.authorRepository = authorRepository;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public Author convert(Long id) {
 | 
					 | 
				
			||||||
        return authorRepository.findById(id).orElse(null);
 | 
					 | 
				
			||||||
    }   
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,21 +0,0 @@
 | 
				
			||||||
package ru.redrise.marinesco.library;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.springframework.core.convert.converter.Converter;
 | 
					 | 
				
			||||||
import org.springframework.stereotype.Component;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import ru.redrise.marinesco.data.GenreRepository;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Component
 | 
					 | 
				
			||||||
public class GenreByIdConverter implements Converter<String, Genre>{
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private GenreRepository genreRepository;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public GenreByIdConverter(GenreRepository genreRepository){
 | 
					 | 
				
			||||||
        this.genreRepository = genreRepository;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public Genre convert(String id) {
 | 
					 | 
				
			||||||
        return genreRepository.findById(id).orElse(null);
 | 
					 | 
				
			||||||
    }   
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -17,29 +17,27 @@ import ru.redrise.marinesco.data.AuthorRepository;
 | 
				
			||||||
import ru.redrise.marinesco.data.GenreRepository;
 | 
					import ru.redrise.marinesco.data.GenreRepository;
 | 
				
			||||||
import ru.redrise.marinesco.data.InpEntryRepository;
 | 
					import ru.redrise.marinesco.data.InpEntryRepository;
 | 
				
			||||||
import ru.redrise.marinesco.data.LibraryMetadataRepository;
 | 
					import ru.redrise.marinesco.data.LibraryMetadataRepository;
 | 
				
			||||||
import ru.redrise.marinesco.settings.ApplicationSettings;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Slf4j
 | 
					@Slf4j
 | 
				
			||||||
@Component
 | 
					@Component
 | 
				
			||||||
 | 
					@ConfigurationProperties(prefix = "marinesco.library")
 | 
				
			||||||
public class InpxScanner implements Runnable {
 | 
					public class InpxScanner implements Runnable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static volatile Thread parser;
 | 
					    private static volatile Thread parser;
 | 
				
			||||||
    private static volatile String lastRunErrors;
 | 
					    private static volatile String lastRunErrors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private String filesLocation = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private LibraryMetadata libraryMetadata;
 | 
					    private LibraryMetadata libraryMetadata;
 | 
				
			||||||
    private LibraryMetadataRepository libraryMetadataRepository;
 | 
					    private LibraryMetadataRepository libraryMetadataRepository;
 | 
				
			||||||
    private AuthorRepository authorRepository;
 | 
					    private AuthorRepository authorRepository;
 | 
				
			||||||
    private GenreRepository genreRepository;
 | 
					    private GenreRepository genreRepository;
 | 
				
			||||||
    private InpEntryRepository inpEntryRepository;
 | 
					    private InpEntryRepository inpEntryRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private String filesLocation;
 | 
					    public InpxScanner(AuthorRepository authorRepository,
 | 
				
			||||||
 | 
					 | 
				
			||||||
    public InpxScanner(ApplicationSettings applicationSettings,
 | 
					 | 
				
			||||||
            AuthorRepository authorRepository,
 | 
					 | 
				
			||||||
            GenreRepository genreRepository,
 | 
					            GenreRepository genreRepository,
 | 
				
			||||||
            InpEntryRepository inpEntryRepository,
 | 
					            InpEntryRepository inpEntryRepository,
 | 
				
			||||||
            LibraryMetadataRepository libraryMetadataRepository) {
 | 
					            LibraryMetadataRepository libraryMetadataRepository) {
 | 
				
			||||||
        this.filesLocation = applicationSettings.getFilesLocation();
 | 
					 | 
				
			||||||
        this.authorRepository = authorRepository;
 | 
					        this.authorRepository = authorRepository;
 | 
				
			||||||
        this.genreRepository = genreRepository;
 | 
					        this.genreRepository = genreRepository;
 | 
				
			||||||
        this.inpEntryRepository = inpEntryRepository;
 | 
					        this.inpEntryRepository = inpEntryRepository;
 | 
				
			||||||
| 
						 | 
					@ -49,7 +47,7 @@ public class InpxScanner implements Runnable {
 | 
				
			||||||
    /*
 | 
					    /*
 | 
				
			||||||
     * @return true if executed, false if already running
 | 
					     * @return true if executed, false if already running
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public boolean reScan() {
 | 
					    public boolean reScan(){
 | 
				
			||||||
        if (parser == null || !parser.isAlive()) {
 | 
					        if (parser == null || !parser.isAlive()) {
 | 
				
			||||||
            parser = new Thread(this);
 | 
					            parser = new Thread(this);
 | 
				
			||||||
            parser.start();
 | 
					            parser.start();
 | 
				
			||||||
| 
						 | 
					@ -135,9 +133,6 @@ public class InpxScanner implements Runnable {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void parseInp(File inpxFile) throws Exception {
 | 
					    private void parseInp(File inpxFile) throws Exception {
 | 
				
			||||||
        log.warn("REMOVE TEMPORARY SOLUTION - BREAKER");
 | 
					 | 
				
			||||||
        log.warn("REMOVE TEMPORARY SOLUTION - BREAKER");
 | 
					 | 
				
			||||||
        log.warn("REMOVE TEMPORARY SOLUTION - BREAKER");
 | 
					 | 
				
			||||||
        boolean breaker = false;
 | 
					        boolean breaker = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(inpxFile))) {
 | 
					        try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(inpxFile))) {
 | 
				
			||||||
| 
						 | 
					@ -192,6 +187,14 @@ public class InpxScanner implements Runnable {
 | 
				
			||||||
        return i + 1 < content.length && (content[i + 1] == '\r');
 | 
					        return i + 1 < content.length && (content[i + 1] == '\r');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public String getFilesLocation() {
 | 
				
			||||||
 | 
					        return filesLocation;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void setFilesLocation(String location) {
 | 
				
			||||||
 | 
					        filesLocation = location;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static String getLastRunErrors() {
 | 
					    public static String getLastRunErrors() {
 | 
				
			||||||
        return lastRunErrors;
 | 
					        return lastRunErrors;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,15 +1,11 @@
 | 
				
			||||||
package ru.redrise.marinesco.settings;
 | 
					package ru.redrise.marinesco.settings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
 | 
					 | 
				
			||||||
import org.springframework.stereotype.Component;
 | 
					import org.springframework.stereotype.Component;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component
 | 
					@Component
 | 
				
			||||||
@ConfigurationProperties(prefix = "marinesco.library")
 | 
					 | 
				
			||||||
public class ApplicationSettings {
 | 
					public class ApplicationSettings {
 | 
				
			||||||
    private static final String ALLOW_REGISTRATION = "allow_registration";
 | 
					    private static final String ALLOW_REGISTRATION = "allow_registration";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private String filesLocation = "";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private KeyValueRepository keyValueRepository;
 | 
					    private KeyValueRepository keyValueRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private boolean registrationAllowed;
 | 
					    private boolean registrationAllowed;
 | 
				
			||||||
| 
						 | 
					@ -38,13 +34,4 @@ public class ApplicationSettings {
 | 
				
			||||||
    public synchronized boolean isRegistrationAllowed() {
 | 
					    public synchronized boolean isRegistrationAllowed() {
 | 
				
			||||||
        return registrationAllowed;
 | 
					        return registrationAllowed;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    public String getFilesLocation() {
 | 
					 | 
				
			||||||
        return filesLocation;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public void setFilesLocation(String location) {
 | 
					 | 
				
			||||||
        filesLocation = location;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,10 +8,6 @@
 | 
				
			||||||
	src: url('/styles/Arimo-VariableFont_wght.ttf');
 | 
						src: url('/styles/Arimo-VariableFont_wght.ttf');
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
html{
 | 
					 | 
				
			||||||
	line-height: 1.5;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
body {
 | 
					body {
 | 
				
			||||||
	font-family: Arimo;
 | 
						font-family: Arimo;
 | 
				
			||||||
	background-color: #212121;
 | 
						background-color: #212121;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,40 +0,0 @@
 | 
				
			||||||
<!DOCTYPE html>
 | 
					 | 
				
			||||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<head>
 | 
					 | 
				
			||||||
    <title>Marinesco</title>
 | 
					 | 
				
			||||||
    <link rel="icon" href="/favicon.svg" type="image/svg+xml">
 | 
					 | 
				
			||||||
    <link rel="alternate icon" href="/favicon.png" type="image/png">
 | 
					 | 
				
			||||||
    <link rel="stylesheet" th:href="@{/styles/styles.css}" />
 | 
					 | 
				
			||||||
</head>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<body>
 | 
					 | 
				
			||||||
    <div class="page">
 | 
					 | 
				
			||||||
        <div th:replace="~{fragments/header :: 'header'}"></div>
 | 
					 | 
				
			||||||
        <div class="container base">
 | 
					 | 
				
			||||||
            <span class="validationError" th:if="${Error} != null" th:text="${Error}"></span>
 | 
					 | 
				
			||||||
            <div th:if="${book} != null">
 | 
					 | 
				
			||||||
                <br /><span th:text="${'Title: ' + book.title}"></span>
 | 
					 | 
				
			||||||
                <br /><span>Authors: </span>
 | 
					 | 
				
			||||||
                <div th:each="author : ${book.authors}">
 | 
					 | 
				
			||||||
                    <span th:text="${'  * ' + author.authorName}"></span>
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
                <br /><span>Genres: </span>
 | 
					 | 
				
			||||||
                <div th:each="genre : ${book.genres}">
 | 
					 | 
				
			||||||
                    <span th:text="${'  * ' + genre.genreId + ' — ' + genre.humanReadableDescription}"></span>
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
                <br /><span th:if="${book.series} != null" th:text="${'Series: ' + book.series}"></span>
 | 
					 | 
				
			||||||
                <br /><span th:if="${book.serNo} != null" th:text="${'Series # : ' + book.serNo}"></span>
 | 
					 | 
				
			||||||
                <br /><span th:text="${'Format: ' + book.fileExtension}"></span>
 | 
					 | 
				
			||||||
                <br /><span th:if="${book.addedDate} != null" th:text="${'Added : ' + book.addedDate}"></span>
 | 
					 | 
				
			||||||
                <br /><span th:text="${'Size: ' + book.fileSize + ' bytes'}"></span>
 | 
					 | 
				
			||||||
                <p>
 | 
					 | 
				
			||||||
                    <a th:href="${'/download/?container=' + book.container + '&file=' + book.fsFileName}" th:text="Download"></a>
 | 
					 | 
				
			||||||
                </p>
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
    <div th:replace="~{fragments/footer :: 'footer'}"></div>
 | 
					 | 
				
			||||||
</body>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</html>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -9,7 +9,7 @@
 | 
				
			||||||
    <div class="container">
 | 
					    <div class="container">
 | 
				
			||||||
      <footer class="inner_footer">
 | 
					      <footer class="inner_footer">
 | 
				
			||||||
        <a class="entry" href="https://redrise.ru">
 | 
					        <a class="entry" href="https://redrise.ru">
 | 
				
			||||||
          <div class="copy_link">© 2023-2024 Dmitry Isaenko</div>
 | 
					          <div class="copy_link">© 2023 Dmitry Isaenko</div>
 | 
				
			||||||
        </a>
 | 
					        </a>
 | 
				
			||||||
      </footer>
 | 
					      </footer>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,14 +16,7 @@
 | 
				
			||||||
            <br /><a href="/profile">/profile</a>
 | 
					            <br /><a href="/profile">/profile</a>
 | 
				
			||||||
            <br /><a href="/manage_users">/manage_users</a>
 | 
					            <br /><a href="/manage_users">/manage_users</a>
 | 
				
			||||||
            <br /><a href="/settings">/settings</a>
 | 
					            <br /><a href="/settings">/settings</a>
 | 
				
			||||||
            <br /><a href="/book/59992766">/book/59992766</a>
 | 
					 | 
				
			||||||
            <br /><a href="/h2">H2</a>
 | 
					            <br /><a href="/h2">H2</a>
 | 
				
			||||||
            <br />
 | 
					 | 
				
			||||||
            <br />
 | 
					 | 
				
			||||||
            <form action='' method='get'>
 | 
					 | 
				
			||||||
                <input type='text' name='search'>
 | 
					 | 
				
			||||||
                <button class="sign" type='submit'>Search</button>
 | 
					 | 
				
			||||||
            </form>
 | 
					 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <div th:replace="~{fragments/footer :: 'footer'}"></div>
 | 
					    <div th:replace="~{fragments/footer :: 'footer'}"></div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue