mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 16:20:04 +00:00
feat(download): add file size calculation for DownloadService
This allows the browser to show file size and a time estimate for downloading files. Does not work for ZipOutputStreams though since dir size doesn't match the zip file size. Also added no-cache headers so browser won't start caching downloads.
This commit is contained in:
@@ -5,11 +5,16 @@ import de.grimsi.gameyfin.entities.DetectedGame;
|
|||||||
import de.grimsi.gameyfin.service.DownloadService;
|
import de.grimsi.gameyfin.service.DownloadService;
|
||||||
import de.grimsi.gameyfin.service.GameService;
|
import de.grimsi.gameyfin.service.GameService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.core.io.ByteArrayResource;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -50,10 +55,21 @@ public class GamesController {
|
|||||||
DetectedGame game = gameService.getDetectedGame(slug);
|
DetectedGame game = gameService.getDetectedGame(slug);
|
||||||
|
|
||||||
String downloadFileName = downloadService.getDownloadFileName(game);
|
String downloadFileName = downloadService.getDownloadFileName(game);
|
||||||
|
long downloadFileSize = downloadService.getDownloadFileSize(game);
|
||||||
|
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"%s\"".formatted(downloadFileName));
|
||||||
|
headers.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
|
||||||
|
headers.add(HttpHeaders.PRAGMA, "no-cache");
|
||||||
|
headers.add(HttpHeaders.EXPIRES, "0");
|
||||||
|
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
if (downloadFileSize > 0) {
|
||||||
|
headers.setContentLength(downloadFileSize);
|
||||||
|
}
|
||||||
|
|
||||||
return ResponseEntity
|
return ResponseEntity
|
||||||
.ok()
|
.ok()
|
||||||
.header("Content-Disposition", "attachment; filename=\"%s\"".formatted(downloadFileName))
|
.headers(headers)
|
||||||
.body(out -> downloadService.sendGamefilesToClient(game, out));
|
.body(out -> downloadService.sendGamefilesToClient(game, out));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.catalina.connector.ClientAbortException;
|
import org.apache.catalina.connector.ClientAbortException;
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.StopWatch;
|
import org.springframework.util.StopWatch;
|
||||||
@@ -34,6 +35,23 @@ public class DownloadService {
|
|||||||
return getFilenameWithExtension(path) + ".zip";
|
return getFilenameWithExtension(path) + ".zip";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getDownloadFileSize(DetectedGame game) {
|
||||||
|
Path path = Path.of(game.getPath());
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!path.toFile().isDirectory()) {
|
||||||
|
long fileSize = filesystemService.getSizeOnDisk(path);
|
||||||
|
log.info("Calculated file size for {} ({} MB).", path, Math.divideExact(fileSize, 1000000L));
|
||||||
|
return fileSize;
|
||||||
|
} else {
|
||||||
|
// return zero since we cannot set content length for ZipOutputStreams that are used to archive directories
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new DownloadAbortedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Resource sendImageToClient(String imageId) {
|
public Resource sendImageToClient(String imageId) {
|
||||||
String filename = "%s.png".formatted(imageId);
|
String filename = "%s.png".formatted(imageId);
|
||||||
return filesystemService.getFileFromCache(filename);
|
return filesystemService.getFileFromCache(filename);
|
||||||
@@ -78,6 +96,7 @@ public class DownloadService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void sendGamefilesAsZipToClient(Path path, OutputStream outputStream) {
|
private void sendGamefilesAsZipToClient(Path path, OutputStream outputStream) {
|
||||||
|
log.info("Archiving game path {} for download...", path);
|
||||||
ZipOutputStream zos = new ZipOutputStream(outputStream) {{
|
ZipOutputStream zos = new ZipOutputStream(outputStream) {{
|
||||||
def.setLevel(Deflater.NO_COMPRESSION);
|
def.setLevel(Deflater.NO_COMPRESSION);
|
||||||
}};
|
}};
|
||||||
@@ -87,6 +106,7 @@ public class DownloadService {
|
|||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
||||||
zos.putNextEntry(new ZipEntry(path.relativize(file).toString()));
|
zos.putNextEntry(new ZipEntry(path.relativize(file).toString()));
|
||||||
|
log.debug("Adding file {} to archive...", file);
|
||||||
Files.copy(file, zos);
|
Files.copy(file, zos);
|
||||||
zos.closeEntry();
|
zos.closeEntry();
|
||||||
|
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "1.2.3-SNAPSHOT",
|
"version": "1.2.4-SNAPSHOT",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "1.2.3-SNAPSHOT",
|
"version": "1.2.4-SNAPSHOT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^14.0.0",
|
"@angular/animations": "^14.0.0",
|
||||||
"@angular/cdk": "^14.1.0",
|
"@angular/cdk": "^14.1.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "1.2.3-SNAPSHOT",
|
"version": "1.2.4-SNAPSHOT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"start": "ng serve",
|
"start": "ng serve",
|
||||||
|
|||||||
Reference in New Issue
Block a user