Correct download process

This commit is contained in:
Dmitry Isaenko 2024-01-19 18:15:57 +03:00
parent dd7582b2d3
commit 57de3bea2f
10 changed files with 71 additions and 118 deletions

View file

@ -1,88 +0,0 @@
package ru.redrise.marinesco;
import java.io.File;
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];
}
}
}
}

View file

@ -8,7 +8,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Configuration @Configuration
public class ThreadPoolTaskExecutorSettings { public class ThreadPoolTaskExecutorSettings {
@Bean @Bean
public TaskExecutor configTaskExecutor(){ public ThreadPoolTaskExecutor configTaskExecutor(){
final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(8); executor.setCorePoolSize(8);
executor.setMaxPoolSize(16); executor.setMaxPoolSize(16);

View file

@ -8,16 +8,13 @@ import java.util.Set;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToMany;
import jakarta.persistence.Transient; import jakarta.persistence.Transient;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import ru.redrise.marinesco.RainbowDump; import ru.redrise.marinesco.RainbowDump;
@Slf4j
@Entity @Entity
@Data @Data
@NoArgsConstructor(access = AccessLevel.PRIVATE, force = true) @NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
@ -59,7 +56,7 @@ public class Book {
this.libraryId = libraryId; this.libraryId = libraryId;
this.libraryVersion = libraryVersion; this.libraryVersion = libraryVersion;
this.id = new String(line).hashCode(); this.id = new String(line).hashCode();
this.container = container + ".zip"; this.container = container;
this.authors = new ArrayList<>(); this.authors = new ArrayList<>();
this.genres = new ArrayList<>(); this.genres = new ArrayList<>();
parseAuthors(authorsCollection); parseAuthors(authorsCollection);

View file

@ -0,0 +1,55 @@
package ru.redrise.marinesco.library;
import java.io.File;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
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 org.springframework.web.server.ResponseStatusException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import ru.redrise.marinesco.settings.ApplicationSettings;
@Controller
@RequestMapping("/download")
public class DownloadController {
private final 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 {
try {
File bookFile = new File(filesLocation + File.separator + container + File.separator + file);
if (! bookFile.exists())
throw new Exception("No file found :[");
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
ContentDisposition.attachment()
.filename(file + ".fb2", StandardCharsets.UTF_8) // TODO: fix
.build()
.toString());
try (ServletOutputStream outStream = response.getOutputStream();
FileInputStream inputStream = new FileInputStream(bookFile)) {
IOUtils.copy(inputStream, outStream);
}
} catch (Exception e) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Book not found [" + e.getMessage() + "]");
}
}
}

View file

@ -3,9 +3,6 @@ package ru.redrise.marinesco.library;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -17,7 +14,7 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.FileSystemResource;
import org.springframework.core.task.TaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -31,9 +28,8 @@ import ru.redrise.marinesco.settings.ApplicationSettings;
@Component @Component
public class InpxScanner { public class InpxScanner {
private static volatile String lastRunErrors = ""; private static volatile String lastRunErrors = "";
private static LocalDateTime lastRunTime = LocalDateTime.of(1970, 01, 01, 0, 0, 0);
private TaskExecutor executor; private ThreadPoolTaskExecutor executor;
private LibraryMetadataRepository libraryMetadataRepository; private LibraryMetadataRepository libraryMetadataRepository;
private AuthorRepository authorRepository; private AuthorRepository authorRepository;
private GenreRepository genreRepository; private GenreRepository genreRepository;
@ -41,7 +37,7 @@ public class InpxScanner {
private String filesLocation; private String filesLocation;
public InpxScanner(TaskExecutor executor, public InpxScanner(ThreadPoolTaskExecutor executor,
ApplicationSettings applicationSettings, ApplicationSettings applicationSettings,
AuthorRepository authorRepository, AuthorRepository authorRepository,
GenreRepository genreRepository, GenreRepository genreRepository,
@ -59,15 +55,9 @@ public class InpxScanner {
* @return true if executed, false otherwise * @return true if executed, false otherwise
*/ */
public boolean reScan() { public boolean reScan() {
if (executor.getActiveCount() > 0)
LocalDateTime currentDateTime = LocalDateTime.now();
if (ChronoUnit.MINUTES.between(lastRunTime, currentDateTime) < 5) {
lastRunErrors = "Too frequent requests. Please whait 5 min. Last attmpt: "
+ lastRunTime.format(DateTimeFormatter.ofPattern("DD.MM.YYYY HH:mm:ss"));
return false; return false;
}
lastRunTime = currentDateTime;
lastRunErrors = ""; lastRunErrors = "";
Thread scanThread = new Thread(() -> { Thread scanThread = new Thread(() -> {

View file

@ -1,12 +1,10 @@
package ru.redrise.marinesco.library.web; package ru.redrise.marinesco.library.web;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.util.comparator.Comparators;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;

View file

@ -5,8 +5,8 @@ spring:
driver-class-name: org.h2.Driver driver-class-name: org.h2.Driver
generate-unique-name: false generate-unique-name: false
name: marinesco name: marinesco
url: jdbc:h2:mem:marinesco # url: jdbc:h2:mem:marinesco
# url: jdbc:h2:file:/tmp/h2 # url: jdbc:h2:file:/tmp/h22
username: sa username: sa
password: password:
jpa: jpa:

View file

@ -16,7 +16,8 @@
<div class="container base"> <div class="container base">
<span class="validationError" th:if="${Error} != null" th:text="${Error}"></span> <span class="validationError" th:if="${Error} != null" th:text="${Error}"></span>
<div th:if="${author} != null"> <div th:if="${author} != null">
<div class="title" th:text="${author.authorName}"><br /></div> <div class="title" th:text="${author.authorName}"></div>
<br />
<div th:each="book : ${books}"> <div th:each="book : ${books}">
<div class="wrapper"> <div class="wrapper">
<div class="horizontal_el_left"> <div class="horizontal_el_left">
@ -28,7 +29,7 @@
</em> </em>
</div> </div>
<div class="horizontal_el_right"> <div class="horizontal_el_right">
<a th:href="${'/download/?container=' + book.container + '&file=' + book.fsFileName}" <a th:href="${'/download/?container=' + book.container + '&file=' + book.fsFileName + '.' + book.fileExtension + '.zip'}"
th:text="${'Download ' + book.fileExtension + ' (' + book.fileSizeForHumans + ')'}"></a> th:text="${'Download ' + book.fileExtension + ' (' + book.fileSizeForHumans + ')'}"></a>
</div> </div>
</div> </div>

View file

@ -23,7 +23,7 @@
<br /> <br />
<span th:each="genre : ${book.genres}" th:text="${((genre.humanReadableDescription == null || genre.humanReadableDescription == '') ? genre.genreId : genre.humanReadableDescription) + '&nbsp;&nbsp;'}"></span> <span th:each="genre : ${book.genres}" th:text="${((genre.humanReadableDescription == null || genre.humanReadableDescription == '') ? genre.genreId : genre.humanReadableDescription) + '&nbsp;&nbsp;'}"></span>
<p> <p>
<a th:href="${'/download/?container=' + book.container + '&file=' + book.fsFileName}" <a th:href="${'/download/?container=' + book.container + '&file=' + book.fsFileName + '.' + book.fileExtension + '.zip'}"
th:text="${'Download ' + book.fileExtension + ' (' + book.fileSizeForHumans + ')'}"></a> th:text="${'Download ' + book.fileExtension + ' (' + book.fileSizeForHumans + ')'}"></a>
</p> </p>
<div class="not_important" th:if="${book.serNo} != ''" th:text="${'Series # : ' + book.serNo}"></div> <div class="not_important" th:if="${book.serNo} != ''" th:text="${'Series # : ' + book.serNo}"></div>

View file

@ -27,7 +27,7 @@
<a th:href="${'/author/' + author.id}" th:text="${author.authorName}"></a>, <a th:href="${'/author/' + author.id}" th:text="${author.authorName}"></a>,
</span> </span>
<br /> <br />
<a th:href="${'/download/?container=' + book.container + '&file=' + book.fsFileName}" <a th:href="${'/download/?container=' + book.container + '&file=' + book.fsFileName + '.' + book.fileExtension + '.zip'}"
th:text="Download"> th:text="Download">
</a> </a>
<span th:text="${' (' + book.fileExtension + ' ' + book.fileSizeForHumans + ')'}"></span> <span th:text="${' (' + book.fileExtension + ' ' + book.fileSizeForHumans + ')'}"></span>
@ -51,7 +51,7 @@
<a th:href="${'/author/' + author.id}" th:text="${author.authorName}"></a>, <a th:href="${'/author/' + author.id}" th:text="${author.authorName}"></a>,
</span> </span>
<br /> <br />
<a th:href="${'/download/?container=' + book.container + '&file=' + book.fsFileName}" <a th:href="${'/download/?container=' + book.container + '&file=' + book.fsFileName + '.' + book.fileExtension + '.zip'}"
th:text="Download"> th:text="Download">
</a> </a>
<span th:text="${' (' + book.fileExtension + ' ' + book.fileSizeForHumans + ')'}"></span> <span th:text="${' (' + book.fileExtension + ' ' + book.fileSizeForHumans + ')'}"></span>