Compare commits

...

2 Commits

Author SHA1 Message Date
a60df48774 Implement default thumbnail generator 2026-04-25 12:35:19 +02:00
23db8681d3 Small fixes 2026-04-22 13:26:26 +02:00
19 changed files with 353 additions and 156 deletions

View File

@@ -10,16 +10,28 @@ import jakarta.inject.Named;
import sh.rhiobet.lalafin.advent.configuration.AdventConfiguration; import sh.rhiobet.lalafin.advent.configuration.AdventConfiguration;
import sh.rhiobet.lalafin.core.access.AccessService; import sh.rhiobet.lalafin.core.access.AccessService;
import sh.rhiobet.lalafin.core.path.model.Path; import sh.rhiobet.lalafin.core.path.model.Path;
import sh.rhiobet.lalafin.core.path.resolver.PathURIResolver;
@ApplicationScoped @ApplicationScoped
@Named("advent") @Named("advent/access")
public class AdventAccessService implements AccessService { public class AdventAccessService implements AccessService {
@Inject @Inject
AdventConfiguration adventConfiguration; AdventConfiguration adventConfiguration;
@Inject
@Named("file/resolver")
PathURIResolver fileURIResolver;
@Override @Override
public boolean checkAccess(Path path) { public boolean checkAccess(Path path) {
String pathUri = path.getURI(); Optional<String> pathUriOptional = this.fileURIResolver.resolve(path);
String pathUri;
if (pathUriOptional.isEmpty()) {
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.startsWith(e.path()))

View File

@@ -1,43 +1,44 @@
package sh.rhiobet.lalafin.advent.thumbnail; package sh.rhiobet.lalafin.advent.resolver;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files; 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.annotation.Priority;
import jakarta.decorator.Decorator; import jakarta.decorator.Decorator;
import jakarta.decorator.Delegate; import jakarta.decorator.Delegate;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.inject.Named; import jakarta.inject.Named;
import sh.rhiobet.lalafin.advent.access.AdventAccessService; import sh.rhiobet.lalafin.advent.access.AdventAccessService;
import sh.rhiobet.lalafin.api.configuration.FileApiConfiguration;
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.plugin.PathPlugin; import sh.rhiobet.lalafin.core.path.resolver.PathURIResolver;
import sh.rhiobet.lalafin.file.configuration.FileConfiguration;
@Decorator @Decorator
public class AdventThumbnailPathPlugin extends PathAccessor implements PathPlugin { @Priority(0)
public class AdventThumbnailPathURIResolver extends PathAccessor implements PathURIResolver {
@Inject @Inject
FileApiConfiguration fileApiConfiguration; FileConfiguration fileConfiguration;
@Inject @Inject
@Named("file/thumbnail") @Named("file/resolver/thumbnail")
@Delegate @Delegate
PathPlugin thumbnailPathPlugin; PathURIResolver thumbnailURIResolver;
@Inject @Inject
AdventAccessService adventAccessService; AdventAccessService adventAccessService;
@Override @Override
public Optional<String> resolveURI(Path path) { public Optional<String> resolve(Path path) {
if (this.adventAccessService.checkAccess(path)) { if (this.adventAccessService.checkAccess(path)) {
return this.thumbnailPathPlugin.resolveURI(path); return this.thumbnailURIResolver.resolve(path);
} else { } else {
Optional<java.nio.file.Path> thumbnailAbsolutePath = getDefaultThumbnailPath(path); Optional<java.nio.file.Path> thumbnailAbsolutePath = getDefaultThumbnailPath(path);
if (thumbnailAbsolutePath.isPresent()) { if (thumbnailAbsolutePath.isPresent()) {
java.nio.file.Path rootFolderPath = Paths.get(this.fileApiConfiguration.directory()); java.nio.file.Path rootFolderPath = Paths.get(this.fileConfiguration.directory());
return Optional.of( return Optional.of(
"/" + rootFolderPath.resolve("file").relativize(thumbnailAbsolutePath.get()) "/" + rootFolderPath.resolve("file").relativize(thumbnailAbsolutePath.get())
.toString()); .toString());
@@ -47,15 +48,6 @@ public class AdventThumbnailPathPlugin extends PathAccessor implements PathPlugi
} }
} }
@Override
public OutputStream getOutputStream(Path path) throws IOException {
if (this.adventAccessService.checkAccess(path)) {
return this.thumbnailPathPlugin.getOutputStream(path);
} else {
throw new IOException("You don't yet have access to this resource.");
}
}
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).getParent().resolve(".thumbnails"))

View File

@@ -9,8 +9,6 @@ import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
import org.jboss.resteasy.reactive.common.util.Encode;
public final class FileSystemPath implements Path { public final class FileSystemPath implements Path {
private java.nio.file.Path rootFolderPath; private java.nio.file.Path rootFolderPath;
@@ -29,16 +27,6 @@ public final class FileSystemPath implements Path {
this.absolutePath = getAbsolutePath(); this.absolutePath = getAbsolutePath();
} }
@Override
public String getURI() {
if (this.segments.isEmpty()) {
return "/";
}
return this.segments.stream()
.map(s -> "/" + Encode.encodePathSegment(s))
.reduce("", String::concat);
}
@Override @Override
public String getFilename() { public String getFilename() {
return this.segments.isEmpty() ? "/" : this.segments.getLast(); return this.segments.isEmpty() ? "/" : this.segments.getLast();

View File

@@ -5,7 +5,6 @@ import java.io.InputStream;
import java.util.List; import java.util.List;
sealed public interface Path permits FileSystemPath, ZipEntryPath { sealed public interface Path permits FileSystemPath, ZipEntryPath {
String getURI();
String getFilename(); String getFilename();
long getSize(); long getSize();
boolean exists(); boolean exists();

View File

@@ -1,5 +1,10 @@
package sh.rhiobet.lalafin.core.path.model; package sh.rhiobet.lalafin.core.path.model;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public abstract class PathAccessor { public abstract class PathAccessor {
protected java.nio.file.Path getAbsolutePath(Path path) { protected java.nio.file.Path getAbsolutePath(Path path) {
switch (path) { switch (path) {
@@ -10,4 +15,15 @@ public abstract class PathAccessor {
return zep.zipFilePath.absolutePath; return zep.zipFilePath.absolutePath;
} }
} }
protected List<String> getSegments(Path path) {
switch (path) {
case FileSystemPath fsp:
return Collections.unmodifiableList(fsp.segments);
case ZipEntryPath zep:
return Stream.concat(zep.zipFilePath.segments.stream(), zep.segments.stream())
.collect(Collectors.toUnmodifiableList());
}
}
} }

View File

@@ -1,28 +1,21 @@
package sh.rhiobet.lalafin.core.path.model; package sh.rhiobet.lalafin.core.path.model;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.PathSegment; import jakarta.ws.rs.core.PathSegment;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.List; import java.util.List;
import sh.rhiobet.lalafin.api.configuration.FileApiConfiguration;
import sh.rhiobet.lalafin.core.path.exception.InvalidPathException; import sh.rhiobet.lalafin.core.path.exception.InvalidPathException;
@ApplicationScoped @ApplicationScoped
public class PathFactory { public class PathFactory {
@Inject public Path toPath(List<PathSegment> names, java.nio.file.Path rootFolderPath)
FileApiConfiguration fileApiConfiguration; throws InvalidPathException {
public Path toPath(List<PathSegment> names) throws InvalidPathException {
if (hasEncodedPathSeparator(names)) { if (hasEncodedPathSeparator(names)) {
throw new InvalidPathException("Some path segments contain illegal characters."); throw new InvalidPathException("Some path segments contain illegal characters.");
} else { } else {
java.nio.file.Path rootFolderPath = Paths.get(this.fileApiConfiguration.directory());
int zipIndex = -1; int zipIndex = -1;
for (int i = 0; i < names.size(); i++) { for (int i = 0; i < names.size(); i++) {
if (names.get(i).getPath().endsWith(".zip")) { if (names.get(i).getPath().endsWith(".zip")) {

View File

@@ -10,8 +10,6 @@ import java.util.stream.Collectors;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
import org.jboss.resteasy.reactive.common.util.Encode;
public final class ZipEntryPath implements Path { public final class ZipEntryPath implements Path {
private long size = 0; private long size = 0;
private boolean exists = false; private boolean exists = false;
@@ -32,13 +30,6 @@ public final class ZipEntryPath implements Path {
this.entryName = entryName; this.entryName = entryName;
} }
@Override
public String getURI() {
return this.zipFilePath.getURI() + this.segments.stream()
.map(s -> "/" + Encode.encodePathSegment(s))
.reduce("", String::concat);
}
@Override @Override
public String getFilename() { public String getFilename() {
return this.segments.getLast(); return this.segments.getLast();

View File

@@ -1,12 +0,0 @@
package sh.rhiobet.lalafin.core.path.plugin;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Optional;
import sh.rhiobet.lalafin.core.path.model.Path;
public interface PathPlugin {
Optional<String> resolveURI(Path path);
OutputStream getOutputStream(Path path) throws IOException;
}

View File

@@ -0,0 +1,9 @@
package sh.rhiobet.lalafin.core.path.resolver;
import java.util.Optional;
import sh.rhiobet.lalafin.core.path.model.Path;
public interface PathURIResolver {
Optional<String> resolve(Path path);
}

View File

@@ -0,0 +1,9 @@
package sh.rhiobet.lalafin.core.thumbnail;
import java.io.IOException;
import sh.rhiobet.lalafin.core.path.model.Path;
public interface ThumbnailGenerator {
void generate(Path path) throws IOException;
}

View File

@@ -0,0 +1,41 @@
package sh.rhiobet.lalafin.core.thumbnail;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Optional;
import java.util.regex.Pattern;
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 {
@Override
protected java.nio.file.Path getAbsolutePath(Path path) {
try {
switch (path) {
case FileSystemPath fsp:
java.nio.file.Path thumbnailsFolder =
super.getAbsolutePath(path).getParent().resolve(".thumbnails");
Optional<java.nio.file.Path> foundPath = Optional.empty();
if (Files.exists(thumbnailsFolder)) {
foundPath = Files.list(thumbnailsFolder)
.filter(f -> f.getFileName().toString()
.matches("^" + Pattern.quote(path.getFilename()) + "(\\.[^.]+)*$"))
.findFirst();
}
return foundPath.isPresent() ?
foundPath.get() :
thumbnailsFolder.resolve(path.getFilename() + ".png");
case ZipEntryPath zep:
return null;
}
} catch (IOException ignored) {
return null;
}
}
}

View File

@@ -0,0 +1,42 @@
package sh.rhiobet.lalafin.core.util;
public class MimeType {
public static String getMimeType(String filename) {
String extension = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase();
switch (extension) {
case "3gp":
return "video/3gpp";
case "avi":
return "video/x-msvideo";
case "flac":
return "audio/x-flac";
case "flv":
return "video/x-flv";
case "html":
return "text/html";
case "jpg":
return "image/jpeg";
case "mkv":
return "video/x-matroska";
case "mp3":
return "audio/mp3";
case "mp4":
return "video/mp4";
case "png":
return "image/png";
case "ts":
return "video/MP2T";
case "wav":
return "audio/x-wav";
case "webm":
return "video/webm";
case "wmv":
return "video/x-ms-wmv";
case "zip":
return "application/zip";
default:
return "application/octet-stream";
}
}
}

View File

@@ -1,6 +1,7 @@
package sh.rhiobet.lalafin.file.access; package sh.rhiobet.lalafin.file.access;
import java.util.List; import java.util.List;
import java.util.Optional;
import io.quarkus.security.identity.SecurityIdentity; import io.quarkus.security.identity.SecurityIdentity;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
@@ -8,10 +9,11 @@ import jakarta.inject.Inject;
import jakarta.inject.Named; import jakarta.inject.Named;
import sh.rhiobet.lalafin.core.access.AccessService; import sh.rhiobet.lalafin.core.access.AccessService;
import sh.rhiobet.lalafin.core.path.model.Path; import sh.rhiobet.lalafin.core.path.model.Path;
import sh.rhiobet.lalafin.core.path.resolver.PathURIResolver;
import sh.rhiobet.lalafin.file.configuration.FileConfiguration; import sh.rhiobet.lalafin.file.configuration.FileConfiguration;
@ApplicationScoped @ApplicationScoped
@Named("file/role") @Named("file/access/role")
public class FileRoleAccessService implements AccessService { public class FileRoleAccessService implements AccessService {
@Inject @Inject
SecurityIdentity securityIdentity; SecurityIdentity securityIdentity;
@@ -19,13 +21,24 @@ public class FileRoleAccessService implements AccessService {
@Inject @Inject
FileConfiguration fileConfiguration; FileConfiguration fileConfiguration;
@Inject
@Named("file/resolver")
PathURIResolver fileURIResolver;
public boolean checkAccess(Path path) { public boolean checkAccess(Path path) {
String pathUri = path.getURI(); Optional<String> pathUriOptional = this.fileURIResolver.resolve(path);
String pathUri;
if (pathUriOptional.isEmpty()) {
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.startsWith(r.path()))
.toList(); .toList();
if (matchingRoutes.isEmpty()) { if (matchingRoutes.isEmpty()) {
return false; return false;
} }

View File

@@ -8,8 +8,8 @@ import jakarta.inject.Named;
import sh.rhiobet.lalafin.LalafinConfiguration; import sh.rhiobet.lalafin.LalafinConfiguration;
import sh.rhiobet.lalafin.api.internal.redis.FileTokenProvider; import sh.rhiobet.lalafin.api.internal.redis.FileTokenProvider;
import sh.rhiobet.lalafin.core.path.model.Path; import sh.rhiobet.lalafin.core.path.model.Path;
import sh.rhiobet.lalafin.core.path.plugin.PathPlugin; import sh.rhiobet.lalafin.core.path.resolver.PathURIResolver;
import sh.rhiobet.lalafin.file.ThumbnailService; import sh.rhiobet.lalafin.core.thumbnail.ThumbnailGenerator;
import sh.rhiobet.lalafin.file.configuration.FileConfiguration; import sh.rhiobet.lalafin.file.configuration.FileConfiguration;
@ApplicationScoped @ApplicationScoped
@@ -21,21 +21,36 @@ public class FileMetadataService {
FileConfiguration fileConfiguration; FileConfiguration fileConfiguration;
@Inject @Inject
ThumbnailService thumbnailService; @Named("file/resolver")
PathURIResolver fileURIResolver;
@Inject
@Named("file/resolver/thumbnail")
PathURIResolver fileThumbnailURIResolver;
@Inject @Inject
@Named("file/thumbnail") @Named("file/thumbnail")
PathPlugin thumbnailPathPlugin; ThumbnailGenerator fileThumbnailGenerator;
public FileInfoBase getInfo(Path filePath, FileTokenProvider fileTokenProvider) { public FileInfoBase getInfo(Path filePath, FileTokenProvider fileTokenProvider) {
if (filePath.exists()) { if (filePath.exists()) {
Optional<String> thumbUrl = this.thumbnailPathPlugin.resolveURI(filePath); Optional<String> fileUrl = this.fileURIResolver.resolve(filePath);
Optional<String> thumbUrl = this.fileThumbnailURIResolver.resolve(filePath);
if (thumbUrl.isEmpty()) {
try {
this.fileThumbnailGenerator.generate(filePath);
thumbUrl = this.fileThumbnailURIResolver.resolve(filePath);
} catch (IOException e) {
e.printStackTrace();
}
}
if (filePath.canHaveChildren()) { if (filePath.canHaveChildren()) {
FolderInfo folderInfo = new FolderInfo( FolderInfo folderInfo = new FolderInfo(
filePath.getFilename(), filePath.getFilename(),
thumbUrl.isPresent() ? "/file" + thumbUrl.get() : "", thumbUrl.isPresent() ? thumbUrl.get() : "",
"/file" + filePath.getURI(), fileUrl.isPresent() ? fileUrl.get() : "",
"" ""
); );
@@ -52,17 +67,29 @@ public class FileMetadataService {
} }
} }
Optional<String> childThumbUrl = this.thumbnailPathPlugin.resolveURI(child); Optional<String> childFileUrl = this.fileURIResolver.resolve(child);
Optional<String> childThumbUrl = this.fileThumbnailURIResolver.resolve(child);
if (childThumbUrl.isEmpty()) {
try {
this.fileThumbnailGenerator.generate(child);
childThumbUrl = this.fileThumbnailURIResolver.resolve(child);
} catch (IOException e) {
e.printStackTrace();
}
}
FileInfoBase contentInfo; FileInfoBase contentInfo;
if (child.canHaveChildren()) { if (child.canHaveChildren()) {
contentInfo = new FolderInfo(child.getFilename(), "/file" + child.getURI()); contentInfo = new FolderInfo(child.getFilename(),
childFileUrl.isPresent() ? childFileUrl.get() : "");
} else { } else {
contentInfo = new FileInfo(child.getFilename(), "/file" + child.getURI()); contentInfo = new FileInfo(child.getFilename(),
childFileUrl.isPresent() ? childFileUrl.get() : "");
} }
if (childThumbUrl.isPresent()) { if (childThumbUrl.isPresent()) {
contentInfo.thumbnailUrl = "/file" + childThumbUrl.get(); contentInfo.thumbnailUrl = childThumbUrl.get();
} }
folderInfo.content.add(contentInfo); folderInfo.content.add(contentInfo);
@@ -75,8 +102,8 @@ public class FileMetadataService {
} else { } else {
FileInfo fileInfo = new FileInfo( FileInfo fileInfo = new FileInfo(
filePath.getFilename(), filePath.getFilename(),
thumbUrl.isPresent() ? "/file" + thumbUrl.get() : "", thumbUrl.isPresent() ? thumbUrl.get() : "",
"/file" + filePath.getURI(), fileUrl.isPresent() ? fileUrl.get() : "",
"" ""
); );

View File

@@ -0,0 +1,34 @@
package sh.rhiobet.lalafin.file.resolver;
import java.util.List;
import java.util.Optional;
import org.jboss.resteasy.reactive.common.util.Encode;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import sh.rhiobet.lalafin.file.configuration.FileConfiguration;
import sh.rhiobet.lalafin.core.path.model.Path;
import sh.rhiobet.lalafin.core.path.model.PathAccessor;
import sh.rhiobet.lalafin.core.path.resolver.PathURIResolver;
@ApplicationScoped
@Named("file/resolver")
public class FilePathURIResolver extends PathAccessor implements PathURIResolver {
@Inject
FileConfiguration fileConfiguration;
@Override
public Optional<String> resolve(Path path) {
List<String> segments = getSegments(path);
if (segments.isEmpty()) {
return Optional.of("/file/");
}
return Optional.of(segments.stream()
.map(s -> "/" + Encode.encodePathSegment(s))
.reduce("/file", String::concat));
}
}

View File

@@ -0,0 +1,42 @@
package sh.rhiobet.lalafin.file.resolver;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Optional;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import sh.rhiobet.lalafin.file.configuration.FileConfiguration;
import sh.rhiobet.lalafin.core.path.model.FileSystemPath;
import sh.rhiobet.lalafin.core.path.model.Path;
import sh.rhiobet.lalafin.core.path.model.ZipEntryPath;
import sh.rhiobet.lalafin.core.path.resolver.PathURIResolver;
import sh.rhiobet.lalafin.core.thumbnail.ThumbnailPathAccessor;
@ApplicationScoped
@Named("file/resolver/thumbnail")
public class FileThumbnailPathURIResolver extends ThumbnailPathAccessor implements PathURIResolver {
@Inject
FileConfiguration fileConfiguration;
@Override
public Optional<String> resolve(Path path) {
switch (path) {
case FileSystemPath fsp:
java.nio.file.Path thumbnailAbsolutePath = getAbsolutePath(path);
if (thumbnailAbsolutePath != null && Files.exists(thumbnailAbsolutePath)) {
java.nio.file.Path rootFolderPath = Paths.get(this.fileConfiguration.directory());
return Optional.of(
"/file/" + rootFolderPath.resolve("file").relativize(thumbnailAbsolutePath)
.toString());
} else {
return Optional.empty();
}
case ZipEntryPath zep:
return Optional.empty();
}
}
}

View File

@@ -1,5 +1,6 @@
package sh.rhiobet.lalafin.file.rest; package sh.rhiobet.lalafin.file.rest;
import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -15,12 +16,16 @@ import io.quarkus.security.Authenticated;
import sh.rhiobet.lalafin.core.access.AccessService; import sh.rhiobet.lalafin.core.access.AccessService;
import sh.rhiobet.lalafin.core.path.exception.InvalidPathException; import sh.rhiobet.lalafin.core.path.exception.InvalidPathException;
import sh.rhiobet.lalafin.core.path.model.PathFactory; import sh.rhiobet.lalafin.core.path.model.PathFactory;
import sh.rhiobet.lalafin.file.configuration.FileConfiguration;
import sh.rhiobet.lalafin.file.model.FileInfoBase; import sh.rhiobet.lalafin.file.model.FileInfoBase;
import sh.rhiobet.lalafin.file.model.FileMetadataService; import sh.rhiobet.lalafin.file.model.FileMetadataService;
@Authenticated @Authenticated
@Path("/v1/api/private/file") @Path("/v1/api/private/file")
public class FilePrivateAPI { public class FilePrivateAPI {
@Inject
FileConfiguration fileConfiguration;
@Inject @Inject
FileMetadataService fileMetadataService; FileMetadataService fileMetadataService;
@@ -41,9 +46,11 @@ public class FilePrivateAPI {
@Path("/{names: .+}") @Path("/{names: .+}")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public Response getFileInfo(List<PathSegment> names) { public Response getFileInfo(List<PathSegment> names) {
java.nio.file.Path rootFolderPath = Paths.get(this.fileConfiguration.directory());
sh.rhiobet.lalafin.core.path.model.Path path; sh.rhiobet.lalafin.core.path.model.Path path;
try { try {
path = this.pathFactory.toPath(names); path = this.pathFactory.toPath(names, rootFolderPath);
} catch (InvalidPathException ignored) { } catch (InvalidPathException ignored) {
return Response.status(Response.Status.BAD_REQUEST).build(); return Response.status(Response.Status.BAD_REQUEST).build();
} }

View File

@@ -0,0 +1,68 @@
package sh.rhiobet.lalafin.file.thumbnail;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import org.im4java.core.ConvertCmd;
import org.im4java.core.IM4JavaException;
import org.im4java.core.IMOperation;
import org.im4java.process.Pipe;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import sh.rhiobet.lalafin.file.configuration.FileConfiguration;
import sh.rhiobet.lalafin.core.path.model.Path;
import sh.rhiobet.lalafin.core.thumbnail.ThumbnailGenerator;
import sh.rhiobet.lalafin.core.thumbnail.ThumbnailPathAccessor;
import sh.rhiobet.lalafin.core.util.MimeType;
@ApplicationScoped
@Named("file/thumbnail")
public class FileThumbnailGenerator extends ThumbnailPathAccessor implements ThumbnailGenerator {
@Inject
FileConfiguration fileConfiguration;
@Override
public void generate(Path path) throws IOException {
java.nio.file.Path thumbnailAbsolutePath = getAbsolutePath(path);
if (!path.canHaveChildren() || thumbnailAbsolutePath == null) {
return;
}
if (Files.exists(thumbnailAbsolutePath)) {
Files.delete(thumbnailAbsolutePath);
}
Files.createDirectories(thumbnailAbsolutePath.getParent());
for (Path childPath : path.getChildren()) {
String mimetype = MimeType.getMimeType(childPath.getFilename());
if (mimetype.startsWith("image/")) {
String extension = childPath.getFilename()
.substring(childPath.getFilename().lastIndexOf('.') + 1).toLowerCase();
ConvertCmd convert = new ConvertCmd();
convert.getCommand().clear();
convert.getCommand().push("magick");
IMOperation op = new IMOperation();
try (InputStream pathInputStream = childPath.getInputStream()) {
Pipe inputPipe = new Pipe(pathInputStream, null);
op.addImage(extension + ":-");
op.resize(200, null);
op.addImage(thumbnailAbsolutePath.toString());
convert.setInputProvider(inputPipe);
convert.run(op);
return;
} catch (InterruptedException | IM4JavaException e) {
throw new IOException("Error when generating thumbnail with IM4J.", e);
}
}
}
}
}

View File

@@ -1,74 +0,0 @@
package sh.rhiobet.lalafin.file.thumbnail;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.regex.Pattern;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import sh.rhiobet.lalafin.file.configuration.FileConfiguration;
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;
import sh.rhiobet.lalafin.core.path.plugin.PathPlugin;
@ApplicationScoped
@Named("file/thumbnail")
public class FileThumbnailPathPlugin extends PathAccessor implements PathPlugin {
@Inject
FileConfiguration fileConfiguration;
@Override
public Optional<String> resolveURI(Path path) {
switch (path) {
case FileSystemPath fsp:
Optional<java.nio.file.Path> thumbnailAbsolutePath = getThumbnailPath(path);
if (thumbnailAbsolutePath.isPresent()) {
java.nio.file.Path rootFolderPath = Paths.get(this.fileConfiguration.directory());
return Optional.of(
"/" + rootFolderPath.resolve("file").relativize(thumbnailAbsolutePath.get())
.toString());
} else {
return Optional.empty();
}
case ZipEntryPath zep:
return Optional.empty();
}
}
@Override
public OutputStream getOutputStream(Path path) throws IOException {
switch (path) {
case FileSystemPath fsp:
Optional<java.nio.file.Path> thumbnailAbsolutePath = getThumbnailPath(path);
if (thumbnailAbsolutePath.isPresent()) {
Files.delete(thumbnailAbsolutePath.get());
}
java.nio.file.Path newThumbnailAbsolutePath =
getAbsolutePath(path).getParent().resolve(".thumbnails").resolve(path.getFilename());
Files.createDirectories(newThumbnailAbsolutePath.getParent());
return Files.newOutputStream(newThumbnailAbsolutePath);
case ZipEntryPath zep:
throw new IOException("A zip file is not a valid location to store a thumbnail.");
}
}
private Optional<java.nio.file.Path> getThumbnailPath(Path path) {
try {
return Files.list(getAbsolutePath(path).getParent().resolve(".thumbnails"))
.filter(f -> f.getFileName().toString()
.matches("^" + Pattern.quote(path.getFilename()) + "(\\.[^.]+)*$"))
.findFirst();
} catch (IOException ignored) {
return Optional.empty();
}
}
}