Compare commits
7 Commits
8c0bb2ce4a
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a0c810214 | |||
| 1c6b127543 | |||
| a60df48774 | |||
| 23db8681d3 | |||
| c7ee90cbaa | |||
| 5e453dad51 | |||
| a4c0a4702a |
@@ -4,7 +4,7 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import io.smallrye.config.ConfigMapping;
|
import io.smallrye.config.ConfigMapping;
|
||||||
|
|
||||||
@ConfigMapping(prefix = "lalafin")
|
@ConfigMapping(prefix = "lalafin.core")
|
||||||
public interface LalafinConfiguration {
|
public interface LalafinConfiguration {
|
||||||
|
|
||||||
public String base_url();
|
public String base_url();
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
package sh.rhiobet.lalafin.access;
|
|
||||||
|
|
||||||
import sh.rhiobet.lalafin.path.Path;
|
|
||||||
|
|
||||||
public interface AccessService {
|
|
||||||
boolean checkAccess(Path path);
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package sh.rhiobet.lalafin.advent;
|
package sh.rhiobet.lalafin.advent.access;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -7,31 +7,39 @@ import java.util.regex.Pattern;
|
|||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.inject.Named;
|
import jakarta.inject.Named;
|
||||||
import sh.rhiobet.lalafin.access.AccessService;
|
import sh.rhiobet.lalafin.advent.configuration.AdventConfiguration;
|
||||||
import sh.rhiobet.lalafin.advent.AdventConfiguration.AdventEvent;
|
import sh.rhiobet.lalafin.core.access.AccessService;
|
||||||
import sh.rhiobet.lalafin.path.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> pathUri = this.fileURIResolver.resolve(path);
|
||||||
|
|
||||||
Optional<AdventEvent> matchingAdvent = adventConfiguration.events().stream()
|
if (pathUri.isEmpty()) {
|
||||||
.filter(e -> pathUri.startsWith(e.path()))
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<AdventConfiguration.AdventEvent> matchingAdvent = adventConfiguration.events().stream()
|
||||||
|
.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()) + "/?($1)\\.?[^.]*$",
|
"^" + Pattern.quote(matchingAdvent.get().path()) + "/?([^.]+)\\.?[^.]*$",
|
||||||
"$1");
|
"$1");
|
||||||
try {
|
try {
|
||||||
int day = Integer.parseInt(dayString);
|
int day = Integer.parseInt(dayString);
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package sh.rhiobet.lalafin.advent;
|
package sh.rhiobet.lalafin.advent.configuration;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import io.smallrye.config.ConfigMapping;
|
import io.smallrye.config.ConfigMapping;
|
||||||
|
|
||||||
@ConfigMapping(prefix = "advent")
|
@ConfigMapping(prefix = "lalafin.advent")
|
||||||
public interface AdventConfiguration {
|
public interface AdventConfiguration {
|
||||||
public List<AdventEvent> events();
|
public List<AdventEvent> events();
|
||||||
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package sh.rhiobet.lalafin.advent.resolver;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import jakarta.annotation.Priority;
|
||||||
|
import jakarta.decorator.Decorator;
|
||||||
|
import jakarta.decorator.Delegate;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.inject.Named;
|
||||||
|
import sh.rhiobet.lalafin.advent.access.AdventAccessService;
|
||||||
|
import sh.rhiobet.lalafin.core.path.model.Path;
|
||||||
|
import sh.rhiobet.lalafin.core.path.model.PathAccessor;
|
||||||
|
import sh.rhiobet.lalafin.core.path.resolver.PathURIResolver;
|
||||||
|
import sh.rhiobet.lalafin.file.configuration.FileConfiguration;
|
||||||
|
|
||||||
|
@Decorator
|
||||||
|
@Priority(0)
|
||||||
|
public class AdventThumbnailPathURIResolver extends PathAccessor implements PathURIResolver {
|
||||||
|
@Inject
|
||||||
|
FileConfiguration fileConfiguration;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@Named("file/resolver/thumbnail")
|
||||||
|
@Delegate
|
||||||
|
PathURIResolver thumbnailURIResolver;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
AdventAccessService adventAccessService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<String> resolve(Path path) {
|
||||||
|
if (this.adventAccessService.checkAccess(path)) {
|
||||||
|
return this.thumbnailURIResolver.resolve(path);
|
||||||
|
} else {
|
||||||
|
Optional<java.nio.file.Path> thumbnailAbsolutePath = getDefaultThumbnailPath(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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<java.nio.file.Path> getDefaultThumbnailPath(Path path) {
|
||||||
|
try {
|
||||||
|
return Files.list(getAbsolutePath(path).get().getParent().resolve(".thumbnails"))
|
||||||
|
.filter(f -> f.getFileName().toString().matches("^00(\\.[^.]+)*$"))
|
||||||
|
.findFirst();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package sh.rhiobet.lalafin.core.access;
|
||||||
|
|
||||||
|
import sh.rhiobet.lalafin.core.path.model.Path;
|
||||||
|
|
||||||
|
public interface AccessService {
|
||||||
|
boolean checkAccess(Path path);
|
||||||
|
}
|
||||||
28
src/main/java/sh/rhiobet/lalafin/core/cache/Cache.java
vendored
Normal file
28
src/main/java/sh/rhiobet/lalafin/core/cache/Cache.java
vendored
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/main/java/sh/rhiobet/lalafin/core/cache/ThrowingFunction.java
vendored
Normal file
8
src/main/java/sh/rhiobet/lalafin/core/cache/ThrowingFunction.java
vendored
Normal 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;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package sh.rhiobet.lalafin.path;
|
package sh.rhiobet.lalafin.core.path.exception;
|
||||||
|
|
||||||
public class InvalidPathException extends Exception {
|
public class InvalidPathException extends Exception {
|
||||||
public InvalidPathException(String message) {
|
public InvalidPathException(String message) {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package sh.rhiobet.lalafin.path;
|
package sh.rhiobet.lalafin.core.path.model;
|
||||||
|
|
||||||
import jakarta.ws.rs.core.PathSegment;
|
import jakarta.ws.rs.core.PathSegment;
|
||||||
|
|
||||||
@@ -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,25 +27,18 @@ public final class FileSystemPath implements Path {
|
|||||||
this.absolutePath = getAbsolutePath();
|
this.absolutePath = getAbsolutePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getURI() {
|
|
||||||
return 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.isEmpty() ? "/" : this.segments.getLast();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getSize() {
|
public long getSize() {
|
||||||
try {
|
try {
|
||||||
return Files.size(this.absolutePath);
|
return Files.size(this.absolutePath);
|
||||||
} catch (IOException ignored) {
|
} catch (IOException ignored) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -62,7 +53,7 @@ public final class FileSystemPath implements Path {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Path> getChildren() throws IOException {
|
public List<Path> getChildren() throws IOException {
|
||||||
if (this.segments.getLast().endsWith(".zip")) {
|
if (!this.segments.isEmpty() && this.segments.getLast().endsWith(".zip")) {
|
||||||
try (ZipFile zipFile = new ZipFile(this.absolutePath.toFile())) {
|
try (ZipFile zipFile = new ZipFile(this.absolutePath.toFile())) {
|
||||||
return zipFile.stream()
|
return zipFile.stream()
|
||||||
.map(e -> (Path) new ZipEntryPath(this, e.getName()))
|
.map(e -> (Path) new ZipEntryPath(this, e.getName()))
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
package sh.rhiobet.lalafin.path;
|
package sh.rhiobet.lalafin.core.path.model;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
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();
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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 Optional<java.nio.file.Path> getAbsolutePath(Path path) {
|
||||||
|
switch (path) {
|
||||||
|
case FileSystemPath fsp:
|
||||||
|
return Optional.of(fsp.absolutePath);
|
||||||
|
|
||||||
|
case ZipEntryPath zep:
|
||||||
|
return Optional.of(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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,27 +1,21 @@
|
|||||||
package sh.rhiobet.lalafin.path;
|
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;
|
||||||
|
|
||||||
@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")) {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package sh.rhiobet.lalafin.path;
|
package sh.rhiobet.lalafin.core.path.model;
|
||||||
|
|
||||||
import jakarta.ws.rs.core.PathSegment;
|
import jakarta.ws.rs.core.PathSegment;
|
||||||
|
|
||||||
@@ -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();
|
||||||
@@ -78,6 +69,7 @@ public final class ZipEntryPath implements Path {
|
|||||||
if (zipEntry != null) {
|
if (zipEntry != null) {
|
||||||
return new ZipEntryInputStream(zipFile, zipFile.getInputStream(zipEntry));
|
return new ZipEntryInputStream(zipFile, zipFile.getInputStream(zipEntry));
|
||||||
} else {
|
} else {
|
||||||
|
zipFile.close();
|
||||||
throw new IOException("Zip entry does not exist.");
|
throw new IOException("Zip entry does not exist.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
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.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 Optional<java.nio.file.Path> getAbsolutePath(Path path) {
|
||||||
|
try {
|
||||||
|
switch (path) {
|
||||||
|
case FileSystemPath fsp:
|
||||||
|
java.nio.file.Path thumbnailsFolder = getThumbnailRootPath(path);
|
||||||
|
|
||||||
|
Map<String, java.nio.file.Path> 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 Optional.of(thumbnails.getOrDefault(path.getFilename(),
|
||||||
|
thumbnailsFolder.resolve(path.getFilename() + ".png")));
|
||||||
|
|
||||||
|
case ZipEntryPath zep:
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected java.nio.file.Path getThumbnailRootPath(Path path) {
|
||||||
|
return super.getAbsolutePath(path).get().getParent().resolve(".thumbnails");
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/main/java/sh/rhiobet/lalafin/core/util/MimeType.java
Normal file
42
src/main/java/sh/rhiobet/lalafin/core/util/MimeType.java
Normal 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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package sh.rhiobet.lalafin.file;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import io.quarkus.security.identity.SecurityIdentity;
|
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
|
||||||
import jakarta.inject.Inject;
|
|
||||||
import jakarta.inject.Named;
|
|
||||||
import sh.rhiobet.lalafin.access.AccessService;
|
|
||||||
import sh.rhiobet.lalafin.api.configuration.FileApiConfiguration;
|
|
||||||
import sh.rhiobet.lalafin.api.configuration.FileApiConfiguration.Route;
|
|
||||||
import sh.rhiobet.lalafin.path.Path;
|
|
||||||
|
|
||||||
@ApplicationScoped
|
|
||||||
@Named("file/role")
|
|
||||||
public class FileRoleAccessService implements AccessService {
|
|
||||||
@Inject
|
|
||||||
SecurityIdentity securityIdentity;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
FileApiConfiguration fileApiConfiguration;
|
|
||||||
|
|
||||||
public boolean checkAccess(Path path) {
|
|
||||||
String pathUri = path.getURI();
|
|
||||||
List<Route> matchingRoutes = fileApiConfiguration.routes().stream()
|
|
||||||
.filter(r -> pathUri.startsWith(r.path()))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
|
|
||||||
if (matchingRoutes.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return matchingRoutes.stream()
|
|
||||||
.allMatch(r -> r.roles().isEmpty()
|
|
||||||
|| this.securityIdentity.getRoles().containsAll(r.roles().get()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package sh.rhiobet.lalafin.file.access;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import io.quarkus.security.identity.SecurityIdentity;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.inject.Named;
|
||||||
|
import sh.rhiobet.lalafin.core.access.AccessService;
|
||||||
|
import sh.rhiobet.lalafin.core.path.model.Path;
|
||||||
|
import sh.rhiobet.lalafin.core.path.resolver.PathURIResolver;
|
||||||
|
import sh.rhiobet.lalafin.file.configuration.FileConfiguration;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
@Named("file/access/role")
|
||||||
|
public class FileRoleAccessService implements AccessService {
|
||||||
|
@Inject
|
||||||
|
SecurityIdentity securityIdentity;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FileConfiguration fileConfiguration;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@Named("file/resolver")
|
||||||
|
PathURIResolver fileURIResolver;
|
||||||
|
|
||||||
|
public boolean checkAccess(Path path) {
|
||||||
|
Optional<String> pathUri = this.fileURIResolver.resolve(path);
|
||||||
|
|
||||||
|
if (pathUri.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<FileConfiguration.Route> matchingRoutes = fileConfiguration.routes().stream()
|
||||||
|
.filter(r -> pathUri.get().startsWith(r.path()))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (matchingRoutes.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchingRoutes.stream()
|
||||||
|
.allMatch(r -> r.roles().isEmpty()
|
||||||
|
|| this.securityIdentity.getRoles().containsAll(r.roles().get()));
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/main/java/sh/rhiobet/lalafin/file/access/FileToken.java
Normal file
20
src/main/java/sh/rhiobet/lalafin/file/access/FileToken.java
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package sh.rhiobet.lalafin.file.access;
|
||||||
|
|
||||||
|
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||||
|
|
||||||
|
@RegisterForReflection
|
||||||
|
public class FileToken {
|
||||||
|
public String user;
|
||||||
|
public long timestamp;
|
||||||
|
public String ip;
|
||||||
|
public String file;
|
||||||
|
|
||||||
|
public FileToken() {}
|
||||||
|
|
||||||
|
public FileToken(String user, long timestamp, String ip, String file) {
|
||||||
|
this.user = user;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.ip = ip;
|
||||||
|
this.file = file;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package sh.rhiobet.lalafin.file.access;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import io.quarkus.redis.datasource.RedisDataSource;
|
||||||
|
import io.quarkus.redis.datasource.value.SetArgs;
|
||||||
|
import io.quarkus.security.identity.SecurityIdentity;
|
||||||
|
import io.vertx.core.http.HttpServerRequest;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
public class FileTokenProvider {
|
||||||
|
@Inject
|
||||||
|
RedisDataSource redisDataSource;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
SecurityIdentity securityIdentity;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
HttpServerRequest request;
|
||||||
|
|
||||||
|
public String getFileToken(String fileUri) {
|
||||||
|
FileToken token = new FileToken(this.securityIdentity.getPrincipal().getName(),
|
||||||
|
System.currentTimeMillis(), this.request.remoteAddress().host(), fileUri);
|
||||||
|
|
||||||
|
String uniqueID = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
this.redisDataSource.value(FileToken.class).set(
|
||||||
|
"fileToken-" + uniqueID, token, new SetArgs().pxAt(System.currentTimeMillis() + 86400000));
|
||||||
|
|
||||||
|
return uniqueID;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package sh.rhiobet.lalafin.file;
|
package sh.rhiobet.lalafin.file.configuration;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import io.smallrye.config.ConfigMapping;
|
import io.smallrye.config.ConfigMapping;
|
||||||
|
|
||||||
@ConfigMapping(prefix = "file")
|
@ConfigMapping(prefix = "lalafin.file")
|
||||||
public interface FileConfiguration {
|
public interface FileConfiguration {
|
||||||
public String directory();
|
public String directory();
|
||||||
public Optional<List<String>> ignored();
|
public Optional<List<String>> ignored();
|
||||||
17
src/main/java/sh/rhiobet/lalafin/file/model/FileInfo.java
Normal file
17
src/main/java/sh/rhiobet/lalafin/file/model/FileInfo.java
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package sh.rhiobet.lalafin.file.model;
|
||||||
|
|
||||||
|
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||||
|
|
||||||
|
@RegisterForReflection
|
||||||
|
public class FileInfo extends FileInfoBase {
|
||||||
|
public String publicApiUrl;
|
||||||
|
|
||||||
|
public FileInfo(String filename, String thumbnailUrl, String directUrl, String publicApiUrl) {
|
||||||
|
super(filename, "file", thumbnailUrl, directUrl);
|
||||||
|
this.publicApiUrl = publicApiUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileInfo(String filename, String directUrl, String publicApiUrl) {
|
||||||
|
this(filename, "", directUrl, publicApiUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package sh.rhiobet.lalafin.file.model;
|
||||||
|
|
||||||
|
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||||
|
|
||||||
|
@RegisterForReflection
|
||||||
|
public abstract class FileInfoBase implements Comparable<FileInfoBase> {
|
||||||
|
public String filename;
|
||||||
|
public String type;
|
||||||
|
public String thumbnailUrl;
|
||||||
|
public String directUrl;
|
||||||
|
public String viewUrl;
|
||||||
|
|
||||||
|
public FileInfoBase(String filename, String type, String thumbnailUrl, String directUrl,
|
||||||
|
String viewUrl) {
|
||||||
|
this.filename = filename;
|
||||||
|
this.type = type;
|
||||||
|
this.thumbnailUrl = thumbnailUrl;
|
||||||
|
this.directUrl = directUrl;
|
||||||
|
this.viewUrl = viewUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileInfoBase(String filename, String type, String thumbnailUrl, String directUrl) {
|
||||||
|
this(filename, type, thumbnailUrl, directUrl, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(FileInfoBase f) {
|
||||||
|
return this.filename.compareToIgnoreCase(f.filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package sh.rhiobet.lalafin.file.model;
|
||||||
|
|
||||||
|
import io.quarkus.logging.Log;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Optional;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.inject.Named;
|
||||||
|
import sh.rhiobet.lalafin.LalafinConfiguration;
|
||||||
|
import sh.rhiobet.lalafin.core.path.model.Path;
|
||||||
|
import sh.rhiobet.lalafin.core.path.resolver.PathURIResolver;
|
||||||
|
import sh.rhiobet.lalafin.core.thumbnail.ThumbnailGenerator;
|
||||||
|
import sh.rhiobet.lalafin.file.configuration.FileConfiguration;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
public class FileMetadataService {
|
||||||
|
@Inject
|
||||||
|
LalafinConfiguration lalafinConfiguration;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FileConfiguration fileConfiguration;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@Named("file/resolver")
|
||||||
|
PathURIResolver fileURIResolver;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@Named("file/resolver/thumbnail")
|
||||||
|
PathURIResolver fileThumbnailURIResolver;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@Named("file/resolver/token")
|
||||||
|
PathURIResolver fileTokenURIResolver;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@Named("file/thumbnail")
|
||||||
|
ThumbnailGenerator fileThumbnailGenerator;
|
||||||
|
|
||||||
|
public FileInfoBase getInfo(Path filePath) {
|
||||||
|
if (filePath.exists()) {
|
||||||
|
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) {
|
||||||
|
Log.warn("Failed to generate thumbnail for " + filePath + ".", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filePath.canHaveChildren()) {
|
||||||
|
FolderInfo folderInfo = new FolderInfo(
|
||||||
|
filePath.getFilename(),
|
||||||
|
thumbUrl.isPresent() ? thumbUrl.get() : "",
|
||||||
|
fileUrl.isPresent() ? fileUrl.get() : "",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (Path child : filePath.getChildren()) {
|
||||||
|
if (child.getFilename().startsWith(".") ||
|
||||||
|
fileConfiguration.ignored().isPresent() &&
|
||||||
|
fileConfiguration.ignored().get().stream()
|
||||||
|
.anyMatch(i -> child.getFilename().endsWith(i)))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
Log.warn("Failed to generate thumbnail for " + child + ".", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileInfoBase contentInfo;
|
||||||
|
if (child.canHaveChildren()) {
|
||||||
|
contentInfo = new FolderInfo(child.getFilename(),
|
||||||
|
childFileUrl.isPresent() ? childFileUrl.get() : "");
|
||||||
|
} else {
|
||||||
|
Optional<String> childTokenUrl = this.fileTokenURIResolver.resolve(child);
|
||||||
|
contentInfo = new FileInfo(
|
||||||
|
child.getFilename(),
|
||||||
|
childFileUrl.isPresent() ? childFileUrl.get() : "",
|
||||||
|
childTokenUrl.isPresent() ? childTokenUrl.get() : ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (childThumbUrl.isPresent()) {
|
||||||
|
contentInfo.thumbnailUrl = childThumbUrl.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
folderInfo.content.add(contentInfo);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.warn("Failed to walk through children of " + filePath + ".", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return folderInfo;
|
||||||
|
} else {
|
||||||
|
Optional<String> tokenUrl = this.fileTokenURIResolver.resolve(filePath);
|
||||||
|
FileInfo fileInfo = new FileInfo(
|
||||||
|
filePath.getFilename(),
|
||||||
|
thumbUrl.isPresent() ? thumbUrl.get() : "",
|
||||||
|
fileUrl.isPresent() ? fileUrl.get() : "",
|
||||||
|
tokenUrl.isPresent() ? tokenUrl.get() : ""
|
||||||
|
);
|
||||||
|
|
||||||
|
return fileInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/main/java/sh/rhiobet/lalafin/file/model/FolderInfo.java
Normal file
22
src/main/java/sh/rhiobet/lalafin/file/model/FolderInfo.java
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package sh.rhiobet.lalafin.file.model;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||||
|
|
||||||
|
@RegisterForReflection
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class FolderInfo extends FileInfoBase {
|
||||||
|
public Set<FileInfoBase> content;
|
||||||
|
public FileInfo playlist;
|
||||||
|
|
||||||
|
public FolderInfo(String filename, String thumbnailUrl, String directUrl, String viewUrl) {
|
||||||
|
super(filename, "folder", thumbnailUrl, directUrl, viewUrl);
|
||||||
|
this.content = new TreeSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public FolderInfo(String filename, String directUrl) {
|
||||||
|
this(filename, "", directUrl, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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);
|
||||||
|
|
||||||
|
return Optional.of(segments.stream()
|
||||||
|
.map(s -> "/" + Encode.encodePathSegment(s))
|
||||||
|
.reduce("/file", String::concat));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package sh.rhiobet.lalafin.file.resolver;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
|
||||||
|
@RequestScoped
|
||||||
|
@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:
|
||||||
|
Optional<java.nio.file.Path> thumbnailAbsolutePath = getAbsolutePath(path);
|
||||||
|
|
||||||
|
if (thumbnailAbsolutePath.isPresent() && Files.exists(thumbnailAbsolutePath.get())) {
|
||||||
|
java.nio.file.Path rootFolderPath = Paths.get(this.fileConfiguration.directory());
|
||||||
|
return Optional.of("/file/" + Encode.encodePath(
|
||||||
|
rootFolderPath.resolve("file").relativize(thumbnailAbsolutePath.get()).toString()));
|
||||||
|
} else {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
case ZipEntryPath zep:
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package sh.rhiobet.lalafin.file.resolver;
|
||||||
|
|
||||||
|
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.core.path.model.Path;
|
||||||
|
import sh.rhiobet.lalafin.core.path.resolver.PathURIResolver;
|
||||||
|
import sh.rhiobet.lalafin.file.access.FileTokenProvider;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
@Named("file/resolver/token")
|
||||||
|
public class FileTokenURIResolver implements PathURIResolver {
|
||||||
|
@Inject
|
||||||
|
@Named("file/resolver")
|
||||||
|
PathURIResolver fileURIResolver;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FileTokenProvider fileTokenProvider;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<String> resolve(Path path) {
|
||||||
|
Optional<String> fileURI = this.fileURIResolver.resolve(path);
|
||||||
|
|
||||||
|
if (fileURI.isPresent()) {
|
||||||
|
return Optional.of(
|
||||||
|
"/v1/api/public/file/token/" + this.fileTokenProvider.getFileToken(fileURI.get()) + "/"
|
||||||
|
+ Encode.encodePathSegment(path.getFilename()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package sh.rhiobet.lalafin.file.rest;
|
||||||
|
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import jakarta.enterprise.inject.Instance;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.ws.rs.GET;
|
||||||
|
import jakarta.ws.rs.Path;
|
||||||
|
import jakarta.ws.rs.Produces;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import jakarta.ws.rs.core.PathSegment;
|
||||||
|
import jakarta.ws.rs.core.Response;
|
||||||
|
import io.quarkus.security.Authenticated;
|
||||||
|
import sh.rhiobet.lalafin.core.access.AccessService;
|
||||||
|
import sh.rhiobet.lalafin.core.path.exception.InvalidPathException;
|
||||||
|
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.FileMetadataService;
|
||||||
|
|
||||||
|
@Authenticated
|
||||||
|
@Path("/v1/api/private/file")
|
||||||
|
public class FilePrivateAPI {
|
||||||
|
@Inject
|
||||||
|
FileConfiguration fileConfiguration;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FileMetadataService fileMetadataService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
Instance<AccessService> accessServices;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
PathFactory pathFactory;
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
public Response getFileInfo() {
|
||||||
|
return this.getFileInfo(new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/{names: .+}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
public Response getFileInfo(List<PathSegment> names) {
|
||||||
|
java.nio.file.Path rootFolderPath = Paths.get(this.fileConfiguration.directory());
|
||||||
|
|
||||||
|
sh.rhiobet.lalafin.core.path.model.Path path;
|
||||||
|
try {
|
||||||
|
path = this.pathFactory.toPath(names, rootFolderPath);
|
||||||
|
} catch (InvalidPathException ignored) {
|
||||||
|
return Response.status(Response.Status.BAD_REQUEST).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accessServices.stream().anyMatch((a -> !a.checkAccess(path)))) {
|
||||||
|
return Response.status(Response.Status.FORBIDDEN).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
FileInfoBase fileInfo = this.fileMetadataService.getInfo(path);
|
||||||
|
|
||||||
|
if (fileInfo == null) {
|
||||||
|
return Response.status(Response.Status.NOT_FOUND).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.ok(fileInfo).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
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;
|
||||||
|
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 {
|
||||||
|
Optional<java.nio.file.Path> thumbnailAbsolutePath = getAbsolutePath(path);
|
||||||
|
|
||||||
|
if (!path.canHaveChildren() || thumbnailAbsolutePath.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Files.exists(thumbnailAbsolutePath.get())) {
|
||||||
|
Files.delete(thumbnailAbsolutePath.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
Files.createDirectories(thumbnailAbsolutePath.get().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.get().toString());
|
||||||
|
convert.setInputProvider(inputPipe);
|
||||||
|
convert.run(op);
|
||||||
|
|
||||||
|
Map<String, java.nio.file.Path> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package sh.rhiobet.lalafin.path;
|
|
||||||
|
|
||||||
public abstract class PathAccessor {
|
|
||||||
protected java.nio.file.Path getAbsolutePath(Path path) {
|
|
||||||
switch (path) {
|
|
||||||
case FileSystemPath fsp:
|
|
||||||
return fsp.absolutePath;
|
|
||||||
|
|
||||||
case ZipEntryPath zep:
|
|
||||||
return zep.zipFilePath.absolutePath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package sh.rhiobet.lalafin.path;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public interface PathPlugin {
|
|
||||||
Optional<String> resolveURI(Path path);
|
|
||||||
OutputStream getOutputStream(Path path) throws IOException;
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
package sh.rhiobet.lalafin.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.api.configuration.FileApiConfiguration;
|
|
||||||
import sh.rhiobet.lalafin.path.FileSystemPath;
|
|
||||||
import sh.rhiobet.lalafin.path.Path;
|
|
||||||
import sh.rhiobet.lalafin.path.PathAccessor;
|
|
||||||
import sh.rhiobet.lalafin.path.PathPlugin;
|
|
||||||
import sh.rhiobet.lalafin.path.ZipEntryPath;
|
|
||||||
|
|
||||||
@ApplicationScoped
|
|
||||||
@Named("thumbnail")
|
|
||||||
public class ThumbnailPathPlugin extends PathAccessor implements PathPlugin {
|
|
||||||
@Inject
|
|
||||||
FileApiConfiguration fileApiConfiguration;
|
|
||||||
|
|
||||||
@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.fileApiConfiguration.directory());
|
|
||||||
return Optional.of(rootFolderPath.resolve("file").relativize(thumbnailAbsolutePath.get())
|
|
||||||
.toUri().getRawPath());
|
|
||||||
} 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user