Create path package
This commit is contained in:
88
src/main/java/sh/rhiobet/lalafin/path/FileSystemPath.java
Normal file
88
src/main/java/sh/rhiobet/lalafin/path/FileSystemPath.java
Normal file
@@ -0,0 +1,88 @@
|
||||
package sh.rhiobet.lalafin.path;
|
||||
|
||||
import jakarta.ws.rs.core.PathSegment;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import org.jboss.resteasy.reactive.common.util.Encode;
|
||||
|
||||
public final class FileSystemPath implements Path {
|
||||
private java.nio.file.Path rootFolderPath;
|
||||
|
||||
List<String> segments;
|
||||
java.nio.file.Path absolutePath;
|
||||
|
||||
FileSystemPath(List<PathSegment> names, java.nio.file.Path rootFolderPath) {
|
||||
this.segments = names.stream().map(n -> n.getPath()).toList();
|
||||
this.rootFolderPath = rootFolderPath;
|
||||
this.absolutePath = getAbsolutePath();
|
||||
}
|
||||
|
||||
FileSystemPath(FileSystemPath parent, String fileName) {
|
||||
this.segments = Stream.concat(parent.segments.stream(), Stream.of(fileName)).toList();
|
||||
this.rootFolderPath = parent.rootFolderPath;
|
||||
this.absolutePath = getAbsolutePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getURI() {
|
||||
return this.segments.stream()
|
||||
.map(s -> "/" + Encode.encodePathSegment(s))
|
||||
.reduce("", String::concat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilename() {
|
||||
return this.segments.getLast();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize() {
|
||||
try {
|
||||
return Files.size(this.absolutePath);
|
||||
} catch (IOException ignored) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return Files.exists(this.absolutePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canHaveChildren() {
|
||||
return Files.isDirectory(this.absolutePath) || this.segments.getLast().endsWith(".zip");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Path> getChildren() throws IOException {
|
||||
if (this.segments.getLast().endsWith(".zip")) {
|
||||
try (ZipFile zipFile = new ZipFile(this.absolutePath.toFile())) {
|
||||
return zipFile.stream()
|
||||
.map(e -> (Path) new ZipEntryPath(this, e.getName()))
|
||||
.toList();
|
||||
}
|
||||
} else if (Files.isDirectory(this.absolutePath)){
|
||||
return Files.list(this.absolutePath).sorted()
|
||||
.map(f -> (Path) new FileSystemPath(this, f.getFileName().toString()))
|
||||
.toList();
|
||||
} else {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return Files.newInputStream(this.absolutePath);
|
||||
}
|
||||
|
||||
private java.nio.file.Path getAbsolutePath() {
|
||||
return this.rootFolderPath.resolve("file").resolve(String.join("/", this.segments));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package sh.rhiobet.lalafin.path;
|
||||
|
||||
public class InvalidPathException extends Exception {
|
||||
public InvalidPathException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
15
src/main/java/sh/rhiobet/lalafin/path/Path.java
Normal file
15
src/main/java/sh/rhiobet/lalafin/path/Path.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package sh.rhiobet.lalafin.path;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
sealed public interface Path permits FileSystemPath, ZipEntryPath {
|
||||
String getURI();
|
||||
String getFilename();
|
||||
long getSize();
|
||||
boolean exists();
|
||||
boolean canHaveChildren();
|
||||
List<Path> getChildren() throws IOException;
|
||||
InputStream getInputStream() throws IOException;
|
||||
}
|
||||
13
src/main/java/sh/rhiobet/lalafin/path/PathAccessor.java
Normal file
13
src/main/java/sh/rhiobet/lalafin/path/PathAccessor.java
Normal file
@@ -0,0 +1,13 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/main/java/sh/rhiobet/lalafin/path/PathFactory.java
Normal file
44
src/main/java/sh/rhiobet/lalafin/path/PathFactory.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package sh.rhiobet.lalafin.path;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.core.PathSegment;
|
||||
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
import sh.rhiobet.lalafin.api.configuration.FileApiConfiguration;
|
||||
|
||||
@ApplicationScoped
|
||||
public class PathFactory {
|
||||
@Inject
|
||||
FileApiConfiguration fileApiConfiguration;
|
||||
|
||||
public Path toPath(List<PathSegment> names) throws InvalidPathException {
|
||||
if (hasEncodedPathSeparator(names)) {
|
||||
throw new InvalidPathException("Some path segments contain illegal characters.");
|
||||
} else {
|
||||
java.nio.file.Path rootFolderPath = Paths.get(this.fileApiConfiguration.directory());
|
||||
|
||||
if (names.size() > 1 && names.get(names.size() - 2).getPath().endsWith(".zip")) {
|
||||
return new ZipEntryPath(names, rootFolderPath);
|
||||
} else {
|
||||
return new FileSystemPath(names, rootFolderPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasEncodedPathSeparator(List<PathSegment> names) {
|
||||
return names.stream().anyMatch(s -> {
|
||||
String current = s.getPath();
|
||||
while (true) {
|
||||
String decoded = URLDecoder.decode(current, StandardCharsets.UTF_8);
|
||||
if (decoded.equals(current)) break;
|
||||
current = decoded;
|
||||
}
|
||||
return current.contains("/") || current.contains("\0") || current.equals("..") || current.equals(".");
|
||||
});
|
||||
}
|
||||
}
|
||||
10
src/main/java/sh/rhiobet/lalafin/path/PathPlugin.java
Normal file
10
src/main/java/sh/rhiobet/lalafin/path/PathPlugin.java
Normal file
@@ -0,0 +1,10 @@
|
||||
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;
|
||||
}
|
||||
132
src/main/java/sh/rhiobet/lalafin/path/ZipEntryPath.java
Normal file
132
src/main/java/sh/rhiobet/lalafin/path/ZipEntryPath.java
Normal file
@@ -0,0 +1,132 @@
|
||||
package sh.rhiobet.lalafin.path;
|
||||
|
||||
import jakarta.ws.rs.core.PathSegment;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import org.jboss.resteasy.reactive.common.util.Encode;
|
||||
|
||||
public final class ZipEntryPath implements Path {
|
||||
FileSystemPath zipFilePath;
|
||||
List<String> segments;
|
||||
|
||||
ZipEntryPath(List<PathSegment> names, java.nio.file.Path rootFolderPath) {
|
||||
this(new FileSystemPath(names.subList(0, names.size() - 1), rootFolderPath),
|
||||
names.getLast().getPath());
|
||||
}
|
||||
|
||||
ZipEntryPath(FileSystemPath parent, String entryName) {
|
||||
this.segments = Stream.concat(parent.segments.stream(), Stream.of(entryName)).toList();
|
||||
this.zipFilePath = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getURI() {
|
||||
return this.segments.stream()
|
||||
.map(s -> "/" + Encode.encodePathSegment(s))
|
||||
.reduce("", String::concat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilename() {
|
||||
return this.segments.getLast();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize() {
|
||||
try (ZipFile zipFile = new ZipFile(this.zipFilePath.absolutePath.toFile())) {
|
||||
return zipFile.getEntry(this.segments.getLast()).getSize();
|
||||
} catch (IOException ignored) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
try (ZipFile zipFile = new ZipFile(this.zipFilePath.absolutePath.toFile())) {
|
||||
return zipFile.getEntry(segments.getLast()) != null;
|
||||
} catch (IOException ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canHaveChildren() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Path> getChildren() throws IOException {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
ZipFile zipFile = new ZipFile(this.zipFilePath.absolutePath.toFile());
|
||||
return new ZipEntryInputStream(zipFile,
|
||||
zipFile.getInputStream(zipFile.getEntry(this.segments.getLast())));
|
||||
}
|
||||
}
|
||||
|
||||
class ZipEntryInputStream extends InputStream {
|
||||
private final ZipFile zipFile;
|
||||
private final InputStream is;
|
||||
|
||||
public ZipEntryInputStream(ZipFile zipFile, InputStream is) {
|
||||
this.zipFile = zipFile;
|
||||
this.is = is;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return this.is.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buf) throws IOException {
|
||||
return this.is.read(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buf, int off, int len) throws IOException {
|
||||
return this.is.read(buf, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] readAllBytes() throws IOException {
|
||||
return this.is.readAllBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] readNBytes(int len) throws IOException {
|
||||
return this.is.readNBytes(len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readNBytes(byte[] buf, int off, int len) throws IOException {
|
||||
return this.is.readNBytes(buf, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long n) throws IOException {
|
||||
return this.is.skip(n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return this.is.available();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
this.is.close();
|
||||
} finally {
|
||||
this.zipFile.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
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 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("^" + path.getFilename() + "(\\.[^.]+)*$"))
|
||||
.findFirst();
|
||||
} catch (IOException ignored) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user