Update quarkus to version 3.30.2

* Also adds a bunch of old things I don't remember writing
This commit is contained in:
2025-12-06 15:32:40 +01:00
parent afb1be9793
commit b50ceff57e
9 changed files with 213 additions and 44 deletions

View File

@@ -1,5 +1,5 @@
executable: 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: image:
docker build -t rhiobet/lalafin$(IMAGE_TAG) . -f src/main/docker/Dockerfile.native docker build -t rhiobet/lalafin$(IMAGE_TAG) . -f src/main/docker/Dockerfile.native

View File

@@ -18,7 +18,9 @@ stdenv.mkDerivation rec {
''; '';
postFixup = '' 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; { meta = with lib; {

23
pom.xml
View File

@@ -6,17 +6,17 @@
<artifactId>lalafin</artifactId> <artifactId>lalafin</artifactId>
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
<properties> <properties>
<compiler-plugin.version>3.8.1</compiler-plugin.version> <compiler-plugin.version>3.12.1</compiler-plugin.version>
<maven.compiler.parameters>true</maven.compiler.parameters> <maven.compiler.parameters>true</maven.compiler.parameters>
<maven.compiler.source>11</maven.compiler.source> <maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target> <maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus-plugin.version>3.4.1</quarkus-plugin.version> <quarkus-plugin.version>3.30.2</quarkus-plugin.version>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id> <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id> <quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
<quarkus.platform.version>3.4.1</quarkus.platform.version> <quarkus.platform.version>3.30.2</quarkus.platform.version>
<surefire-plugin.version>2.22.1</surefire-plugin.version> <surefire-plugin.version>3.2.3</surefire-plugin.version>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>
@@ -32,19 +32,19 @@
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive</artifactId> <artifactId>quarkus-rest</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-qute</artifactId> <artifactId>quarkus-rest-qute</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-jackson</artifactId> <artifactId>quarkus-rest-jackson</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-jaxb</artifactId> <artifactId>quarkus-rest-jaxb</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
@@ -107,7 +107,8 @@
<profile> <profile>
<id>native</id> <id>native</id>
<properties> <properties>
<quarkus.package.type>native</quarkus.package.type> <quarkus.native.enabled>true</quarkus.native.enabled>
<quarkus.package.jar.enabled>false</quarkus.package.jar.enabled>
</properties> </properties>
<build> <build>
<plugins> <plugins>

View File

@@ -1,7 +1,9 @@
package sh.rhiobet.lalafin.api; package sh.rhiobet.lalafin.api;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List; import java.util.List;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.ws.rs.GET; import jakarta.ws.rs.GET;
@@ -50,11 +52,24 @@ public class FilePublicAPI {
String decodedFile = URLDecoder.decode(token.file, StandardCharsets.UTF_8); String decodedFile = URLDecoder.decode(token.file, StandardCharsets.UTF_8);
if (request.remoteAddress().host().toString().equals(token.ip) if (request.remoteAddress().host().toString().equals(token.ip)
&& System.currentTimeMillis() < token.timestamp + 172800000) { && System.currentTimeMillis() < token.timestamp + 172800000) {
FileInfoBase fileInfoBase = fileInfoService.getInfo(decodedFile.split("/"), null); String[] decodedFileSplitted = decodedFile.split("/");
if (fileInfoBase instanceof FileInfo) { FileInfoBase fileInfoBase;
return fileServeService.serveFile((FileInfo) fileInfoBase, range); 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 { } 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 { } else {
return Response.status(Response.Status.FORBIDDEN).build(); return Response.status(Response.Status.FORBIDDEN).build();

View File

@@ -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<FileInfoBase> 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, "");
}
}

View File

@@ -84,10 +84,14 @@ public class JellyfinThumbnailGenerator implements ThumbnailGenerator {
try { try {
Optional<Path> imgPath; Optional<Path> imgPath;
Path posterPath = folderPath.resolve("poster.jpg"); Path posterPathJpg = folderPath.resolve("poster.jpg");
if (Files.exists(posterPath)) { Path posterPathPng = folderPath.resolve("poster.png");
// Check for series poster if (Files.exists(posterPathJpg)) {
imgPath = Optional.of(posterPath); // 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 ") } else if (folderPath.getFileName().toString().startsWith("Season ")
|| (folderPath.getFileName().toString().equals("Specials"))) { || (folderPath.getFileName().toString().equals("Specials"))) {
// Season folder // Season folder
@@ -95,10 +99,22 @@ public class JellyfinThumbnailGenerator implements ThumbnailGenerator {
if (folderName.startsWith("Season ")) { if (folderName.startsWith("Season ")) {
String seasonNumber = String seasonNumber =
String.format("%02d", Integer.parseInt(folderName.substring(7))); String.format("%02d", Integer.parseInt(folderName.substring(7)));
imgPath = Optional Path seasonPathJpg =
.of(folderPath.resolveSibling("season" + seasonNumber + "-poster.jpg")); 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 { } 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 { } else {
imgPath = Optional.empty(); imgPath = Optional.empty();

View File

@@ -9,6 +9,7 @@ import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.List; import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
import java.util.zip.ZipFile;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.ws.rs.core.PathSegment; 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.advent.AdventAccessService;
import sh.rhiobet.lalafin.api.configuration.FileApiConfiguration; import sh.rhiobet.lalafin.api.configuration.FileApiConfiguration;
import sh.rhiobet.lalafin.api.internal.redis.FileTokenProvider; 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.FileInfo;
import sh.rhiobet.lalafin.api.model.FileInfoBase; import sh.rhiobet.lalafin.api.model.FileInfoBase;
import sh.rhiobet.lalafin.api.model.FolderInfo; import sh.rhiobet.lalafin.api.model.FolderInfo;
@@ -110,7 +112,7 @@ public class FileInfoService {
} }
StringBuilder playlistContent = new StringBuilder(); StringBuilder playlistContent = new StringBuilder();
try { try {
Stream<Path> stream = Files.list(path); Stream<Path> stream = Files.list(path).sorted();
stream.forEach(p -> { stream.forEach(p -> {
String fileName = p.getFileName().toString(); String fileName = p.getFileName().toString();
String fileUri = URLEncoder.encode(fileName, StandardCharsets.UTF_8) String fileUri = URLEncoder.encode(fileName, StandardCharsets.UTF_8)
@@ -213,22 +215,50 @@ public class FileInfoService {
return folderInfo; return folderInfo;
} else { } else {
String requestedFilenameUri = URLEncoder String requestedFilenameUri = URLEncoder
.encode(requestedFilename, StandardCharsets.UTF_8).replace("+", "%20"); .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;
}
if (requestedFilename.endsWith(".zip")) { if (requestedFilename.endsWith(".zip")) {
fileInfo.viewUrl = "/view" + requestedUri + "/1"; ArchiveInfo archiveInfo = new ArchiveInfo(requestedFilename, "/file" + requestedUri);
} else if (requestedFilename.endsWith(".epub")) { if (!requestedThumbUrl.isEmpty()) {
fileInfo.viewUrl = "/view" + requestedUri + "/0"; 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; return null;

View File

@@ -8,6 +8,8 @@ import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.ws.rs.core.Response; 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 { class FileServeInputStream extends InputStream {

View File

@@ -5,24 +5,72 @@
<meta name="mobile-web-app-capable" content="yes" /> <meta name="mobile-web-app-capable" content="yes" />
<link rel="stylesheet" href="/style/sakura.css" /> <link rel="stylesheet" href="/style/sakura.css" />
<title>{info.filename}</title> <title>{info.filename}</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head> </head>
<body> <body>
<table style="table-layout: fixed;"> <table style="table-layout: fixed;">
<tr> <tr>
<td style="text-align: left; padding-top: 0; padding-bottom: 0;"><a href="{info.directUrl}/..">back</a></td> <td style="text-align: left; padding-top: 0; padding-bottom: 0;"><a href="{info.directUrl}/..">back</a></td>
<td style="text-align: center; padding-top: 0; padding-bottom: 0;">{currpage}/{totpage}</td> <td id="viewer-page-counter" style="text-align: center; padding-top: 0; padding-bottom: 0;">{currpage}/{totpage}</td>
<td style="text-align: right; padding-top: 0; padding-bottom: 0;">{#if currpage > 1}<a href="{prevuri}">&lt;</a> {/if}{#if currpage < totpage}<a href="{nexturi}">&gt;</a>{/if}</td> <td style="text-align: right; padding-top: 0; padding-bottom: 0;"><a id="viewer-page-navigation-left" {#if currpage > 1}href="{prevuri}"{/if}>&lt;</a> <a id="viewer-page-navigation-right" {#if currpage < totpage}href="{nexturi}"{/if}>&gt;</a></td>
</tr> </tr>
</table> </table>
<hr /> <hr />
{#if currpage < totpage}<a href="{nexturi}">{/if} {#if currpage < totpage}<a id="viewer-page-link" href="{nexturi}">{/if}
{#if image??} {#if image??}
<img style="position: absolute; left: 50%; transform: translate(-50%, 0);" src="{image}" /> <img id="viewer-page-image" style="position: absolute; left: 50%; transform: translate(-50%, 0);" src="{image}" />
{#else if video??} {#else if video??}
<video src="{video}" controls style="width: 100%"></video> <video src="{video}" controls style="width: 100%"></video>
{/if} {/if}
{#if currpage < totpage}</a>{/if} {#if currpage < totpage}</a>{/if}
<hr /> <hr />
{#if image??}
<script>
var pages = [];
var preloadedPages = [];
$.getJSON('/api/private{info.directUrl}', function(data) {
for (const c of data.content) {
pages.push(c.publicApiUrl);
}
for (const page of pages) {
preloadedPages.push(new Promise(resolve => {
const img = new Image();
img.src = page;
resolve(img);
}));
}
displayPage({currpage});
});
function displayPage(pageNumber) {
var imgtag = document.getElementById("viewer-page-image");
var atag = document.getElementById("viewer-page-link");
atag.setAttribute("href", "#");
var leftatag = document.getElementById("viewer-page-navigation-left");
leftatag.setAttribute('href', '#');
if (pageNumber > 1) {
leftatag.setAttribute('onClick', 'displayPage(' + (pageNumber-1) + ')');
}
var rightatag = document.getElementById("viewer-page-navigation-right");
if (pageNumber < pages.length) {
rightatag.setAttribute('href', '#');
rightatag.setAttribute('onClick', 'displayPage(' + (pageNumber+1) + ')');
atag.setAttribute('onClick', 'displayPage(' + (pageNumber+1) + ')');
}
if (typeof preloadedPages !== "undefined" && preloadedPages.length > 0) {
preloadedPages[pageNumber-1].then(img => {
img.setAttribute('id', imgtag.id)
img.setAttribute('style', imgtag.style.cssText);
atag.innerHTML = '';
atag.appendChild(img);
});
}
var middlespantag = document.getElementById("viewer-page-counter");
middlespantag.innerHTML = pageNumber + "/" + pages.length;
}
</script>
{/if}
</body> </body>
</html> </html>