Skip to content

Commit

Permalink
Added more to the VFS. Stubbed out test cases, and moved direct file …
Browse files Browse the repository at this point in the history
…system access to an abstraction layer, so things like ssh mounting, streaming protocols, or memory file systems can easily be added later.
  • Loading branch information
LadyCailin committed Nov 8, 2012
1 parent eef673d commit 605e96d
Show file tree
Hide file tree
Showing 4 changed files with 483 additions and 30 deletions.
@@ -0,0 +1,67 @@
package com.laytonsmith.PureUtilities.VirtualFS;

import java.io.IOException;
import java.io.InputStream;

/**
* A file system layer is a layer between a VirtualFile and the real file
* system. This allows for non-traditional file systems to be transparently added
* to the VFS system. These layers are specified by symlinks in the VFS, which
* use a specific URI to denote the path. Once a FSL is implemented,
* it is trivial for a user to add a new symlink to make use of the new FSL.
* All functions may throw an IOException, which is not something real File objects
* normally do (for instance, delete() will simply return false) but to give the user
* more information, this class throws exceptions instead.
* @author lsmith
*/
public abstract class FileSystemLayer {

protected final VirtualFile path;
protected final VirtualFileSystem fileSystem;
protected FileSystemLayer(VirtualFile path, VirtualFileSystem fileSystem){
this.path = path;
this.fileSystem = fileSystem;
}

public abstract InputStream getInputStream() throws IOException;

public abstract void writeByteArray(byte[] bytes) throws IOException;

public abstract VirtualFile[] listFiles() throws IOException;

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.
* @throws IOException
*/
public abstract void deleteOnExit() throws IOException;

public abstract boolean exists() throws IOException;

public abstract boolean canRead() throws IOException;

public abstract boolean canWrite() throws IOException;

public abstract boolean isDirectory() throws IOException;

public abstract boolean isFile() throws IOException;

public abstract void mkdirs() throws IOException;

public abstract void createNewFile() throws IOException;

/**
* Used to denote a FileSystemLayer protocol
*/
public static @interface fslayer {
/**
* The protocol identifier, for instance, "file", which would
* map to a file://uri type uri.
* @return
*/
String value();
}

}
@@ -0,0 +1,109 @@
package com.laytonsmith.PureUtilities.VirtualFS;

import com.laytonsmith.PureUtilities.FileUtility;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.commons.io.FileUtils;

