begin actual network client work (file management)
parent
0941297e42
commit
4cf56430e7
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="false">
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8" inherit-compiler-output="false">
|
||||
<output url="file://$MODULE_DIR$/target/classes" />
|
||||
<output-test url="file://$MODULE_DIR$/target/test-classes" />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
|
@ -10,7 +10,6 @@
|
|||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Maven: com.google.guava:guava:15.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.commons:commons-math:2.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: commons-lang:commons-lang:2.6" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.1" level="project" />
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package net.mosstest.client;
|
||||
|
||||
/**
|
||||
* Created by hexafraction on 4/26/14.
|
||||
*/
|
||||
public class MossClient {
|
||||
RemoteFileManager fileManager;
|
||||
|
||||
}
|
|
@ -0,0 +1,277 @@
|
|||
package net.mosstest.client;
|
||||
|
||||
import com.jme3.asset.*;
|
||||
import net.mosstest.servercore.*;
|
||||
import org.apache.commons.configuration.ConfigurationException;
|
||||
import org.apache.commons.configuration.XMLConfiguration;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Created by hexafraction on 4/26/14.
|
||||
*/
|
||||
public class RemoteFileManager implements IFileManager {
|
||||
private static final String XML_DEPENDENCY_KEY = "dependencies.dependency";
|
||||
private static volatile RemoteFileManager instance = null;
|
||||
private HashSet<String> visitedScripts;
|
||||
|
||||
protected HashSet<AbstractMossScript> executed = new HashSet<>();
|
||||
|
||||
public static RemoteFileManager getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (RemoteFileManager.class) {
|
||||
if (instance == null) {
|
||||
instance = new RemoteFileManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
|
||||
}
|
||||
|
||||
private static final Logger logger = Logger.getLogger(RemoteFileManager.class);
|
||||
private HashMap<String, RemoteFile> knownByName = new HashMap<>();
|
||||
|
||||
private HashMap<String, RemoteFile> knownByHash = new HashMap<>();
|
||||
private File cacheBasedir;
|
||||
// used to request files
|
||||
private MossClient client;
|
||||
|
||||
@Override
|
||||
public RemoteFile getFile(String name) throws IOException {
|
||||
return knownByName.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerFile(String name, String sha256, long size) throws IOException {
|
||||
logger.info(MessageFormat.format("Registered {0} with hash {1}, and a size of {2} bytes", name, sha256, size));
|
||||
RemoteFile rmf = new RemoteFile(sha256, name, size);
|
||||
knownByName.put(name, rmf);
|
||||
knownByHash.put(sha256, rmf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveFileChunk(String sha256, int chunkId, ByteBuffer buf) throws IOException {
|
||||
RemoteFile rmf = this.knownByHash.get(sha256);
|
||||
if (rmf == null) {
|
||||
logger.warn(MessageFormat.format("The server tried to send a file chunk for a file with hash {0} that we don't know about.", sha256));
|
||||
return;
|
||||
}
|
||||
|
||||
rmf.writeChunk(chunkId, buf.array());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Class<? extends AssetLocator> getAssetLocatorClass() {
|
||||
return RemoteAssetLocator.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends IMossFile> getFiles() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractMossScript getScript(String name) throws IOException, MossWorldLoadException {
|
||||
List<AbstractMossScript> dependencies = new ArrayList<>();
|
||||
try {
|
||||
File scriptXml = this.getFile(name + "/script.xml").file;
|
||||
|
||||
|
||||
XMLConfiguration scriptCfg = new XMLConfiguration(scriptXml);
|
||||
String[] scNames = scriptCfg.getStringArray(XML_DEPENDENCY_KEY);
|
||||
for (String sc : scNames) {
|
||||
if (sc.equals(name)) continue;
|
||||
if (visitedScripts.contains(sc)) {
|
||||
logger.fatal(MessageFormat.format(Messages.getString("CIRCULAR_DEPENDENCY_ISSUE"), name, sc));
|
||||
throw new MossWorldLoadException(MessageFormat.format(Messages.getString("CIRCULAR_DEPENDENCY_ISSUE"), name, sc));
|
||||
}
|
||||
try {
|
||||
dependencies.add(this.getScript(sc));
|
||||
} catch (StackOverflowError e) {
|
||||
// should never happen
|
||||
logger.fatal(Messages.getString("DEPFIND_STACK_OVERFLOW"));
|
||||
throw new MossWorldLoadException("FIXME The stack overflowed while resolving dependencies. Either there is an undetected dependency issue, or there is simply an extreme number of dependencies.");
|
||||
}
|
||||
}
|
||||
} catch (ConfigurationException | FileNotFoundException e) {
|
||||
logger.warn(Messages.getString("SCRIPT_XML_MISSING"));
|
||||
}
|
||||
|
||||
return new RemoteScript(name, dependencies);
|
||||
}
|
||||
|
||||
|
||||
public class RemoteFile implements IMossFile {
|
||||
private final long size;
|
||||
|
||||
private final File file;
|
||||
|
||||
private final RandomAccessFile rFile;
|
||||
|
||||
private final BitSet completedChunks;
|
||||
|
||||
private final String sha256;
|
||||
|
||||
private final int numChunks;
|
||||
|
||||
private final String name;
|
||||
|
||||
RemoteFile(String sha256, String name, long size) throws IOException {
|
||||
this.sha256 = sha256;
|
||||
this.name = name;
|
||||
numChunks = (int) Math.ceil(size / IMossFile.CHUNK_SIZE);
|
||||
if (!sha256.matches("[0-9A-Za-z]{32}"))
|
||||
throw new MosstestFatalDeathException("A file SHA256 was invalid, and could not be used for caching.");
|
||||
this.file = new File(RemoteFileManager.this.cacheBasedir, sha256);
|
||||
this.file.createNewFile();
|
||||
this.rFile = new RandomAccessFile(this.file, "rw");
|
||||
this.size = size;
|
||||
try {
|
||||
completedChunks = new BitSet(numChunks);
|
||||
|
||||
String actualSha = IFileManager.getHash(file);
|
||||
if (actualSha.equalsIgnoreCase(sha256)) {
|
||||
completedChunks.set(0, numChunks);
|
||||
}
|
||||
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new MosstestFatalDeathException("The SHA-256 algorithm could not be found, but is required to cache files.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader getReader() throws FileNotFoundException {
|
||||
return new FileReader(this.file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws FileNotFoundException {
|
||||
return new FileInputStream(this.file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] readChunk(int chunkId) throws IOException {
|
||||
this.rFile.seek(chunkId * IMossFile.CHUNK_SIZE);
|
||||
byte[] buf = new byte[(int) (chunkId == (this.numChunks - 1) ? (this.size % IMossFile.CHUNK_SIZE)
|
||||
: IMossFile.CHUNK_SIZE)];
|
||||
this.rFile.readFully(buf);
|
||||
return buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeChunk(int chunkId, byte[] buf) throws IOException {
|
||||
this.rFile.seek(chunkId * IMossFile.CHUNK_SIZE);
|
||||
if (((chunkId == (this.numChunks - 1) ? (this.size % IMossFile.CHUNK_SIZE) : IMossFile.CHUNK_SIZE)) != buf.length)
|
||||
throw new MosstestFatalDeathException(MessageFormat.format("An inbound chunk for file {0} is not of the correct size. The server may be sending erroneous data.", this.name));
|
||||
this.completedChunks.set(chunkId);
|
||||
if (isComplete()) {
|
||||
logger.info(MessageFormat.format("Completed file {0}.", this.name));
|
||||
}
|
||||
try {
|
||||
String realHash = IFileManager.getHash(this.file);
|
||||
if (!this.sha256.equalsIgnoreCase(realHash)) {
|
||||
logger.warn(MessageFormat.format("File {0} expected with hash {1} but got reassembled as hash {2}", this.name, this.sha256, realHash));
|
||||
}
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new MosstestFatalDeathException("The SHA-256 algorithm could not be found, but is required to cache files.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSha256() {
|
||||
return this.sha256;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isComplete() {
|
||||
return (completedChunks.nextClearBit(0) == -1 || completedChunks.nextClearBit(0) == this.numChunks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize() {
|
||||
return this.size;
|
||||
}
|
||||
}
|
||||
|
||||
private static class RemoteAssetLocator implements AssetLocator {
|
||||
private static final Logger logger = Logger.getLogger(RemoteAssetLocator.class);
|
||||
|
||||
@Override
|
||||
public void setRootPath(String s) {
|
||||
// ignore
|
||||
logger.warn(MessageFormat.format("Tried to set root path {0} for RemoteAssetLocator, which ignores the root path.", s));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetInfo locate(AssetManager assetManager, AssetKey assetKey) {
|
||||
try {
|
||||
return new RemoteAssetInfo(assetManager, assetKey,
|
||||
RemoteFileManager.getInstance().getFile(assetKey.getName()));
|
||||
} catch (IOException ex) {
|
||||
throw new AssetLoadException("Failed to open file: "
|
||||
+ assetKey.getName(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static class RemoteAssetInfo extends AssetInfo {
|
||||
|
||||
private RemoteFile file;
|
||||
|
||||
public RemoteAssetInfo(AssetManager manager, AssetKey key, RemoteFile file) {
|
||||
super(manager, key);
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openStream() {
|
||||
try {
|
||||
return this.file.getInputStream();
|
||||
} catch (IOException ex) {
|
||||
throw new AssetLoadException("Failed to open file: "
|
||||
+ this.file.getName(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RemoteScript extends AbstractMossScript {
|
||||
private final List<AbstractMossScript> dependencies;
|
||||
|
||||
public RemoteScript(String name, List<AbstractMossScript> dependencies) {
|
||||
super(name);
|
||||
this.dependencies = dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exec(ScriptEnv sEnv) throws IOException, MossWorldLoadException {
|
||||
RemoteFileManager.this.executed.add(this);
|
||||
for (AbstractMossScript sc : dependencies) {
|
||||
sc.exec(sEnv);
|
||||
}
|
||||
sEnv.runScript(this.getInitFile());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMossFile getInitFile() throws IOException {
|
||||
return RemoteFileManager.this.getFile(this.name + "/init.js");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AbstractMossScript> getDependencies() {
|
||||
return Collections.unmodifiableList(dependencies);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,12 +9,12 @@ public abstract class AbstractMossScript {
|
|||
this.name = name;
|
||||
}
|
||||
|
||||
final String name;
|
||||
abstract void exec(ScriptEnv sEnv) throws IOException, MossWorldLoadException;
|
||||
protected final String name;
|
||||
public abstract void exec(ScriptEnv sEnv) throws IOException, MossWorldLoadException;
|
||||
|
||||
abstract IMossFile getInitFile() throws IOException;
|
||||
public abstract IMossFile getInitFile() throws IOException;
|
||||
|
||||
abstract List<AbstractMossScript> getDependencies();
|
||||
public abstract List<AbstractMossScript> getDependencies();
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object o) {
|
||||
|
|
|
@ -2,16 +2,61 @@ package net.mosstest.servercore;
|
|||
|
||||
import com.jme3.asset.AssetLocator;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
|
||||
public interface IFileManager {
|
||||
static String getHash(File f) throws IOException,
|
||||
NoSuchAlgorithmException, FileNotFoundException {
|
||||
|
||||
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
|
||||
try (FileInputStream fis = new FileInputStream(f)) {
|
||||
try (FileChannel fc = fis.getChannel()) {
|
||||
ByteBuffer bbf = ByteBuffer.allocateDirect(LocalFileManager.HASHING_BUFFER_SIZE);
|
||||
|
||||
int bytesRead;
|
||||
|
||||
bytesRead = fc.read(bbf);
|
||||
|
||||
while ((bytesRead != -1) && (bytesRead != 0)) {
|
||||
bbf.flip();
|
||||
|
||||
|
||||
md.update(bbf);
|
||||
|
||||
bbf.clear();
|
||||
bytesRead = fc.read(bbf);
|
||||
}
|
||||
|
||||
fis.close();
|
||||
|
||||
byte[] mdBytes = md.digest();
|
||||
|
||||
StringBuilder hexString = new StringBuilder();
|
||||
|
||||
for (byte b : mdBytes) {
|
||||
hexString.append(Integer.toHexString((LocalFileManager.BYTE_CAST_MASK & b)));
|
||||
}
|
||||
|
||||
return hexString.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IMossFile getFile(String name) throws IOException;
|
||||
|
||||
public void registerFile(String name, String sha256, long size);
|
||||
public void registerFile(String name, String sha256, long size) throws IOException;
|
||||
|
||||
public void receiveFileChunk(String sha512, int chunkId, ByteBuffer buf) throws IOException;
|
||||
public void receiveFileChunk(String sha256, int chunkId, ByteBuffer buf) throws IOException;
|
||||
|
||||
public Class<? extends AssetLocator> getAssetLocatorClass();
|
||||
|
||||
|
|
|
@ -19,4 +19,8 @@ public interface IMossFile {
|
|||
public String getSha256();
|
||||
|
||||
public String getName();
|
||||
|
||||
public boolean isComplete();
|
||||
|
||||
public long getSize();
|
||||
}
|
|
@ -33,6 +33,17 @@ public class LocalFile implements IMossFile {
|
|||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isComplete() {
|
||||
// local files are always complete
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize() {
|
||||
return f.length();
|
||||
}
|
||||
|
||||
private final String sha256;
|
||||
|
||||
public LocalFile(String name, File f) throws IOException, FileNotFoundException {
|
||||
|
@ -46,7 +57,7 @@ public class LocalFile implements IMossFile {
|
|||
this.numChunks = (int) Math.ceil(this.length
|
||||
/ ((double) IMossFile.CHUNK_SIZE));
|
||||
try {
|
||||
this.sha256 = LocalFileManager.getHash(f);
|
||||
this.sha256 = IFileManager.getHash(f);
|
||||
logger.info("Hashed " + f.getAbsolutePath() + " as " + this.sha256);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
logger.error("Could not find algorithm SHA-256 while hashing " + f.getAbsolutePath());
|
||||
|
|
|
@ -75,7 +75,7 @@ public class LocalFileManager implements IFileManager {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void receiveFileChunk(String sha512, int chunkId, ByteBuffer buf) throws IOException {
|
||||
public void receiveFileChunk(String sha256, int chunkId, ByteBuffer buf) throws IOException {
|
||||
throw new IOException("This file is read-only due to its being in a non-cache directory.");
|
||||
}
|
||||
|
||||
|
@ -89,45 +89,6 @@ public class LocalFileManager implements IFileManager {
|
|||
|
||||
}
|
||||
|
||||
public static String getHash(File f) throws IOException,
|
||||
NoSuchAlgorithmException, FileNotFoundException {
|
||||
|
||||
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
|
||||
try (FileInputStream fis = new FileInputStream(f)) {
|
||||
try (FileChannel fc = fis.getChannel()) {
|
||||
ByteBuffer bbf = ByteBuffer.allocateDirect(HASHING_BUFFER_SIZE);
|
||||
|
||||
int bytesRead;
|
||||
|
||||
bytesRead = fc.read(bbf);
|
||||
|
||||
while ((bytesRead != -1) && (bytesRead != 0)) {
|
||||
bbf.flip();
|
||||
|
||||
|
||||
md.update(bbf);
|
||||
|
||||
bbf.clear();
|
||||
bytesRead = fc.read(bbf);
|
||||
}
|
||||
|
||||
fis.close();
|
||||
|
||||
byte[] mdBytes = md.digest();
|
||||
|
||||
StringBuilder hexString = new StringBuilder();
|
||||
|
||||
for (byte b : mdBytes) {
|
||||
hexString.append(Integer.toHexString((BYTE_CAST_MASK & b)));
|
||||
}
|
||||
|
||||
return hexString.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends IMossFile> getFiles() {
|
||||
return ImmutableList.copyOf(files.values());
|
||||
|
|
Loading…
Reference in New Issue