Permalink
Browse files

Continue working on VFS.

Add more comments and unit tests, though everything is disabled for now,
since it's not usable in code yet, and most things don't work. I have
identified the tests that need to pass for a minimally viable product,
so once those are completed, I will integrate the system into MS proper.
  • Loading branch information...
LadyCailin committed Nov 7, 2018
1 parent de97884 commit 714e64a06078a53a12187484156697a7e2ede02c
@@ -1,5 +1,6 @@
package com.laytonsmith.PureUtilities.VirtualFS;
import com.laytonsmith.PureUtilities.Common.Annotations.ForceImplementation;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.ElementType;
@@ -23,39 +24,113 @@
protected final VirtualFile path;
protected final VirtualFileSystem fileSystem;
protected FileSystemLayer(VirtualFile path, VirtualFileSystem fileSystem) {
@ForceImplementation
protected FileSystemLayer(VirtualFile path, VirtualFileSystem fileSystem, String symlink) {
this.path = path;
this.fileSystem = fileSystem;
}
/**
* Returns an input stream to the underlying resource.
* @return
* @throws IOException If the file cannot be read
*/
public abstract InputStream getInputStream() throws IOException;
/**
* Given the byte array, writes it to the underlying resource.
* @param bytes
* @throws IOException If the file cannot be written, for instance, the underlying resource is not available, or
* the file is read only.
*/
public abstract void writeByteArray(byte[] bytes) throws IOException;
/**
* Returns a list of files in this directory. If this is not a directory, this should throw an exception.
* @return
* @throws IOException If this file is not a directory, or the user does not have permission to list the
* files.
*/
public abstract VirtualFile[] listFiles() throws IOException;
/**
* Deletes the file immediately.
* @throws IOException If the file could not in fact be deleted, either because of permissions issues, or because
* the file does not exist.
*/
public abstract void delete() throws IOException;
/**
* This may work the exact same as delete in some cases, but otherwise, the
* file will be deleted upon exit of the virtual machine.
* file will be deleted upon exit of the virtual machine. This method will not necessarily throw an exception if the
* operation should succeed, but doesn't when the deletion actually is attempted. However, in cases where the
* attempt will definitely never succeed, this should throw an exception.
*
* @throws IOException
* @throws IOException If the file cannot under any circumstance be deleted, for instance, if the file does not
* exist, or the user does not have permission, or the underlying resource is not available.
*/
public abstract void deleteOnExit() throws IOException;
public abstract void deleteEventually() throws IOException;
/**
* Returns true if this file exists, false otherwise.
* @return
* @throws IOException If the underlying resource is not available, or the user does not have permission to check
* existence of a file.
*/
public abstract boolean exists() throws IOException;
/**
* Returns true if the user can read this file.
* @return True if the file exists, and the user can read it. False if the file does exist, but the user cannot
* read it.
* @throws IOException If the file does not exist, the underlying resource is not available, or the user does
* not have permission to check if this file can be read. If the user has permission to see the existence of the
* file, but simply is not allowed to read it, then this will not throw an exception, but return false instead.
*/
public abstract boolean canRead() throws IOException;
/**
* Returns true if the user can write to this file.
* @return True if the file exists, and the user can read it. False if the file does exist, but the user cannot
* write to it.
* @throws IOException If the file does not exist, the underlying resource is not available, or the user does
* not have permission to check if this can be written to. If the user has permission to see the existence of the
* file, but simply is not allowed to write to it, then this will not throw an exception, but return false instead.
*/
public abstract boolean canWrite() throws IOException;
/**
* Returns true if this is a directory. In some cases, on some platforms, this is not guaranteed to return the
* opposite of {@link #isFile()}.
* @return True if this is a directory, false otherwise.
* @throws IOException If the path does not exist, the underlying resource is not available, or the user does
* not have permission to check if this is a directory.
*/
public abstract boolean isDirectory() throws IOException;
/**
* Returns true if this is a file. In some cases, on some platforms, this is not guaranteed to return the
* opposite of {@link #isDirectory()}.
* @return True if this is a file, false otherwise.
* @throws IOException If the path does not exist, the underlying resource is not available, or the user does
* not have permission to check if this is a file.
*/
public abstract boolean isFile() throws IOException;
/**
* Creates the specified directory, and any parent directories necessary. Directories that already exist will not be
* touched, and it is not an error to call this on an already existing directory.
* @throws IOException If the directory could not be created. Note that even in the case where the call fails, it
* may be that some of the parent directories were created. This will also throw an exception if the path specified
* already exists, but it is not a directory. It will also be thrown if the underlying resource is not available.
*/
public abstract void mkdirs() throws IOException;
/**
* Creates a new file, if the path does not point to an existing file.
* @throws IOException If the path already exists, or the file could otherwise not be created, or if the underlying
* resource is not available.
*/
public abstract void createNewFile() throws IOException;
/**
@@ -20,7 +20,7 @@
protected final File real;
public RealFileSystemLayer(VirtualFile path, VirtualFileSystem fileSystem, String symlink) throws IOException {
super(path, fileSystem);
super(path, fileSystem, symlink);
if(symlink == null) {
real = new File(fileSystem.root, path.getPath());
if(!real.getCanonicalPath().startsWith(fileSystem.root.getCanonicalPath())) {
@@ -70,13 +70,19 @@ private VirtualFile normalize(File real) throws IOException {
@Override
public void delete() throws IOException {
if(!exists()) {
throw new IOException("File does not exist");
}
if(!real.delete()) {
throw new IOException("Could not delete the file");
}
}
@Override
public void deleteOnExit() {
public void deleteEventually() throws IOException {
if(!exists()) {
throw new IOException("File does not exist");
}
real.deleteOnExit();
}
@@ -86,36 +92,54 @@ public boolean exists() {
}
@Override
public boolean canRead() {
public boolean canRead() throws IOException {
if(!exists()) {
throw new IOException("File does not exist");
}
return real.canRead();
}
@Override
public boolean canWrite() {
public boolean canWrite() throws IOException {
if(!exists()) {
throw new IOException("File does not exist");
}
return real.canWrite();
}
@Override
public boolean isDirectory() {
public boolean isDirectory() throws IOException {
if(!exists()) {
throw new IOException("File does not exist");
}
return real.isDirectory();
}
@Override
public boolean isFile() {
public boolean isFile() throws IOException {
if(!exists()) {
throw new IOException("File does not exist");
}
return real.isFile();
}
@Override
public void mkdirs() throws IOException {
if(exists() && !isDirectory()) {
throw new IOException("The specified path already exists, and is not a directory");
}
if(!real.mkdirs()) {
throw new IOException("Directory structure could not be created");
}
}
@Override
public void createNewFile() throws IOException {
if(exists()) {
throw new IOException("File already exists");
}
if(!real.createNewFile()) {
throw new IOException("File already exists!");
throw new IOException("File could not be created");
}
}
}
@@ -0,0 +1,128 @@
package com.laytonsmith.PureUtilities.VirtualFS;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class represents the underlying manifest file. Generally, it should only be necessary to use this version,
* but it implements an interface which should be used throughout the code to allow for easier testing.
*/
public class SystemVirtualFileSystemManifest implements VirtualFileSystemManifest {
private final static Map<File, VirtualFileSystemManifest> INSTANCE = new HashMap<>();
/**
* There should only be one accessor for the manifest file in the system, but for test purposes, it may be
* useful to mock the interface. This method is used to get the instance for each manifest file (of which
* there will most likely ever only be one). A file listener will be added to the underlying file so that
* changes from other processes can be reflected here as well, but the file can be manually refreshed as well.
* @param manifestFile The underlying manifest file.
* @return The VirtualFileSystemManifest wrapping the given manifestFile
* @throws IOException If there is some error when loading the manifest
*/
public static VirtualFileSystemManifest getInstance(File manifestFile) throws IOException {
if(!INSTANCE.containsKey(manifestFile)) {
INSTANCE.put(manifestFile, new SystemVirtualFileSystemManifest(manifestFile));
}
return INSTANCE.get(manifestFile);
}
private final Set<String> manifest;
private final File manifestFile;
private final WatchService watcher = FileSystems.getDefault().newWatchService();
private SystemVirtualFileSystemManifest(File manifestFile) throws FileNotFoundException, IOException {
this.manifestFile = manifestFile;
manifest = Collections.synchronizedSet(new TreeSet<>());
if(!manifestFile.exists()) {
save();
} else {
read(manifestFile, manifest);
}
Path p = manifestFile.getParentFile().toPath();
final WatchKey watchKey = p.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);
new Thread(() -> {
while(true) {
WatchKey key;
try {
key = watcher.take();
} catch(InterruptedException ex) {
return;
}
for(WatchEvent<?> event : key.pollEvents()) {
if(event.kind().equals(StandardWatchEventKinds.ENTRY_MODIFY)) {
WatchEvent<Path> ev = (WatchEvent<Path>) event;
Path path = ev.context();
if(path.equals(manifestFile.toPath())) {
try {
refresh();
} catch(IOException ex) {
Logger.getLogger(SystemVirtualFileSystemManifest.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
}
}, SystemVirtualFileSystemManifest.class.getSimpleName() + " Manifest Watcher").start();
}
private void read(File manifestFile, Set<String> manifest) throws IOException {
try {
// Read the file in outside of the synchronization block
Set<String> fileSet = (Set<String>) new ObjectInputStream(new FileInputStream(manifestFile)).readObject();
synchronized(manifest) {
manifest.clear();
manifest.addAll(fileSet);
}
} catch(ClassNotFoundException ex) {
throw new Error(ex);
}
}
private void save() throws IOException {
new ObjectOutputStream(new FileOutputStream(manifestFile)).writeObject(manifest);
}
@Override
public boolean fileInManifest(VirtualFile file) {
String p = file.getPath();
return manifest.contains(p);
}
@Override
public void removeFromManifest(VirtualFile file) throws IOException {
manifest.remove(file.getPath());
save();
}
@Override
public void addToManifest(VirtualFile file) throws IOException {
manifest.add(file.getPath());
save();
}
@Override
public void refresh() throws IOException {
System.out.println("Refreshing file");
read(manifestFile, manifest);
}
}
@@ -16,7 +16,7 @@
private final boolean isAbsolute;
public VirtualFile(String path) {
String working = path;
String working = path.trim().toLowerCase();
working = working.replace('\\', '/');
for(String s : RESTRICTED_CHARS) {
if(working.contains(s)) {
Oops, something went wrong.

0 comments on commit 714e64a

Please sign in to comment.