/**
*
* @author lsmith
*/
@FileSystemLayer.fslayer("file")
public class RealFileSystemLayer extends FileSystemLayer {

protected final File real;

public RealFileSystemLayer(VirtualFile path, VirtualFileSystem fileSystem) throws IOException {
super(path, fileSystem);
real = new File(fileSystem.root, path.getPath());
if (!real.getCanonicalPath().startsWith(fileSystem.root.getCanonicalPath())) {
throw new PermissionException(path.getPath() + " extends above the root directory of this file system, and does not point to a valid file.");
}
}

@Override
public InputStream getInputStream() throws FileNotFoundException {
return new FileInputStream(real);
}

@Override
public void writeByteArray(byte[] bytes) throws IOException {
FileUtils.writeByteArrayToFile(real, bytes);
}

@Override
public VirtualFile[] listFiles() throws IOException {
List<VirtualFile> virtuals = new ArrayList<VirtualFile>();
for (File sub : real.listFiles()) {
virtuals.add(normalize(sub));
}
return virtuals.toArray(new VirtualFile[virtuals.size()]);
}

private VirtualFile normalize(File real) throws IOException {
String path = real.getCanonicalPath().replaceFirst(Pattern.quote(fileSystem.root.getCanonicalPath()), "");
path = path.replace("\\", "/");
if (!path.startsWith("/")) {
path = "/" + path;
}
return new VirtualFile(path);
}

@Override
public void delete() throws IOException {
if(!real.delete()){
throw new IOException("Could not delete the file");
}
}

@Override
public void deleteOnExit() {
real.deleteOnExit();
}

@Override
public boolean exists() {
return real.exists();
}

@Override
public boolean canRead() {
return real.canRead();
}

@Override
public boolean canWrite() {
return real.canWrite();
}

@Override
public boolean isDirectory() {
return real.isDirectory();
}

@Override
public boolean isFile() {
return real.isFile();
}

@Override
public void mkdirs() throws IOException {
if(!real.mkdirs()){
throw new IOException("Directory structure could not be created");
}
}

@Override
public void createNewFile() throws IOException {
if(!real.createNewFile()){
throw new IOException("File already exists!");
}
}
}
Expand Up @@ -8,6 +8,7 @@
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -50,18 +51,22 @@
* Symlinks can be added, which map directories inside the virtual file system to
* other directories on the real file system, and these links appear completely
* transparent to the file system. This allows for non-continuous file systems
* to appear continuous internally.
* to appear continuous internally. Additionally, remote file systems can be mounted
* via ssh, and they will appear continuous.
* @author lsmith
*/
public class VirtualFileSystem {
private static final String META_DIRECTORY_PATH = ".vfsmeta";
public static final VirtualFile META_DIRECTORY = new VirtualFile("/" + META_DIRECTORY_PATH);
private static final String TMP_DIRECTORY_PATH = META_DIRECTORY_PATH + "/tmp";
public static final VirtualFile TMP_DIRECTORY = new VirtualFile("/" + TMP_DIRECTORY_PATH);

private final VirtualFileSystemSettings settings;
private final File root;
protected final File root;
private BigInteger quota = new BigInteger("-1");
private BigInteger FSSize = new BigInteger("0");
private Thread fsSizeThread;
private List<FileSystemLayer> currentTmpFiles = new ArrayList<FileSystemLayer>();


/**
Expand Down Expand Up @@ -97,6 +102,7 @@ public void run() {
fsSizeThread.setPriority(Thread.MIN_PRIORITY);
fsSizeThread.start();
}
//TODO: Kick off the tmp file deleter thread
}

private void install() throws IOException{
Expand All @@ -109,6 +115,7 @@ private void install() throws IOException{
File settingsFile = new File(meta, "settings.config");
File manifest = new File(meta, "manifest.txt");
File symlinks = new File(meta, "symlinks.txt");
File tmpDir = new File(meta, "tmp");

if(!settingsFile.exists()){
settingsFile.createNewFile();
Expand All @@ -122,6 +129,10 @@ private void install() throws IOException{
symlinks.createNewFile();
}

if(!tmpDir.exists()){
tmpDir.mkdirs();
}

}

private void assertReadPermission(VirtualFile file){
Expand All @@ -134,22 +145,16 @@ private void assertWritePermission(VirtualFile file){
throw new PermissionException(file.getPath() + " cannot be written to.");
}

private File normalize(VirtualFile virtual) throws IOException{
File real = new File(root, virtual.getPath());
if(!real.getCanonicalPath().startsWith(root.getCanonicalPath())){
throw new PermissionException(virtual.getPath() + " extends above the root directory of this file system, and does not point to a valid file.");
} else {
return real;
}
}

private VirtualFile normalize(File real) throws IOException{
String path = real.getCanonicalPath().replaceFirst(Pattern.quote(root.getCanonicalPath()), "");
path = path.replace("\\", "/");
if(!path.startsWith("/")){
path = "/" + path;
}
return new VirtualFile(path);
private FileSystemLayer normalize(VirtualFile virtual) throws IOException{
return null;
//TODO: See if thi points to a symlinked file. If so, we
//have to take that into account too.
// File real = new File(root, virtual.getPath());
// if(!real.getCanonicalPath().startsWith(root.getCanonicalPath())){
// throw new PermissionException(virtual.getPath() + " extends above the root directory of this file system, and does not point to a valid file.");
// } else {
// return real;
// }
}

/**
Expand Down Expand Up @@ -188,8 +193,8 @@ public void write(VirtualFile file, InputStream data){
*/
public InputStream readAsStream(VirtualFile file) throws IOException{
assertReadPermission(file);
File real = normalize(file);
return new FileInputStream(real);
FileSystemLayer real = normalize(file);
return real.getInputStream();
}

/**
Expand All @@ -200,8 +205,8 @@ public InputStream readAsStream(VirtualFile file) throws IOException{
*/
public void write(VirtualFile file, byte[] bytes) throws IOException{
assertWritePermission(file);
File real = normalize(file);
FileUtils.writeByteArrayToFile(real, bytes);
FileSystemLayer real = normalize(file);
real.writeByteArray(bytes);
}

/**
Expand All @@ -218,12 +223,8 @@ public void write(VirtualFile file, byte[] bytes) throws IOException{
if(settings.isCordonedOff()){
throw new UnsupportedOperationException("Not yet implemented.");
} else {
File real = normalize(directory);
List<VirtualFile> virtuals = new ArrayList<VirtualFile>();
for(File sub : real.listFiles()){
virtuals.add(normalize(sub));
}
return virtuals.toArray(new VirtualFile[virtuals.size()]);
FileSystemLayer real = normalize(directory);
return real.listFiles();
}
}

Expand All @@ -237,12 +238,12 @@ public void write(VirtualFile file, byte[] bytes) throws IOException{
* @param file
* @return
*/
public boolean delete(VirtualFile file) throws IOException{
public void delete(VirtualFile file) throws IOException{
assertWritePermission(file);
if(settings.isCordonedOff()){
throw new UnsupportedOperationException("Not implemented yet.");
} else {
return normalize(file).delete();
normalize(file).delete();
}
}

Expand Down Expand Up @@ -365,5 +366,27 @@ public void createEmptyFile(VirtualFile file) throws IOException{
}
normalize(file).createNewFile();
}

/**
* Creates a new temporary file, which is guaranteed to be unique, and
* will definitely exist for this session. The file is likely to be deleted
* at the start of the next session however, and so must not be relied on to
* continue to exist. Temporary files do count towards the quota if enabled,
* but will be deleted by the system automatically. You must have read and
* write permissions to / to create a temp file.
* @return
* @throws IOException
*/
public VirtualFile createTempFile() throws IOException{
assertWritePermission(new VirtualFile("/"));
assertReadPermission(new VirtualFile("/"));
String filename = "/" + TMP_DIRECTORY_PATH + "/" + UUID.randomUUID().toString() + ".tmp";
VirtualFile path = new VirtualFile(filename);
FileSystemLayer real = normalize(path);
//Add this to the current session's list, so it doesn't get hosed by the file deletion thread.
currentTmpFiles.add(real);
real.createNewFile();
return path;
}

}

0 comments on commit 605e96d

Please sign in to comment.