package jk_5.nailed.server.map.game.script;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import jk_5.nailed.api.mappack.filesystem.IMount;
import jk_5.nailed.api.util.Checks;
import org.apache.commons.io.FilenameUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;
import java.util.regex.Pattern;
/**
* No description given
*
* @author jk-5
*/
public class FileSystem {
private Map<String, MountWrapper> mounts = Maps.newHashMap();
private Set<IMountedFile> openFiles = Sets.newHashSet();
public void unload() {
//noinspection SynchronizeOnNonFinalField
synchronized(this.openFiles){
while(this.openFiles.size() > 0){
IMountedFile file = this.openFiles.iterator().next();
try{
file.close();
}catch(IOException e){
this.openFiles.remove(file);
}
}
}
}
public synchronized void mount(String location, IMount mount) throws FileSystemException {
if(mount == null){
throw new NullPointerException();
}
location = sanitizePath(location);
if(location.contains("..")){
throw new FileSystemException("Cannot mount below the root");
}
mount(new MountWrapper(location, mount));
}
private synchronized void mount(MountWrapper wrapper) throws FileSystemException {
String location = wrapper.getLocation();
if(this.mounts.containsKey(location)){
this.mounts.remove(location);
}
this.mounts.put(location, wrapper);
}
@SuppressWarnings("unused")
public synchronized void unmount(String path) {
path = sanitizePath(path);
if(this.mounts.containsKey(path)){
this.mounts.remove(path);
}
}
public synchronized String combine(String path, String childPath) {
path = sanitizePath(path);
childPath = sanitizePath(childPath);
if(path.length() == 0){
return childPath;
}
if(childPath.length() == 0){
return path;
}
return sanitizePath(path + '/' + childPath);
}
public static String getDirectory(String path) {
path = sanitizePath(path);
if(path.length() == 0){
return "..";
}
int lastSlash = path.lastIndexOf('/');
if(lastSlash >= 0){
return path.substring(0, lastSlash);
}
return "";
}
public static String getName(String path) {
path = sanitizePath(path);
if(path.length() == 0){
return "root";
}
int lastSlash = path.lastIndexOf('/');
if(lastSlash >= 0){
return FilenameUtils.removeExtension(path.substring(lastSlash + 1));
}
return FilenameUtils.removeExtension(path);
}
public synchronized long getSize(String path) throws FileSystemException {
path = sanitizePath(path);
MountWrapper mount = getMount(path);
return mount.getSize(path);
}
public synchronized String[] list(String path) throws FileSystemException {
path = sanitizePath(path);
MountWrapper mount = getMount(path);
List<String> list = Lists.newArrayList();
mount.list(path, list);
for(MountWrapper otherMount : this.mounts.values()){
if(getDirectory(otherMount.getLocation()).equals(path)){
list.add(getName(otherMount.getLocation()));
}
}
String[] array = new String[list.size()];
list.toArray(array);
return array;
}
private void findIn(String dir, List<String> matches, Pattern wildPattern) throws FileSystemException {
String[] list = list(dir);
//noinspection ForLoopReplaceableByForEach
for(int i = 0; i < list.length; i++){
String entry = list[i];
String entryPath = dir + "/" + entry;
if(wildPattern.matcher(entryPath).matches()){
matches.add(entryPath);
}
if(isDir(entryPath)){
findIn(entryPath, matches, wildPattern);
}
}
}
public synchronized String[] find(String wildPath) throws FileSystemException {
wildPath = sanitizePath(wildPath, true);
Pattern wildPattern = Pattern.compile("^\\Q" + wildPath.replaceAll("\\*", "\\\\E[^\\\\/]*\\\\Q") + "\\E$");
List<String> matches = Lists.newArrayList();
findIn("", matches, wildPattern);
return matches.toArray(new String[matches.size()]);
}
public synchronized boolean exists(String path) throws FileSystemException {
path = sanitizePath(path);
MountWrapper mount = getMount(path);
return mount.exists(path);
}
public synchronized boolean isDir(String path) throws FileSystemException {
path = sanitizePath(path);
MountWrapper mount = getMount(path);
return mount.isDirectory(path);
}
public synchronized IMountedFileText openForRead(String path) throws FileSystemException {
path = sanitizePath(path);
MountWrapper mount = getMount(path);
InputStream stream = mount.openForRead(path);
if(stream != null){
final BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
IMountedFileText file = new IMountedFileText() {
public String readLine() throws IOException {
return reader.readLine();
}
public void write(String s, int off, int len, boolean newLine) throws IOException {
throw new UnsupportedOperationException();
}
public void close() throws IOException {
//noinspection SynchronizeOnNonFinalField
synchronized(FileSystem.this.openFiles){
FileSystem.this.openFiles.remove(this);
reader.close();
}
}
public void flush() throws IOException {
throw new UnsupportedOperationException();
}
};
//noinspection SynchronizeOnNonFinalField
synchronized(this.openFiles){
this.openFiles.add(file);
}
return file;
}
return null;
}
public synchronized IMountedFileBinary openForBinaryRead(String path) throws FileSystemException {
path = sanitizePath(path);
MountWrapper mount = getMount(path);
final InputStream stream = mount.openForRead(path);
if(stream != null){
IMountedFileBinary file = new IMountedFileBinary() {
public int read() throws IOException {
return stream.read();
}
public void write(int i) throws IOException {
throw new UnsupportedOperationException();
}
public void close() throws IOException {
//noinspection SynchronizeOnNonFinalField
synchronized(FileSystem.this.openFiles){
FileSystem.this.openFiles.remove(this);
stream.close();
}
}
public void flush()
throws IOException {
throw new UnsupportedOperationException();
}
};
//noinspection SynchronizeOnNonFinalField
synchronized(this.openFiles){
this.openFiles.add(file);
}
return file;
}
return null;
}
private MountWrapper getMount(String path)
throws FileSystemException {
Iterator it = this.mounts.values().iterator();
MountWrapper match = null;
int matchLength = 999;
while(it.hasNext()){
MountWrapper mount = (MountWrapper) it.next();
if(contains(mount.getLocation(), path)){
int len = toLocal(path, mount.getLocation()).length();
if((match == null) || (len < matchLength)){
match = mount;
matchLength = len;
}
}
}
if(match == null){
throw new FileSystemException("Invalid Path");
}
return match;
}
private static String sanitizePath(String path) {
return sanitizePath(path, false);
}
private static String sanitizePath(String path, boolean allowWildcards) {
path = path.replace('\\', '/');
char[] specialChars = {'"', ':', '<', '>', '?', '|'};
StringBuilder cleanName = new StringBuilder();
for(int i = 0; i < path.length(); i++){
char c = path.charAt(i);
if((c >= ' ') && (Arrays.binarySearch(specialChars, c) < 0) && (allowWildcards || c != '*')){
cleanName.append(c);
}
}
path = cleanName.toString();
String[] parts = path.split("/");
Stack<String> outputParts = new Stack<String>();
//noinspection ForLoopReplaceableByForEach
for(int n = 0; n < parts.length; n++){
String part = parts[n];
if((part.length() != 0) && (!".".equals(part))){
if("..".equals(part)){
if(!outputParts.empty()){
String top = outputParts.peek();
if(!"..".equals(top)){
outputParts.pop();
}else{
outputParts.push("..");
}
}else{
outputParts.push("..");
}
}else if(part.length() >= 255){
outputParts.push(part.substring(0, 255));
}else{
outputParts.push(part);
}
}
}
StringBuilder result = new StringBuilder("");
Iterator it = outputParts.iterator();
while(it.hasNext()){
String part = (String) it.next();
result.append(part);
if(it.hasNext()){
result.append('/');
}
}
return result.toString();
}
public static boolean contains(String pathA, String pathB) {
pathA = sanitizePath(pathA);
pathB = sanitizePath(pathB);
return !"..".equals(pathB) && !pathB.startsWith("../") && (pathB.equals(pathA) || pathA.length() == 0 || pathB.startsWith(pathA + "/"));
}
public static String toLocal(String path, String location) {
path = sanitizePath(path);
location = sanitizePath(location);
assert contains(location, path);
String local = path.substring(location.length());
if(local.startsWith("/")){
return local.substring(1);
}
return local;
}
private class MountWrapper {
private String location;
private IMount mount;
public MountWrapper(String location, IMount mount) {
Checks.notNull(location, "Location is null");
Checks.notNull(mount, "Mount is null");
this.location = location;
this.mount = mount;
}
public boolean exists(String path) throws FileSystemException {
path = toLocal(path);
try{
return this.mount.exists(path);
}catch(IOException e){
throw new FileSystemException(e.getMessage());
}
}
public boolean isDirectory(String path) throws FileSystemException {
path = toLocal(path);
try{
return (this.mount.exists(path)) && (this.mount.isDirectory(path));
}catch(IOException e){
throw new FileSystemException(e.getMessage());
}
}
public void list(String path, List<String> contents) throws FileSystemException {
path = toLocal(path);
try{
if(this.mount.exists(path) && this.mount.isDirectory(path)){
this.mount.list(path, contents);
}else{
throw new FileSystemException("Not a directory");
}
}catch(IOException e){
throw new FileSystemException(e.getMessage());
}
}
public long getSize(String path) throws FileSystemException {
path = toLocal(path);
try{
if(this.mount.exists(path)){
if(this.mount.isDirectory(path)){
return 0L;
}
return this.mount.getSize(path);
}
throw new FileSystemException("No such file");
}catch(IOException e){
throw new FileSystemException(e.getMessage());
}
}
public InputStream openForRead(String path) throws FileSystemException {
path = toLocal(path);
try{
if((this.mount.exists(path)) && (!this.mount.isDirectory(path))){
return this.mount.openForRead(path);
}
throw new FileSystemException("No such file");
}catch(IOException e){
throw new FileSystemException(e.getMessage());
}
}
private String toLocal(String path) {
return FileSystem.toLocal(path, this.location);
}
public String getLocation() {
return this.location;
}
}
}