Add cache for performance improvements + small fixes

This commit is contained in:
2026-04-26 18:20:07 +02:00
parent a60df48774
commit 1c6b127543
12 changed files with 121 additions and 53 deletions

View File

@@ -24,24 +24,21 @@ public class AdventAccessService implements AccessService {
@Override @Override
public boolean checkAccess(Path path) { public boolean checkAccess(Path path) {
Optional<String> pathUriOptional = this.fileURIResolver.resolve(path); Optional<String> pathUri = this.fileURIResolver.resolve(path);
String pathUri; if (pathUri.isEmpty()) {
if (pathUriOptional.isEmpty()) {
return false; return false;
} else {
pathUri = pathUriOptional.get().replaceAll("^.file", "");
} }
Optional<AdventConfiguration.AdventEvent> matchingAdvent = adventConfiguration.events().stream() Optional<AdventConfiguration.AdventEvent> matchingAdvent = adventConfiguration.events().stream()
.filter(e -> pathUri.startsWith(e.path())) .filter(e -> pathUri.get().startsWith(e.path()))
.findFirst(); .findFirst();
if (matchingAdvent.isEmpty()) { if (matchingAdvent.isEmpty()) {
return true; return true;
} }
String dayString = pathUri.replaceAll( String dayString = pathUri.get().replaceAll(
"^" + Pattern.quote(matchingAdvent.get().path()) + "/?([^.]+)\\.?[^.]*$", "^" + Pattern.quote(matchingAdvent.get().path()) + "/?([^.]+)\\.?[^.]*$",
"$1"); "$1");
try { try {

View File

@@ -50,7 +50,7 @@ public class AdventThumbnailPathURIResolver extends PathAccessor implements Path
private Optional<java.nio.file.Path> getDefaultThumbnailPath(Path path) { private Optional<java.nio.file.Path> getDefaultThumbnailPath(Path path) {
try { 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(\\.[^.]+)*$")) .filter(f -> f.getFileName().toString().matches("^00(\\.[^.]+)*$"))
.findFirst(); .findFirst();
} catch (IOException ignored) { } catch (IOException ignored) {

View File

@@ -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<K, V> {
private final Map<K, V> cache = new HashMap<>();
public V computeIfAbsent(K key, ThrowingFunction<K, V> 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);
}
}

View File

@@ -0,0 +1,8 @@
package sh.rhiobet.lalafin.core.cache;
import java.io.IOException;
@FunctionalInterface
public interface ThrowingFunction<K, V> {
V apply(K key) throws IOException;
}

View File

@@ -2,17 +2,18 @@ package sh.rhiobet.lalafin.core.path.model;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
public abstract class PathAccessor { public abstract class PathAccessor {
protected java.nio.file.Path getAbsolutePath(Path path) { protected Optional<java.nio.file.Path> getAbsolutePath(Path path) {
switch (path) { switch (path) {
case FileSystemPath fsp: case FileSystemPath fsp:
return fsp.absolutePath; return Optional.of(fsp.absolutePath);
case ZipEntryPath zep: case ZipEntryPath zep:
return zep.zipFilePath.absolutePath; return Optional.of(zep.zipFilePath.absolutePath);
} }
} }

View File

@@ -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<Path, Map<String, Path>> thumbnailCache;
public Cache<Path, Map<String, Path>> getCache() {
return this.thumbnailCache;
}
}

View File

@@ -2,40 +2,55 @@ package sh.rhiobet.lalafin.core.thumbnail;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional; 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.FileSystemPath;
import sh.rhiobet.lalafin.core.path.model.Path; import sh.rhiobet.lalafin.core.path.model.Path;
import sh.rhiobet.lalafin.core.path.model.PathAccessor; import sh.rhiobet.lalafin.core.path.model.PathAccessor;
import sh.rhiobet.lalafin.core.path.model.ZipEntryPath; import sh.rhiobet.lalafin.core.path.model.ZipEntryPath;
public abstract class ThumbnailPathAccessor extends PathAccessor { public abstract class ThumbnailPathAccessor extends PathAccessor {
@Inject
protected ThumbnailCacheManager thumbnailCacheManager;
@Override @Override
protected java.nio.file.Path getAbsolutePath(Path path) { protected Optional<java.nio.file.Path> getAbsolutePath(Path path) {
try { try {
switch (path) { switch (path) {
case FileSystemPath fsp: case FileSystemPath fsp:
java.nio.file.Path thumbnailsFolder = java.nio.file.Path thumbnailsFolder = getThumbnailRootPath(path);
super.getAbsolutePath(path).getParent().resolve(".thumbnails");
Optional<java.nio.file.Path> foundPath = Optional.empty(); Map<String, java.nio.file.Path> thumbnails =
this.thumbnailCacheManager.getCache().computeIfAbsent(thumbnailsFolder, p -> {
if (Files.exists(thumbnailsFolder)) { if (Files.exists(thumbnailsFolder)) {
foundPath = Files.list(thumbnailsFolder) return Files.list(thumbnailsFolder)
.filter(f -> f.getFileName().toString() .collect(Collectors.toMap(
.matches("^" + Pattern.quote(path.getFilename()) + "(\\.[^.]+)*$")) f -> f.getFileName().toString().replaceAll("\\.[^.]+$", ""),
.findFirst(); f -> f,
(a, b) -> a,
HashMap::new
));
} else {
return new HashMap<>();
} }
});
return foundPath.isPresent() ? return Optional.of(thumbnails.getOrDefault(path.getFilename(),
foundPath.get() : thumbnailsFolder.resolve(path.getFilename() + ".png")));
thumbnailsFolder.resolve(path.getFilename() + ".png");
case ZipEntryPath zep: case ZipEntryPath zep:
return null; return Optional.empty();
} }
} catch (IOException ignored) { } catch (IOException ignored) {
return null; return Optional.empty();
} }
} }
protected java.nio.file.Path getThumbnailRootPath(Path path) {
return super.getAbsolutePath(path).get().getParent().resolve(".thumbnails");
}
} }

View File

@@ -26,17 +26,14 @@ public class FileRoleAccessService implements AccessService {
PathURIResolver fileURIResolver; PathURIResolver fileURIResolver;
public boolean checkAccess(Path path) { public boolean checkAccess(Path path) {
Optional<String> pathUriOptional = this.fileURIResolver.resolve(path); Optional<String> pathUri = this.fileURIResolver.resolve(path);
String pathUri; if (pathUri.isEmpty()) {
if (pathUriOptional.isEmpty()) {
return false; return false;
} else {
pathUri = pathUriOptional.get().replaceAll("^.file", "");
} }
List<FileConfiguration.Route> matchingRoutes = fileConfiguration.routes().stream() List<FileConfiguration.Route> matchingRoutes = fileConfiguration.routes().stream()
.filter(r -> pathUri.startsWith(r.path())) .filter(r -> pathUri.get().startsWith(r.path()))
.toList(); .toList();
if (matchingRoutes.isEmpty()) { if (matchingRoutes.isEmpty()) {

View File

@@ -1,5 +1,6 @@
package sh.rhiobet.lalafin.file.model; package sh.rhiobet.lalafin.file.model;
import io.quarkus.logging.Log;
import java.io.IOException; import java.io.IOException;
import java.util.Optional; import java.util.Optional;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
@@ -42,7 +43,7 @@ public class FileMetadataService {
this.fileThumbnailGenerator.generate(filePath); this.fileThumbnailGenerator.generate(filePath);
thumbUrl = this.fileThumbnailURIResolver.resolve(filePath); thumbUrl = this.fileThumbnailURIResolver.resolve(filePath);
} catch (IOException e) { } 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); this.fileThumbnailGenerator.generate(child);
childThumbUrl = this.fileThumbnailURIResolver.resolve(child); childThumbUrl = this.fileThumbnailURIResolver.resolve(child);
} catch (IOException e) { } 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); folderInfo.content.add(contentInfo);
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); Log.warn("Failed to walk through children of " + filePath + ".", e);
} }
return folderInfo; return folderInfo;

View File

@@ -23,10 +23,6 @@ public class FilePathURIResolver extends PathAccessor implements PathURIResolver
public Optional<String> resolve(Path path) { public Optional<String> resolve(Path path) {
List<String> segments = getSegments(path); List<String> segments = getSegments(path);
if (segments.isEmpty()) {
return Optional.of("/file/");
}
return Optional.of(segments.stream() return Optional.of(segments.stream()
.map(s -> "/" + Encode.encodePathSegment(s)) .map(s -> "/" + Encode.encodePathSegment(s))
.reduce("/file", String::concat)); .reduce("/file", String::concat));

View File

@@ -4,7 +4,9 @@ import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Optional; 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.Inject;
import jakarta.inject.Named; import jakarta.inject.Named;
import sh.rhiobet.lalafin.file.configuration.FileConfiguration; 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.path.resolver.PathURIResolver;
import sh.rhiobet.lalafin.core.thumbnail.ThumbnailPathAccessor; import sh.rhiobet.lalafin.core.thumbnail.ThumbnailPathAccessor;
@ApplicationScoped @RequestScoped
@Named("file/resolver/thumbnail") @Named("file/resolver/thumbnail")
public class FileThumbnailPathURIResolver extends ThumbnailPathAccessor implements PathURIResolver { public class FileThumbnailPathURIResolver extends ThumbnailPathAccessor implements PathURIResolver {
@Inject @Inject
@@ -24,13 +26,12 @@ public class FileThumbnailPathURIResolver extends ThumbnailPathAccessor implemen
public Optional<String> resolve(Path path) { public Optional<String> resolve(Path path) {
switch (path) { switch (path) {
case FileSystemPath fsp: case FileSystemPath fsp:
java.nio.file.Path thumbnailAbsolutePath = getAbsolutePath(path); Optional<java.nio.file.Path> 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()); java.nio.file.Path rootFolderPath = Paths.get(this.fileConfiguration.directory());
return Optional.of( return Optional.of("/file/" + Encode.encodePath(
"/file/" + rootFolderPath.resolve("file").relativize(thumbnailAbsolutePath) rootFolderPath.resolve("file").relativize(thumbnailAbsolutePath.get()).toString()));
.toString());
} else { } else {
return Optional.empty(); return Optional.empty();
} }

View File

@@ -3,6 +3,8 @@ package sh.rhiobet.lalafin.file.thumbnail;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.Map;
import java.util.Optional;
import org.im4java.core.ConvertCmd; import org.im4java.core.ConvertCmd;
import org.im4java.core.IM4JavaException; import org.im4java.core.IM4JavaException;
@@ -26,17 +28,17 @@ public class FileThumbnailGenerator extends ThumbnailPathAccessor implements Thu
@Override @Override
public void generate(Path path) throws IOException { public void generate(Path path) throws IOException {
java.nio.file.Path thumbnailAbsolutePath = getAbsolutePath(path); Optional<java.nio.file.Path> thumbnailAbsolutePath = getAbsolutePath(path);
if (!path.canHaveChildren() || thumbnailAbsolutePath == null) { if (!path.canHaveChildren() || thumbnailAbsolutePath.isEmpty()) {
return; return;
} }
if (Files.exists(thumbnailAbsolutePath)) { if (Files.exists(thumbnailAbsolutePath.get())) {
Files.delete(thumbnailAbsolutePath); Files.delete(thumbnailAbsolutePath.get());
} }
Files.createDirectories(thumbnailAbsolutePath.getParent()); Files.createDirectories(thumbnailAbsolutePath.get().getParent());
for (Path childPath : path.getChildren()) { for (Path childPath : path.getChildren()) {
String mimetype = MimeType.getMimeType(childPath.getFilename()); String mimetype = MimeType.getMimeType(childPath.getFilename());
@@ -54,10 +56,14 @@ public class FileThumbnailGenerator extends ThumbnailPathAccessor implements Thu
Pipe inputPipe = new Pipe(pathInputStream, null); Pipe inputPipe = new Pipe(pathInputStream, null);
op.addImage(extension + ":-"); op.addImage(extension + ":-");
op.resize(200, null); op.resize(200, null);
op.addImage(thumbnailAbsolutePath.toString()); op.addImage(thumbnailAbsolutePath.get().toString());
convert.setInputProvider(inputPipe); convert.setInputProvider(inputPipe);
convert.run(op); convert.run(op);
Map<String, java.nio.file.Path> thumbnailCache =
this.thumbnailCacheManager.getCache().get(getThumbnailRootPath(path));
thumbnailCache.put(path.getFilename(), thumbnailAbsolutePath.get());
return; return;
} catch (InterruptedException | IM4JavaException e) { } catch (InterruptedException | IM4JavaException e) {
throw new IOException("Error when generating thumbnail with IM4J.", e); throw new IOException("Error when generating thumbnail with IM4J.", e);