diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/Vfs.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/Vfs.java new file mode 100644 index 00000000..992ca4fb --- /dev/null +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/Vfs.java @@ -0,0 +1,85 @@ +package pro.gravit.launcher.base.vfs; + + +import pro.gravit.launcher.base.vfs.directory.SimpleVfsDirectory; +import pro.gravit.launcher.base.vfs.protocol.vfs.VfsURLStreamHandlerProvider; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +public class Vfs { + private final VfsDirectory directory; + private static final Map map = new HashMap<>(); + private static final Vfs defaultImpl = new Vfs(); + static { + URL.setURLStreamHandlerFactory(new VfsURLStreamHandlerProvider()); + register("", defaultImpl); + } + + public Vfs() { + directory = new SimpleVfsDirectory(); + } + + public Vfs(VfsDirectory directory) { + this.directory = directory; + } + + public VfsEntry resolve(Path path) { + return directory.resolve(path); + } + + public void put(Path path, VfsEntry entry) { + VfsEntry parent = resolve(path.getParent()); + if(parent instanceof SimpleVfsDirectory dir) { + dir.put(path.getFileName().toString(), entry); + } else { + throw new VfsException(String.format("%s not support add new files", path.getParent())); + } + } + + public static void register(String name, Vfs vfs) { + map.put(name, vfs); + } + + public static Vfs getByName(String name) { + if(name == null) { + return defaultImpl; + } + return map.get(name); + } + + public static Vfs get() { + return defaultImpl; + } + + public InputStream getInputStream(Path path) throws IOException { + VfsEntry entry = directory.resolve(path); + if (entry == null) throw new FileNotFoundException(String.format("File %s not found", path)); + if(entry instanceof VfsFile file) { + return file.getInputStream(); + } + throw new VfsException(String.format("%s is a directory", path.getParent())); + } + + public URL getURL(String path) throws IOException { + return getURL(Paths.get(path)); + } + + public URL getURL(Path name) throws IOException { + try (InputStream stream = defaultImpl.getInputStream(name)) { + return new URI("vfs", null, "/"+name, null).toURL(); + } catch (UnsupportedOperationException ex) { + throw new FileNotFoundException(name.toString()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } +} diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/VfsDirectory.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/VfsDirectory.java new file mode 100644 index 00000000..50f167c2 --- /dev/null +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/VfsDirectory.java @@ -0,0 +1,26 @@ +package pro.gravit.launcher.base.vfs; + +import java.nio.file.Path; +import java.util.stream.Stream; + +public abstract class VfsDirectory extends VfsEntry { + public abstract VfsEntry find(String name); + public VfsEntry resolve(Path path) { + if(path == null) { + return null; + } + + VfsDirectory current = this; + for(int i=0;i getFiles(); +} diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/VfsEntry.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/VfsEntry.java new file mode 100644 index 00000000..517cd65c --- /dev/null +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/VfsEntry.java @@ -0,0 +1,4 @@ +package pro.gravit.launcher.base.vfs; + +public class VfsEntry { +} diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/VfsException.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/VfsException.java new file mode 100644 index 00000000..bec909a6 --- /dev/null +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/VfsException.java @@ -0,0 +1,22 @@ +package pro.gravit.launcher.base.vfs; + +public class VfsException extends RuntimeException { + public VfsException() { + } + + public VfsException(String message) { + super(message); + } + + public VfsException(String message, Throwable cause) { + super(message, cause); + } + + public VfsException(Throwable cause) { + super(cause); + } + + public VfsException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/VfsFile.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/VfsFile.java new file mode 100644 index 00000000..671c85cc --- /dev/null +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/VfsFile.java @@ -0,0 +1,26 @@ +package pro.gravit.launcher.base.vfs; + + +import pro.gravit.launcher.base.vfs.protocol.vfs.VfsURLConnection; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; + +public abstract class VfsFile extends VfsEntry { + public URLConnection openConnection(URL url) { + return new VfsURLConnection(url, this); + } + public abstract InputStream getInputStream(); + + public byte[] readAll() throws IOException { + try(var input = getInputStream()) { + try(var output = new ByteArrayOutputStream()) { + input.transferTo(output); + return output.toByteArray(); + } + } + } +} diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/directory/FileVfsDirectory.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/directory/FileVfsDirectory.java new file mode 100644 index 00000000..ea4cf056 --- /dev/null +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/directory/FileVfsDirectory.java @@ -0,0 +1,42 @@ +package pro.gravit.launcher.base.vfs.directory; + + +import pro.gravit.launcher.base.vfs.VfsDirectory; +import pro.gravit.launcher.base.vfs.VfsEntry; +import pro.gravit.launcher.base.vfs.VfsException; +import pro.gravit.launcher.base.vfs.file.FileVfsFile; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + +public class FileVfsDirectory extends VfsDirectory { + private final Path path; + + public FileVfsDirectory(Path path) { + this.path = path; + } + + @Override + public VfsEntry find(String name) { + Path target = path.resolve(name); + if(Files.exists(target)) { + if(Files.isDirectory(target)) { + return new FileVfsDirectory(target); + } + return new FileVfsFile(target); + } + return null; + } + + @SuppressWarnings("resource") + @Override + protected Stream getFiles() { + try { + return Files.list(path).map(Path::getFileName).map(Path::toString); + } catch (IOException e) { + throw new VfsException(e); + } + } +} diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/directory/SimpleVfsDirectory.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/directory/SimpleVfsDirectory.java new file mode 100644 index 00000000..c9212b0c --- /dev/null +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/directory/SimpleVfsDirectory.java @@ -0,0 +1,30 @@ +package pro.gravit.launcher.base.vfs.directory; + + +import pro.gravit.launcher.base.vfs.VfsDirectory; +import pro.gravit.launcher.base.vfs.VfsEntry; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +public class SimpleVfsDirectory extends VfsDirectory { + private final Map map = new HashMap<>(); + @Override + public VfsEntry find(String name) { + return map.get(name); + } + + public VfsEntry remove(String name) { + return map.remove(name); + } + + public void put(String name, VfsEntry entry) { + map.put(name, entry); + } + + @Override + protected Stream getFiles() { + return map.keySet().stream(); + } +} diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/file/BytesVfsFile.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/file/BytesVfsFile.java new file mode 100644 index 00000000..ed84abea --- /dev/null +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/file/BytesVfsFile.java @@ -0,0 +1,20 @@ +package pro.gravit.launcher.base.vfs.file; + + +import pro.gravit.launcher.base.vfs.VfsFile; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +public class BytesVfsFile extends VfsFile { + private final byte[] bytes; + + public BytesVfsFile(byte[] bytes) { + this.bytes = bytes; + } + + @Override + public InputStream getInputStream() { + return new ByteArrayInputStream(bytes); + } +} diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/file/DelegateVfsFile.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/file/DelegateVfsFile.java new file mode 100644 index 00000000..5562881b --- /dev/null +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/file/DelegateVfsFile.java @@ -0,0 +1,26 @@ +package pro.gravit.launcher.base.vfs.file; + + +import pro.gravit.launcher.base.vfs.VfsFile; + +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; + +public class DelegateVfsFile extends VfsFile { + private final VfsFile vfsFile; + + public DelegateVfsFile(VfsFile vfsFile) { + this.vfsFile = vfsFile; + } + + @Override + public URLConnection openConnection(URL url) { + return vfsFile.openConnection(url); + } + + @Override + public InputStream getInputStream() { + return vfsFile.getInputStream(); + } +} diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/file/FileVfsFile.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/file/FileVfsFile.java new file mode 100644 index 00000000..20fe574d --- /dev/null +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/file/FileVfsFile.java @@ -0,0 +1,40 @@ +package pro.gravit.launcher.base.vfs.file; + + +import pro.gravit.launcher.base.vfs.VfsException; +import pro.gravit.launcher.base.vfs.VfsFile; + +import java.io.*; +import java.net.URL; +import java.net.URLConnection; +import java.nio.file.Path; + +public class FileVfsFile extends VfsFile { + private final File file; + + public FileVfsFile(File file) { + this.file = file; + } + + public FileVfsFile(Path path) { + this.file = path.toFile(); + } + + @Override + public URLConnection openConnection(URL url) { + try { + return file.toURI().toURL().openConnection(); + } catch (IOException e) { + throw new VfsException(e); + } + } + + @Override + public InputStream getInputStream() { + try { + return new FileInputStream(file); + } catch (FileNotFoundException e) { + throw new VfsException(e); + } + } +} diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/file/InputStreamVfsFile.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/file/InputStreamVfsFile.java new file mode 100644 index 00000000..05464bba --- /dev/null +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/file/InputStreamVfsFile.java @@ -0,0 +1,20 @@ +package pro.gravit.launcher.base.vfs.file; + + +import pro.gravit.launcher.base.vfs.VfsFile; + +import java.io.InputStream; +import java.util.function.Supplier; + +public class InputStreamVfsFile extends VfsFile { + private final Supplier streamSupplier; + + public InputStreamVfsFile(Supplier streamSupplier) { + this.streamSupplier = streamSupplier; + } + + @Override + public InputStream getInputStream() { + return streamSupplier.get(); + } +} diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/file/UrlVfsFile.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/file/UrlVfsFile.java new file mode 100644 index 00000000..05fdc41b --- /dev/null +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/file/UrlVfsFile.java @@ -0,0 +1,36 @@ +package pro.gravit.launcher.base.vfs.file; + + +import pro.gravit.launcher.base.vfs.VfsException; +import pro.gravit.launcher.base.vfs.VfsFile; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; + +public class UrlVfsFile extends VfsFile { + private final URL internalUrl; + + public UrlVfsFile(URL internalUrl) { + this.internalUrl = internalUrl; + } + + @Override + public URLConnection openConnection(URL url) { + try { + return internalUrl.openConnection(); + } catch (IOException e) { + throw new VfsException(e); + } + } + + @Override + public InputStream getInputStream() { + try { + return openConnection(internalUrl).getInputStream(); + } catch (IOException e) { + throw new VfsException(e); + } + } +} diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/protocol/vfs/Handler.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/protocol/vfs/Handler.java new file mode 100644 index 00000000..9f14a6ec --- /dev/null +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/protocol/vfs/Handler.java @@ -0,0 +1,28 @@ +package pro.gravit.launcher.base.vfs.protocol.vfs; + + +import pro.gravit.launcher.base.vfs.Vfs; +import pro.gravit.launcher.base.vfs.VfsFile; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLDecoder; +import java.net.URLStreamHandler; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; + +public class Handler extends URLStreamHandler { + @Override + protected URLConnection openConnection(URL url) throws IOException { + Vfs enFS = Vfs.getByName(url.getHost()); + String realPath = URLDecoder.decode(url.getPath(), StandardCharsets.UTF_8); + var fileEntry = enFS.resolve(Paths.get(realPath)); + if(fileEntry == null) throw new FileNotFoundException(url.toString()); + if(fileEntry instanceof VfsFile file) { + return file.openConnection(url); + } + throw new UnsupportedOperationException(String.format("%s not supported openConnection()", url)); + } +} diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/protocol/vfs/VfsURLConnection.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/protocol/vfs/VfsURLConnection.java new file mode 100644 index 00000000..a91da959 --- /dev/null +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/protocol/vfs/VfsURLConnection.java @@ -0,0 +1,27 @@ +package pro.gravit.launcher.base.vfs.protocol.vfs; + + +import pro.gravit.launcher.base.vfs.VfsFile; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; + +public class VfsURLConnection extends URLConnection { + private final VfsFile fileEntry; + public VfsURLConnection(URL url, VfsFile fileEntry) { + super(url); + this.fileEntry = fileEntry; + } + + @Override + public void connect() throws IOException { + // NOP + } + + @Override + public InputStream getInputStream() throws IOException { + return fileEntry.getInputStream(); + } +} diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/protocol/vfs/VfsURLStreamHandlerProvider.java b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/protocol/vfs/VfsURLStreamHandlerProvider.java new file mode 100644 index 00000000..6b96a959 --- /dev/null +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/base/vfs/protocol/vfs/VfsURLStreamHandlerProvider.java @@ -0,0 +1,14 @@ +package pro.gravit.launcher.base.vfs.protocol.vfs; + +import java.net.URLStreamHandler; +import java.net.spi.URLStreamHandlerProvider; + +public class VfsURLStreamHandlerProvider extends URLStreamHandlerProvider { + @Override + public URLStreamHandler createURLStreamHandler(String s) { + if (s.equals("vfs")) { + return new Handler(); + } + return null; + } +}