diff --git a/Makefile b/Makefile index 2440f50..dabb2fa 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ executable: - ./mvnw package -Pnative -Dquarkus.native.additional-build-args="--initialize-at-run-time=sun.java2d.Disposer" $(EXECUTABLE_ARGS) + ./mvnw package -Pnative -Dquarkus.native.file-encoding=UTF-8 -Dquarkus.native.additional-build-args="--initialize-at-run-time=sun.java2d.Disposer" $(EXECUTABLE_ARGS) image: docker build -t rhiobet/lalafin$(IMAGE_TAG) . -f src/main/docker/Dockerfile.native diff --git a/nix/default.nix b/nix/default.nix index 912bc57..5c0a848 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -18,7 +18,9 @@ stdenv.mkDerivation rec { ''; postFixup = '' - wrapProgram $out/bin/lalafin --prefix PATH : ${pkgs.imagemagick}/bin + wrapProgram $out/bin/lalafin \ + --prefix PATH : ${pkgs.imagemagick}/bin \ + --prefix LD_LIBRARY_PATH : ${pkgs.stdenv.cc.cc.lib}/lib ''; meta = with lib; { diff --git a/pom.xml b/pom.xml index c25d865..a435496 100644 --- a/pom.xml +++ b/pom.xml @@ -6,17 +6,17 @@ lalafin 1.0-SNAPSHOT - 3.8.1 + 3.12.1 true - 11 - 11 + 17 + 17 UTF-8 UTF-8 - 3.4.1 + 3.30.2 quarkus-bom io.quarkus - 3.4.1 - 2.22.1 + 3.30.2 + 3.2.3 @@ -32,19 +32,19 @@ io.quarkus - quarkus-resteasy-reactive + quarkus-rest io.quarkus - quarkus-resteasy-reactive-qute + quarkus-rest-qute io.quarkus - quarkus-resteasy-reactive-jackson + quarkus-rest-jackson io.quarkus - quarkus-resteasy-reactive-jaxb + quarkus-rest-jaxb io.quarkus @@ -107,7 +107,8 @@ native - native + true + false diff --git a/src/main/java/sh/rhiobet/lalafin/api/FilePublicAPI.java b/src/main/java/sh/rhiobet/lalafin/api/FilePublicAPI.java index 50732f2..d3fae4f 100644 --- a/src/main/java/sh/rhiobet/lalafin/api/FilePublicAPI.java +++ b/src/main/java/sh/rhiobet/lalafin/api/FilePublicAPI.java @@ -1,7 +1,9 @@ package sh.rhiobet.lalafin.api; import java.net.URLDecoder; +import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.List; import jakarta.inject.Inject; import jakarta.ws.rs.GET; @@ -50,11 +52,24 @@ public class FilePublicAPI { String decodedFile = URLDecoder.decode(token.file, StandardCharsets.UTF_8); if (request.remoteAddress().host().toString().equals(token.ip) && System.currentTimeMillis() < token.timestamp + 172800000) { - FileInfoBase fileInfoBase = fileInfoService.getInfo(decodedFile.split("/"), null); - if (fileInfoBase instanceof FileInfo) { - return fileServeService.serveFile((FileInfo) fileInfoBase, range); + String[] decodedFileSplitted = decodedFile.split("/"); + FileInfoBase fileInfoBase; + if (decodedFileSplitted[decodedFileSplitted.length - 2].endsWith(".zip")) { + fileInfoBase = fileInfoService.getInfo( + Arrays.copyOf(decodedFileSplitted, decodedFileSplitted.length - 1), null); + if (fileInfoBase instanceof FileInfo) { + return fileServeService.serveArchiveContent((FileInfo) fileInfoBase, + decodedFileSplitted[decodedFileSplitted.length - 1]); + } else { + return Response.status(Response.Status.NOT_FOUND).build(); + } } else { - return Response.status(Response.Status.NOT_FOUND).build(); + fileInfoBase = fileInfoService.getInfo(decodedFileSplitted, null); + if (fileInfoBase instanceof FileInfo) { + return fileServeService.serveFile((FileInfo) fileInfoBase, range); + } else { + return Response.status(Response.Status.NOT_FOUND).build(); + } } } else { return Response.status(Response.Status.FORBIDDEN).build(); diff --git a/src/main/java/sh/rhiobet/lalafin/api/model/ArchiveInfo.java b/src/main/java/sh/rhiobet/lalafin/api/model/ArchiveInfo.java new file mode 100644 index 0000000..4666cdc --- /dev/null +++ b/src/main/java/sh/rhiobet/lalafin/api/model/ArchiveInfo.java @@ -0,0 +1,30 @@ +package sh.rhiobet.lalafin.api.model; + +import java.util.List; +import java.util.ArrayList; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.quarkus.runtime.annotations.RegisterForReflection; + +@RegisterForReflection +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ArchiveInfo extends FileInfo { + + public List content; + + public ArchiveInfo(String filename, String thumbnailUrl, String directUrl, String viewUrl, + String publicApiUrl) { + super(filename, thumbnailUrl, directUrl, publicApiUrl); + this.content = new ArrayList<>(); + this.viewUrl = viewUrl; + this.type = "archive"; + } + + public ArchiveInfo(String filename, String thumbnailUrl, String directUrl, String viewUrl) { + this(filename, thumbnailUrl, directUrl, viewUrl, ""); + } + + public ArchiveInfo(String filename, String directUrl) { + this(filename, "", directUrl, ""); + } + +} diff --git a/src/main/java/sh/rhiobet/lalafin/api/thumbnail/JellyfinThumbnailGenerator.java b/src/main/java/sh/rhiobet/lalafin/api/thumbnail/JellyfinThumbnailGenerator.java index 069ea29..7472017 100644 --- a/src/main/java/sh/rhiobet/lalafin/api/thumbnail/JellyfinThumbnailGenerator.java +++ b/src/main/java/sh/rhiobet/lalafin/api/thumbnail/JellyfinThumbnailGenerator.java @@ -84,10 +84,14 @@ public class JellyfinThumbnailGenerator implements ThumbnailGenerator { try { Optional imgPath; - Path posterPath = folderPath.resolve("poster.jpg"); - if (Files.exists(posterPath)) { - // Check for series poster - imgPath = Optional.of(posterPath); + Path posterPathJpg = folderPath.resolve("poster.jpg"); + Path posterPathPng = folderPath.resolve("poster.png"); + if (Files.exists(posterPathJpg)) { + // Check for series poster (jpg) + imgPath = Optional.of(posterPathJpg); + } else if (Files.exists(posterPathPng)) { + // Check for series poster (png) + imgPath = Optional.of(posterPathPng); } else if (folderPath.getFileName().toString().startsWith("Season ") || (folderPath.getFileName().toString().equals("Specials"))) { // Season folder @@ -95,10 +99,22 @@ public class JellyfinThumbnailGenerator implements ThumbnailGenerator { if (folderName.startsWith("Season ")) { String seasonNumber = String.format("%02d", Integer.parseInt(folderName.substring(7))); - imgPath = Optional - .of(folderPath.resolveSibling("season" + seasonNumber + "-poster.jpg")); + Path seasonPathJpg = + folderPath.resolveSibling("season" + seasonNumber + "-poster.jpg"); + if (Files.exists(seasonPathJpg)) { + imgPath = Optional.of(seasonPathJpg); + } else { + imgPath = Optional.of(folderPath + .resolveSibling("season" + seasonNumber + "-poster.png")); + } } else { - imgPath = Optional.of(folderPath.resolveSibling("season-specials-poster.jpg")); + Path specialsPathJpg = folderPath.resolveSibling("season-specials-poster.jpg"); + if (Files.exists(specialsPathJpg)) { + imgPath = Optional.of(specialsPathJpg); + } else { + imgPath = Optional + .of(folderPath.resolveSibling("season-specials-poster.png")); + } } } else { imgPath = Optional.empty(); diff --git a/src/main/java/sh/rhiobet/lalafin/file/FileInfoService.java b/src/main/java/sh/rhiobet/lalafin/file/FileInfoService.java index 5582128..ef7e0d7 100644 --- a/src/main/java/sh/rhiobet/lalafin/file/FileInfoService.java +++ b/src/main/java/sh/rhiobet/lalafin/file/FileInfoService.java @@ -9,6 +9,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import java.util.stream.Stream; +import java.util.zip.ZipFile; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.ws.rs.core.PathSegment; @@ -16,6 +17,7 @@ import sh.rhiobet.lalafin.LalafinConfiguration; import sh.rhiobet.lalafin.api.advent.AdventAccessService; import sh.rhiobet.lalafin.api.configuration.FileApiConfiguration; import sh.rhiobet.lalafin.api.internal.redis.FileTokenProvider; +import sh.rhiobet.lalafin.api.model.ArchiveInfo; import sh.rhiobet.lalafin.api.model.FileInfo; import sh.rhiobet.lalafin.api.model.FileInfoBase; import sh.rhiobet.lalafin.api.model.FolderInfo; @@ -110,7 +112,7 @@ public class FileInfoService { } StringBuilder playlistContent = new StringBuilder(); try { - Stream stream = Files.list(path); + Stream stream = Files.list(path).sorted(); stream.forEach(p -> { String fileName = p.getFileName().toString(); String fileUri = URLEncoder.encode(fileName, StandardCharsets.UTF_8) @@ -213,22 +215,50 @@ public class FileInfoService { return folderInfo; } else { String requestedFilenameUri = URLEncoder - .encode(requestedFilename, StandardCharsets.UTF_8).replace("+", "%20"); - FileInfo fileInfo = new FileInfo(requestedFilename, "/file" + requestedUri); - if (!requestedThumbUrl.isEmpty()) { - fileInfo.thumbnailUrl = requestedThumbUrl; - } - if (fileTokenProvider != null) { - fileInfo.publicApiUrl = - "/api/public/file/token/" + fileTokenProvider.getFileToken(requestedUri) - + "/" + requestedFilenameUri; - } + .encode(requestedFilename, StandardCharsets.UTF_8).replace("+", "%20"); if (requestedFilename.endsWith(".zip")) { - fileInfo.viewUrl = "/view" + requestedUri + "/1"; - } else if (requestedFilename.endsWith(".epub")) { - fileInfo.viewUrl = "/view" + requestedUri + "/0"; + ArchiveInfo archiveInfo = new ArchiveInfo(requestedFilename, "/file" + requestedUri); + if (!requestedThumbUrl.isEmpty()) { + archiveInfo.thumbnailUrl = requestedThumbUrl; + } + archiveInfo.viewUrl = "/view" + requestedUri + "/1"; + + Path zipPath = Paths.get(fileApiConfiguration.directory() + "/file/" + requestedPath); + try { + ZipFile zipFile = new ZipFile(zipPath.toFile()); + zipFile.stream().filter(e -> !e.isDirectory()).forEach(e -> { + String zipEntryName = e.getName(); + String zipEntryNameUri = + URLEncoder.encode(zipEntryName, StandardCharsets.UTF_8).replace("+", "%20"); + String zipEntryUri = requestedUri + "/" + zipEntryNameUri; + FileInfo zipEntryInfo = new FileInfo(zipEntryName, "/file" + zipEntryUri); + if (fileTokenProvider != null) { + zipEntryInfo.publicApiUrl = "/api/public/file/token/" + + fileTokenProvider.getFileToken(zipEntryUri) + "/" + zipEntryNameUri; + } + archiveInfo.content.add(zipEntryInfo); + }); + } catch (IOException e) { + e.printStackTrace(); + } + + return archiveInfo; + } else { + FileInfo fileInfo = new FileInfo(requestedFilename, "/file" + requestedUri); + if (!requestedThumbUrl.isEmpty()) { + fileInfo.thumbnailUrl = requestedThumbUrl; + } + if (fileTokenProvider != null) { + fileInfo.publicApiUrl = "/api/public/file/token/" + + fileTokenProvider.getFileToken(requestedUri) + "/" + requestedFilenameUri; + } + if (requestedFilename.endsWith(".zip")) { + fileInfo.viewUrl = "/view" + requestedUri + "/1"; + } else if (requestedFilename.endsWith(".epub")) { + fileInfo.viewUrl = "/view" + requestedUri + "/0"; + } + return fileInfo; } - return fileInfo; } } return null; diff --git a/src/main/java/sh/rhiobet/lalafin/file/FileServeService.java b/src/main/java/sh/rhiobet/lalafin/file/FileServeService.java index 5ea844d..a6fd455 100644 --- a/src/main/java/sh/rhiobet/lalafin/file/FileServeService.java +++ b/src/main/java/sh/rhiobet/lalafin/file/FileServeService.java @@ -8,6 +8,8 @@ import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.ws.rs.core.Response; @@ -100,6 +102,31 @@ public class FileServeService { } } + public Response serveArchiveContent(FileInfo fileInfo, String name) { + try { + Path path = Paths.get(fileApiConfiguration.directory(), + URLDecoder.decode(fileInfo.directUrl, StandardCharsets.UTF_8)); + ZipFile zipFile = new ZipFile(path.toFile()); + ZipEntry zipEntry = zipFile.getEntry(name); + + if (zipEntry == null) { + throw new IOException(); + } + + ResponseBuilder response; + long fileSize = zipEntry.getSize(); + + response = Response.ok(zipFile.getInputStream(zipEntry)); + response.header("Content-Length", Long.toString(fileSize)); + + response.header("Accept-Ranges", "bytes"); + response.header("Content-Disposition", "inline; filename=\"" + name + "\""); + response.header("Content-Type", FileHelper.getMimeType(name)); + return response.build(); + } catch (IOException e) { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + } } class FileServeInputStream extends InputStream { @@ -188,4 +215,4 @@ class FileServeInputStream extends InputStream { return -1; } } -} \ No newline at end of file +} diff --git a/src/main/resources/templates/view-index.html b/src/main/resources/templates/view-index.html index 16bdb7f..672722c 100644 --- a/src/main/resources/templates/view-index.html +++ b/src/main/resources/templates/view-index.html @@ -5,24 +5,72 @@ {info.filename} + - - + +
back{currpage}/{totpage}{#if currpage > 1}< {/if}{#if currpage < totpage}>{/if}{currpage}/{totpage} 1}href="{prevuri}"{/if}>< >

- {#if currpage < totpage}{/if} + {#if currpage < totpage}{/if} {#if image??} - + {#else if video??} {/if} {#if currpage < totpage}{/if}
+ {#if image??} + + {/if}