From 1c6b12754394031c5c0f8bca351d8bf9fb11db66 Mon Sep 17 00:00:00 2001 From: RhiobeT Date: Sun, 26 Apr 2026 18:20:07 +0200 Subject: [PATCH] Add cache for performance improvements + small fixes --- .../advent/access/AdventAccessService.java | 11 ++--- .../AdventThumbnailPathURIResolver.java | 2 +- .../sh/rhiobet/lalafin/core/cache/Cache.java | 28 +++++++++++ .../lalafin/core/cache/ThrowingFunction.java | 8 ++++ .../lalafin/core/path/model/PathAccessor.java | 7 +-- .../core/thumbnail/ThumbnailCacheManager.java | 18 +++++++ .../core/thumbnail/ThumbnailPathAccessor.java | 47 ++++++++++++------- .../file/access/FileRoleAccessService.java | 9 ++-- .../file/model/FileMetadataService.java | 7 +-- .../file/resolver/FilePathURIResolver.java | 4 -- .../FileThumbnailPathURIResolver.java | 15 +++--- .../thumbnail/FileThumbnailGenerator.java | 18 ++++--- 12 files changed, 121 insertions(+), 53 deletions(-) create mode 100644 src/main/java/sh/rhiobet/lalafin/core/cache/Cache.java create mode 100644 src/main/java/sh/rhiobet/lalafin/core/cache/ThrowingFunction.java create mode 100644 src/main/java/sh/rhiobet/lalafin/core/thumbnail/ThumbnailCacheManager.java diff --git a/src/main/java/sh/rhiobet/lalafin/advent/access/AdventAccessService.java b/src/main/java/sh/rhiobet/lalafin/advent/access/AdventAccessService.java index 1b7b623..6562da4 100644 --- a/src/main/java/sh/rhiobet/lalafin/advent/access/AdventAccessService.java +++ b/src/main/java/sh/rhiobet/lalafin/advent/access/AdventAccessService.java @@ -24,24 +24,21 @@ public class AdventAccessService implements AccessService { @Override public boolean checkAccess(Path path) { - Optional pathUriOptional = this.fileURIResolver.resolve(path); + Optional pathUri = this.fileURIResolver.resolve(path); - String pathUri; - if (pathUriOptional.isEmpty()) { + if (pathUri.isEmpty()) { return false; - } else { - pathUri = pathUriOptional.get().replaceAll("^.file", ""); } Optional matchingAdvent = adventConfiguration.events().stream() - .filter(e -> pathUri.startsWith(e.path())) + .filter(e -> pathUri.get().startsWith(e.path())) .findFirst(); if (matchingAdvent.isEmpty()) { return true; } - String dayString = pathUri.replaceAll( + String dayString = pathUri.get().replaceAll( "^" + Pattern.quote(matchingAdvent.get().path()) + "/?([^.]+)\\.?[^.]*$", "$1"); try { diff --git a/src/main/java/sh/rhiobet/lalafin/advent/resolver/AdventThumbnailPathURIResolver.java b/src/main/java/sh/rhiobet/lalafin/advent/resolver/AdventThumbnailPathURIResolver.java index faa99f2..3c174a1 100644 --- a/src/main/java/sh/rhiobet/lalafin/advent/resolver/AdventThumbnailPathURIResolver.java +++ b/src/main/java/sh/rhiobet/lalafin/advent/resolver/AdventThumbnailPathURIResolver.java @@ -50,7 +50,7 @@ public class AdventThumbnailPathURIResolver extends PathAccessor implements Path private Optional getDefaultThumbnailPath(Path path) { try { - return Files.list(getAbsolutePath(path).getParent().resolve(".thumbnails")) + return Files.list(getAbsolutePath(path).get().getParent().resolve(".thumbnails")) .filter(f -> f.getFileName().toString().matches("^00(\\.[^.]+)*$")) .findFirst(); } catch (IOException ignored) { diff --git a/src/main/java/sh/rhiobet/lalafin/core/cache/Cache.java b/src/main/java/sh/rhiobet/lalafin/core/cache/Cache.java new file mode 100644 index 0000000..e2d8656 --- /dev/null +++ b/src/main/java/sh/rhiobet/lalafin/core/cache/Cache.java @@ -0,0 +1,28 @@ +package sh.rhiobet.lalafin.core.cache; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import jakarta.enterprise.context.Dependent; + +@Dependent +public class Cache { + private final Map cache = new HashMap<>(); + + public V computeIfAbsent(K key, ThrowingFunction loader) throws IOException { + if (!cache.containsKey(key)) { + cache.put(key, loader.apply(key)); + } + + return cache.get(key); + } + + public V get(K key) { + return this.cache.get(key); + } + + public void invalidate(K key) { + cache.remove(key); + } +} diff --git a/src/main/java/sh/rhiobet/lalafin/core/cache/ThrowingFunction.java b/src/main/java/sh/rhiobet/lalafin/core/cache/ThrowingFunction.java new file mode 100644 index 0000000..2b094cf --- /dev/null +++ b/src/main/java/sh/rhiobet/lalafin/core/cache/ThrowingFunction.java @@ -0,0 +1,8 @@ +package sh.rhiobet.lalafin.core.cache; + +import java.io.IOException; + +@FunctionalInterface +public interface ThrowingFunction { + V apply(K key) throws IOException; +} diff --git a/src/main/java/sh/rhiobet/lalafin/core/path/model/PathAccessor.java b/src/main/java/sh/rhiobet/lalafin/core/path/model/PathAccessor.java index a39633b..e5de407 100644 --- a/src/main/java/sh/rhiobet/lalafin/core/path/model/PathAccessor.java +++ b/src/main/java/sh/rhiobet/lalafin/core/path/model/PathAccessor.java @@ -2,17 +2,18 @@ package sh.rhiobet.lalafin.core.path.model; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; public abstract class PathAccessor { - protected java.nio.file.Path getAbsolutePath(Path path) { + protected Optional getAbsolutePath(Path path) { switch (path) { case FileSystemPath fsp: - return fsp.absolutePath; + return Optional.of(fsp.absolutePath); case ZipEntryPath zep: - return zep.zipFilePath.absolutePath; + return Optional.of(zep.zipFilePath.absolutePath); } } diff --git a/src/main/java/sh/rhiobet/lalafin/core/thumbnail/ThumbnailCacheManager.java b/src/main/java/sh/rhiobet/lalafin/core/thumbnail/ThumbnailCacheManager.java new file mode 100644 index 0000000..bdde0a4 --- /dev/null +++ b/src/main/java/sh/rhiobet/lalafin/core/thumbnail/ThumbnailCacheManager.java @@ -0,0 +1,18 @@ +package sh.rhiobet.lalafin.core.thumbnail; + +import java.nio.file.Path; +import java.util.Map; + +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import sh.rhiobet.lalafin.core.cache.Cache; + +@RequestScoped +public class ThumbnailCacheManager { + @Inject + private Cache> thumbnailCache; + + public Cache> getCache() { + return this.thumbnailCache; + } +} diff --git a/src/main/java/sh/rhiobet/lalafin/core/thumbnail/ThumbnailPathAccessor.java b/src/main/java/sh/rhiobet/lalafin/core/thumbnail/ThumbnailPathAccessor.java index 98f6731..2d59868 100644 --- a/src/main/java/sh/rhiobet/lalafin/core/thumbnail/ThumbnailPathAccessor.java +++ b/src/main/java/sh/rhiobet/lalafin/core/thumbnail/ThumbnailPathAccessor.java @@ -2,40 +2,55 @@ package sh.rhiobet.lalafin.core.thumbnail; import java.io.IOException; import java.nio.file.Files; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; -import java.util.regex.Pattern; +import java.util.stream.Collectors; +import jakarta.inject.Inject; import sh.rhiobet.lalafin.core.path.model.FileSystemPath; import sh.rhiobet.lalafin.core.path.model.Path; import sh.rhiobet.lalafin.core.path.model.PathAccessor; import sh.rhiobet.lalafin.core.path.model.ZipEntryPath; public abstract class ThumbnailPathAccessor extends PathAccessor { + @Inject + protected ThumbnailCacheManager thumbnailCacheManager; + @Override - protected java.nio.file.Path getAbsolutePath(Path path) { + protected Optional getAbsolutePath(Path path) { try { switch (path) { case FileSystemPath fsp: - java.nio.file.Path thumbnailsFolder = - super.getAbsolutePath(path).getParent().resolve(".thumbnails"); + java.nio.file.Path thumbnailsFolder = getThumbnailRootPath(path); - Optional foundPath = Optional.empty(); - if (Files.exists(thumbnailsFolder)) { - foundPath = Files.list(thumbnailsFolder) - .filter(f -> f.getFileName().toString() - .matches("^" + Pattern.quote(path.getFilename()) + "(\\.[^.]+)*$")) - .findFirst(); - } + Map thumbnails = + this.thumbnailCacheManager.getCache().computeIfAbsent(thumbnailsFolder, p -> { + if (Files.exists(thumbnailsFolder)) { + return Files.list(thumbnailsFolder) + .collect(Collectors.toMap( + f -> f.getFileName().toString().replaceAll("\\.[^.]+$", ""), + f -> f, + (a, b) -> a, + HashMap::new + )); + } else { + return new HashMap<>(); + } + }); - return foundPath.isPresent() ? - foundPath.get() : - thumbnailsFolder.resolve(path.getFilename() + ".png"); + return Optional.of(thumbnails.getOrDefault(path.getFilename(), + thumbnailsFolder.resolve(path.getFilename() + ".png"))); case ZipEntryPath zep: - return null; + return Optional.empty(); } } catch (IOException ignored) { - return null; + return Optional.empty(); } } + + protected java.nio.file.Path getThumbnailRootPath(Path path) { + return super.getAbsolutePath(path).get().getParent().resolve(".thumbnails"); + } } diff --git a/src/main/java/sh/rhiobet/lalafin/file/access/FileRoleAccessService.java b/src/main/java/sh/rhiobet/lalafin/file/access/FileRoleAccessService.java index 1605e8c..57cfbac 100644 --- a/src/main/java/sh/rhiobet/lalafin/file/access/FileRoleAccessService.java +++ b/src/main/java/sh/rhiobet/lalafin/file/access/FileRoleAccessService.java @@ -26,17 +26,14 @@ public class FileRoleAccessService implements AccessService { PathURIResolver fileURIResolver; public boolean checkAccess(Path path) { - Optional pathUriOptional = this.fileURIResolver.resolve(path); + Optional pathUri = this.fileURIResolver.resolve(path); - String pathUri; - if (pathUriOptional.isEmpty()) { + if (pathUri.isEmpty()) { return false; - } else { - pathUri = pathUriOptional.get().replaceAll("^.file", ""); } List matchingRoutes = fileConfiguration.routes().stream() - .filter(r -> pathUri.startsWith(r.path())) + .filter(r -> pathUri.get().startsWith(r.path())) .toList(); if (matchingRoutes.isEmpty()) { diff --git a/src/main/java/sh/rhiobet/lalafin/file/model/FileMetadataService.java b/src/main/java/sh/rhiobet/lalafin/file/model/FileMetadataService.java index 2b6fa51..ee91ea6 100644 --- a/src/main/java/sh/rhiobet/lalafin/file/model/FileMetadataService.java +++ b/src/main/java/sh/rhiobet/lalafin/file/model/FileMetadataService.java @@ -1,5 +1,6 @@ package sh.rhiobet.lalafin.file.model; +import io.quarkus.logging.Log; import java.io.IOException; import java.util.Optional; import jakarta.enterprise.context.ApplicationScoped; @@ -42,7 +43,7 @@ public class FileMetadataService { this.fileThumbnailGenerator.generate(filePath); thumbUrl = this.fileThumbnailURIResolver.resolve(filePath); } catch (IOException e) { - e.printStackTrace(); + Log.warn("Failed to generate thumbnail for " + filePath + ".", e); } } @@ -75,7 +76,7 @@ public class FileMetadataService { this.fileThumbnailGenerator.generate(child); childThumbUrl = this.fileThumbnailURIResolver.resolve(child); } catch (IOException e) { - e.printStackTrace(); + Log.warn("Failed to generate thumbnail for " + child + ".", e); } } @@ -95,7 +96,7 @@ public class FileMetadataService { folderInfo.content.add(contentInfo); } } catch (IOException e) { - e.printStackTrace(); + Log.warn("Failed to walk through children of " + filePath + ".", e); } return folderInfo; diff --git a/src/main/java/sh/rhiobet/lalafin/file/resolver/FilePathURIResolver.java b/src/main/java/sh/rhiobet/lalafin/file/resolver/FilePathURIResolver.java index 988ba82..64c6c00 100644 --- a/src/main/java/sh/rhiobet/lalafin/file/resolver/FilePathURIResolver.java +++ b/src/main/java/sh/rhiobet/lalafin/file/resolver/FilePathURIResolver.java @@ -23,10 +23,6 @@ public class FilePathURIResolver extends PathAccessor implements PathURIResolver public Optional resolve(Path path) { List segments = getSegments(path); - if (segments.isEmpty()) { - return Optional.of("/file/"); - } - return Optional.of(segments.stream() .map(s -> "/" + Encode.encodePathSegment(s)) .reduce("/file", String::concat)); diff --git a/src/main/java/sh/rhiobet/lalafin/file/resolver/FileThumbnailPathURIResolver.java b/src/main/java/sh/rhiobet/lalafin/file/resolver/FileThumbnailPathURIResolver.java index 59f2c78..a715bfa 100644 --- a/src/main/java/sh/rhiobet/lalafin/file/resolver/FileThumbnailPathURIResolver.java +++ b/src/main/java/sh/rhiobet/lalafin/file/resolver/FileThumbnailPathURIResolver.java @@ -4,7 +4,9 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.Optional; -import jakarta.enterprise.context.ApplicationScoped; +import org.jboss.resteasy.reactive.common.util.Encode; + +import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; import jakarta.inject.Named; import sh.rhiobet.lalafin.file.configuration.FileConfiguration; @@ -14,7 +16,7 @@ import sh.rhiobet.lalafin.core.path.model.ZipEntryPath; import sh.rhiobet.lalafin.core.path.resolver.PathURIResolver; import sh.rhiobet.lalafin.core.thumbnail.ThumbnailPathAccessor; -@ApplicationScoped +@RequestScoped @Named("file/resolver/thumbnail") public class FileThumbnailPathURIResolver extends ThumbnailPathAccessor implements PathURIResolver { @Inject @@ -24,13 +26,12 @@ public class FileThumbnailPathURIResolver extends ThumbnailPathAccessor implemen public Optional resolve(Path path) { switch (path) { case FileSystemPath fsp: - java.nio.file.Path thumbnailAbsolutePath = getAbsolutePath(path); + Optional thumbnailAbsolutePath = getAbsolutePath(path); - if (thumbnailAbsolutePath != null && Files.exists(thumbnailAbsolutePath)) { + if (thumbnailAbsolutePath.isPresent() && Files.exists(thumbnailAbsolutePath.get())) { java.nio.file.Path rootFolderPath = Paths.get(this.fileConfiguration.directory()); - return Optional.of( - "/file/" + rootFolderPath.resolve("file").relativize(thumbnailAbsolutePath) - .toString()); + return Optional.of("/file/" + Encode.encodePath( + rootFolderPath.resolve("file").relativize(thumbnailAbsolutePath.get()).toString())); } else { return Optional.empty(); } diff --git a/src/main/java/sh/rhiobet/lalafin/file/thumbnail/FileThumbnailGenerator.java b/src/main/java/sh/rhiobet/lalafin/file/thumbnail/FileThumbnailGenerator.java index d5c9d96..22735dd 100644 --- a/src/main/java/sh/rhiobet/lalafin/file/thumbnail/FileThumbnailGenerator.java +++ b/src/main/java/sh/rhiobet/lalafin/file/thumbnail/FileThumbnailGenerator.java @@ -3,6 +3,8 @@ package sh.rhiobet.lalafin.file.thumbnail; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; +import java.util.Map; +import java.util.Optional; import org.im4java.core.ConvertCmd; import org.im4java.core.IM4JavaException; @@ -26,17 +28,17 @@ public class FileThumbnailGenerator extends ThumbnailPathAccessor implements Thu @Override public void generate(Path path) throws IOException { - java.nio.file.Path thumbnailAbsolutePath = getAbsolutePath(path); + Optional thumbnailAbsolutePath = getAbsolutePath(path); - if (!path.canHaveChildren() || thumbnailAbsolutePath == null) { + if (!path.canHaveChildren() || thumbnailAbsolutePath.isEmpty()) { return; } - if (Files.exists(thumbnailAbsolutePath)) { - Files.delete(thumbnailAbsolutePath); + if (Files.exists(thumbnailAbsolutePath.get())) { + Files.delete(thumbnailAbsolutePath.get()); } - Files.createDirectories(thumbnailAbsolutePath.getParent()); + Files.createDirectories(thumbnailAbsolutePath.get().getParent()); for (Path childPath : path.getChildren()) { String mimetype = MimeType.getMimeType(childPath.getFilename()); @@ -54,10 +56,14 @@ public class FileThumbnailGenerator extends ThumbnailPathAccessor implements Thu Pipe inputPipe = new Pipe(pathInputStream, null); op.addImage(extension + ":-"); op.resize(200, null); - op.addImage(thumbnailAbsolutePath.toString()); + op.addImage(thumbnailAbsolutePath.get().toString()); convert.setInputProvider(inputPipe); convert.run(op); + Map thumbnailCache = + this.thumbnailCacheManager.getCache().get(getThumbnailRootPath(path)); + thumbnailCache.put(path.getFilename(), thumbnailAbsolutePath.get()); + return; } catch (InterruptedException | IM4JavaException e) { throw new IOException("Error when generating thumbnail with IM4J.", e);