+ Server Block [almost finished]

This commit is contained in:
Nicolas BARBOTIN 2018-02-09 01:18:25 +01:00
parent b3aede2fee
commit 9789daa33e
12 changed files with 397 additions and 82 deletions

View File

@ -5,8 +5,6 @@ This is the unfinished port of the WebDisplays mod for Minecraft 1.12.2. The tex
* Peripheral: OpenComputers interface
* Read config (see "Config elements" below)
* Miniserv timeout
* Recipe for server block
* del command
### TODO
* French translations

View File

@ -27,6 +27,7 @@ import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@ -38,7 +39,7 @@ public class GuiServer extends WDScreen {
private static final ResourceLocation BG_IMAGE = new ResourceLocation("webdisplays", "textures/gui/server_bg.png");
private static final ResourceLocation FG_IMAGE = new ResourceLocation("webdisplays", "textures/gui/server_fg.png");
private static final HashMap<String, Method> COMMAND_MAP = new HashMap<>();
private static final int MAX_LINE_LEN = 30;
private static final int MAX_LINE_LEN = 32;
private static final int MAX_LINES = 12;
private final NameUUIDPair owner;
@ -48,8 +49,9 @@ public class GuiServer extends WDScreen {
private int blinkTime;
private String lastCmd;
private boolean promptLocked;
private long queryTime;
private volatile long queryTime;
private ClientTask<?> currentTask;
private int selectedLine = -1;
//Access command
private int accessTrials;
@ -59,10 +61,12 @@ public class GuiServer extends WDScreen {
//Upload wizard
private boolean uploadWizard;
private int selectedLine = -1;
private File uploadDir;
private File[] uploadFiles;
private final ArrayList<File> uploadFiles = new ArrayList<>();
private int uploadOffset;
private boolean uploadFirstIsParent;
private String uploadFilter = "";
private long uploadFilterTime;
public GuiServer(NameUUIDPair owner) {
this.owner = owner;
@ -171,10 +175,22 @@ public class GuiServer extends WDScreen {
} else {
blinkTime = (blinkTime + 1) % 10;
if(currentTask != null && System.currentTimeMillis() - queryTime >= 10000) {
writeLine(tr("timeout"));
currentTask.cancel();
clearTask();
if(currentTask != null) {
long queryTime;
synchronized(this) {
queryTime = this.queryTime;
}
if(System.currentTimeMillis() - queryTime >= 10000) {
writeLine(tr("timeout"));
currentTask.cancel();
clearTask();
}
}
if(!uploadFilter.isEmpty() && System.currentTimeMillis() - uploadFilterTime >= 1000) {
Log.info("Upload filter cleared");
uploadFilter = "";
}
}
}
@ -187,19 +203,46 @@ public class GuiServer extends WDScreen {
if(uploadWizard) {
if(keyState) {
if(keyCode == Keyboard.KEY_UP) {
if(--selectedLine < 3)
selectedLine = MAX_LINES - 1;
if(selectedLine > 3)
selectedLine--;
else if(uploadOffset > 0) {
uploadOffset--;
updateUploadScreen();
}
} else if(keyCode == Keyboard.KEY_DOWN) {
if(++selectedLine >= MAX_LINES)
selectedLine = 3;
if(selectedLine < MAX_LINES - 1)
selectedLine++;
else if(uploadOffset + selectedLine - 2 < uploadFiles.size()) {
uploadOffset++;
updateUploadScreen();
}
} else if(keyCode == Keyboard.KEY_PRIOR) {
selectedLine = 3;
int dst = uploadOffset - (MAX_LINES - 3);
if(dst < 0)
dst = 0;
selectFile(dst);
} else if(keyCode == Keyboard.KEY_NEXT) {
selectedLine = 3;
int dst = uploadOffset + (MAX_LINES - 3);
if(dst >= uploadFiles.size())
dst = uploadFiles.size() - 1;
selectFile(dst);
} else if(keyCode == Keyboard.KEY_RETURN || keyCode == Keyboard.KEY_NUMPADENTER) {
File file = uploadFiles.get(uploadOffset + selectedLine - 3);
if(file.isDirectory()) {
uploadCD(file);
updateUploadScreen();
} else
startFileUpload(file, true);
}
}
if(keyCode == Keyboard.KEY_ESCAPE) {
lines.clear();
promptLocked = false;
uploadWizard = false;
selectedLine = -1;
quitUploadWizard();
return; //Don't let the screen handle this
}
@ -208,9 +251,16 @@ public class GuiServer extends WDScreen {
super.handleKeyboardInput();
if(keyState) {
if(keyCode == Keyboard.KEY_L && (Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL)))
boolean ctrl = Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL);
if(keyCode == Keyboard.KEY_L && ctrl)
lines.clear();
else if(keyCode == Keyboard.KEY_UP) {
else if(keyCode == Keyboard.KEY_V && ctrl) {
prompt += getClipboardString();
if(prompt.length() > MAX_LINE_LEN)
prompt = prompt.substring(0, MAX_LINE_LEN);
} else if(keyCode == Keyboard.KEY_UP) {
if(lastCmd != null) {
String tmp = prompt;
prompt = lastCmd;
@ -225,7 +275,24 @@ public class GuiServer extends WDScreen {
protected void keyTyped(char typedChar, int keyCode) throws IOException {
super.keyTyped(typedChar, keyCode);
if(promptLocked || uploadWizard)
if(uploadWizard) {
boolean found = false;
uploadFilter += Character.toLowerCase(typedChar);
uploadFilterTime = System.currentTimeMillis();
for(int i = uploadFirstIsParent ? 1 : 0; i < uploadFiles.size(); i++) {
if(uploadFiles.get(i).getName().toLowerCase().startsWith(uploadFilter)) {
selectFile(i);
found = true;
break;
}
}
if(!found && uploadFilter.length() == 1)
uploadFilter = "";
return;
} else if(promptLocked)
return;
if(keyCode == Keyboard.KEY_BACK) {
@ -293,6 +360,13 @@ public class GuiServer extends WDScreen {
}
}
private void quitUploadWizard() {
lines.clear();
promptLocked = false;
uploadWizard = false;
selectedLine = -1;
}
@Override
public void onGuiClosed() {
super.onGuiClosed();
@ -304,7 +378,7 @@ public class GuiServer extends WDScreen {
private boolean queueTask(ClientTask<?> task) {
if(Client.getInstance().addTask(task)) {
promptLocked = true;
queryTime = System.currentTimeMillis();
queryTime = System.currentTimeMillis(); //No task is running so it's okay to have an unsynchronized access here
currentTask = task;
return true;
} else {
@ -336,9 +410,18 @@ public class GuiServer extends WDScreen {
}
@CommandHandler("help")
public void commandHelp() {
for(String c : COMMAND_MAP.keySet())
writeLine(c + " - " + tr("help." + c));
public void commandHelp(String[] args) {
if(args.length > 0) {
String cmd = args[0].toLowerCase();
if(COMMAND_MAP.containsKey(cmd))
writeLine(tr("help." + cmd));
else
writeLine(tr("unknowncmd"));
} else {
for(String c : COMMAND_MAP.keySet())
writeLine(c + " - " + tr("help." + c));
}
}
@CommandHandler("exit")
@ -408,7 +491,7 @@ public class GuiServer extends WDScreen {
@CommandHandler("url")
public void commandURL(String[] args) {
if(args.length < 1) {
writeLine(tr("urlarg"));
writeLine(tr("fnamearg"));
return;
}
@ -442,11 +525,29 @@ public class GuiServer extends WDScreen {
uploadDir = newDir;
}
uploadFiles = uploadDir.listFiles();
if(uploadFiles == null)
uploadFiles = new File[0];
else
uploadFiles = Arrays.stream(uploadFiles).filter(f -> !f.isHidden() && (f.isDirectory() || (f.isFile() && !Util.isFileNameInvalid(f.getName())))).toArray(File[]::new);
uploadFiles.clear();
File parent = uploadDir.getParentFile();
if(parent != null && parent.exists()) {
uploadFiles.add(parent);
uploadFirstIsParent = true;
} else
uploadFirstIsParent = false;
File[] children = uploadDir.listFiles();
if(children != null) {
Collator c = Collator.getInstance();
c.setStrength(Collator.SECONDARY);
c.setDecomposition(Collator.CANONICAL_DECOMPOSITION);
Arrays.stream(children).filter(f -> !f.isHidden() && (f.isDirectory() || f.isFile())).sorted((a, b) -> c.compare(a.getName(), b.getName())).forEach(uploadFiles::add);
}
uploadOffset = 0;
uploadFilter = "";
if(uploadWizard)
selectedLine = 3;
}
private void updateUploadScreen() {
@ -455,20 +556,134 @@ public class GuiServer extends WDScreen {
lines.add("Choose a file to upload");
lines.add(trimStringL(uploadDir.getPath()));
lines.add("");
lines.add("[Parent]");
final int maxl = Math.min(MAX_LINES - 4, uploadFiles.length);
for(int i = uploadOffset; i < maxl; i++)
lines.add(trimStringR(uploadFiles[i].getName()));
for(int i = uploadOffset; i < uploadFiles.size() && lines.size() < MAX_LINES; i++) {
if(i == 0 && uploadFirstIsParent)
lines.add("[Parent directory]");
else
lines.add(trimStringR(uploadFiles.get(i).getName()));
}
}
private void selectFile(int i) {
int pos = 3 + i - uploadOffset;
if(pos >= 3 && pos < MAX_LINES) {
selectedLine = pos;
return;
}
uploadOffset = i;
if(uploadOffset + MAX_LINES - 3 > uploadFiles.size())
uploadOffset = uploadFiles.size() - MAX_LINES + 3;
updateUploadScreen();
selectedLine = 3 + i - uploadOffset;
}
@CommandHandler("upload")
public void commandUpload() {
public void commandUpload(String[] args) {
if(!mc.player.getGameProfile().getId().equals(owner.uuid)) {
writeLine(tr("errowner"));
return;
}
if(args.length > 0) {
File fle = new File(Util.join(args, " "));
if(!fle.exists()) {
writeLine(tr("notfound"));
return;
}
if(fle.isDirectory())
uploadCD(fle);
else if(fle.isFile()) {
startFileUpload(fle, false);
return;
} else {
writeLine(tr("notfound"));
return;
}
}
uploadWizard = true;
promptLocked = true;
selectedLine = 3;
uploadOffset = 0;
selectedLine = 3;
updateUploadScreen();
}
@CommandHandler("rm")
public void commandDelete(String[] args) {
if(!mc.player.getGameProfile().getId().equals(owner.uuid)) {
writeLine(tr("errowner"));
return;
}
if(args.length < 1) {
writeLine(tr("fnamearg"));
return;
}
String fname = Util.join(args, " ");
if(Util.isFileNameInvalid(fname)) {
writeLine(tr("nameerr"));
return;
}
ClientTaskDeleteFile task = new ClientTaskDeleteFile(fname);
task.setFinishCallback((t) -> {
int status = t.getStatus();
if(status == 1)
writeLine(tr("notfound"));
else if(status != 0)
writeLine(tr("error"));
clearTask();
});
queueTask(task);
}
private void startFileUpload(File f, boolean quit) {
if(quit)
quitUploadWizard();
if(Util.isFileNameInvalid(f.getName()) || f.getName().length() >= MAX_LINE_LEN - 3) {
writeLine(tr("nameerr"));
return;
}
ClientTaskUploadFile task;
try {
task = new ClientTaskUploadFile(f);
} catch(IOException ex) {
writeLine(tr("error"));
ex.printStackTrace();
return;
}
task.setProgressCallback((cur, total) -> {
synchronized(GuiServer.this) {
queryTime = System.currentTimeMillis();
}
});
task.setFinishCallback(t -> {
int status = t.getUploadStatus();
if(status == 0)
writeLine(tr("upload.done"));
else if(status == Constants.FUPA_STATUS_FILE_EXISTS)
writeLine(tr("upload.exists"));
else if(status == Constants.FUPA_STATUS_EXCEEDS_QUOTA)
writeLine(tr("upload.quota"));
else
writeLine(tr("error2", status));
clearTask();
});
if(queueTask(task))
writeLine(tr("upload.uploading"));
}
}

View File

@ -46,11 +46,6 @@ public abstract class AbstractClient {
packetHandlers[ph.value().ordinal()] = m;
}
}
for(int i = 0; i < packetHandlers.length; i++) {
if(packetHandlers[i] == null)
Log.warning("AbstractClient: no packet handler for %s", PacketID.fromInt(i).toString());
}
}
protected abstract void onWriteError();
@ -67,17 +62,13 @@ public abstract class AbstractClient {
if(pid >= packetHandlers.length)
Log.error("Caught invalid packet ID %d", pid);
else {
Log.info("Received PID %s", PacketID.fromInt(pid).toString());
if(packetHandlers[pid] != null) {
try {
packetHandlers[pid].invoke(this, dis); //This is slow, I know... sorry
} catch(IllegalAccessException ex) {
Log.errorEx("This shouldn't have happened", ex);
} catch(InvocationTargetException ex) {
Log.warningEx("Caught exception while handling packet %d", ex.getTargetException(), pid);
}
else if(packetHandlers[pid] != null) {
try {
packetHandlers[pid].invoke(this, dis); //This is slow, I know... sorry
} catch(IllegalAccessException ex) {
Log.errorEx("This shouldn't have happened", ex);
} catch(InvocationTargetException ex) {
Log.warningEx("Caught exception while handling packet %d", ex.getTargetException(), pid);
}
}
} catch(IOException ex) {

View File

@ -14,7 +14,8 @@ public enum PacketID {
FILE_STATUS, //S->C
GET_FILE, //C->S
QUOTA, //C->S and S->C
LIST; //C->S and S->C
LIST, //C->S and S->C
DELETE; //C->S and S->C
public static PacketID fromInt(int i) {
PacketID[] values = values();

View File

@ -320,6 +320,12 @@ public class Client extends AbstractClient implements Runnable {
((ClientTaskGetFileList) currentTask).onFileList(files);
}
@PacketHandler(PacketID.DELETE)
public void handleDelete(DataInputStream dis) throws IOException {
if(currentTask instanceof ClientTaskDeleteFile)
((ClientTaskDeleteFile) currentTask).onStatusPacket(dis.readByte());
}
public void nextTask() {
if(currentTask != null)
currentTask.onFinished();

View File

@ -0,0 +1,40 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.miniserv.client;
import net.montoyo.wd.miniserv.OutgoingPacket;
import net.montoyo.wd.miniserv.PacketID;
public class ClientTaskDeleteFile extends ClientTask<ClientTaskDeleteFile> {
private final String fname;
private int status;
public ClientTaskDeleteFile(String fname) {
this.fname = fname;
}
@Override
public void start() {
OutgoingPacket pkt = new OutgoingPacket();
pkt.writeByte(PacketID.DELETE.ordinal());
pkt.writeString(fname);
client.sendPacket(pkt);
}
@Override
public void abort() {
}
public void onStatusPacket(int s) {
status = s;
client.nextTask();
}
public int getStatus() {
return status;
}
}

View File

@ -14,6 +14,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public class ClientTaskUploadFile extends ClientTask<ClientTaskUploadFile> implements Consumer<OutgoingPacket> {
@ -25,11 +26,14 @@ public class ClientTaskUploadFile extends ClientTask<ClientTaskUploadFile> imple
private FileInputStream fis;
private boolean abortFupa;
private int uploadStatus;
private long uploadPos;
private BiConsumer<Long, Long> onProgress;
public ClientTaskUploadFile(File fle) throws IOException {
file = fle;
size = Files.size(fle.toPath());
fis = new FileInputStream(fle);
runCallbackOnMcThread = true;
}
@Override
@ -45,7 +49,7 @@ public class ClientTaskUploadFile extends ClientTask<ClientTaskUploadFile> imple
@Override
public void abort() {
abortFupa = true;
setUploadStatus(Constants.FUPA_STATUS_CONNECTION_LOST);
uploadStatus = Constants.FUPA_STATUS_CONNECTION_LOST;
Util.silentClose(fis);
}
@ -56,14 +60,14 @@ public class ClientTaskUploadFile extends ClientTask<ClientTaskUploadFile> imple
accept(null);
} else {
Util.silentClose(fis);
setUploadStatus(status);
uploadStatus = status;
client.nextTask();
}
}
public void onUploadFinishedStatus(int status) {
abortFupa = true; //This isn't necessary, but just in case...
setUploadStatus(status);
uploadStatus = status;
client.nextTask();
}
@ -91,6 +95,11 @@ public class ClientTaskUploadFile extends ClientTask<ClientTaskUploadFile> imple
pkt.writeBytes(UPLOAD_BUFFER, 0, rd);
client.sendPacket(pkt);
if(onProgress != null) {
uploadPos += (long) rd;
onProgress.accept(uploadPos, size);
}
if(rd > 0) {
pkt.setOnFinishAction(this);
return;
@ -100,19 +109,12 @@ public class ClientTaskUploadFile extends ClientTask<ClientTaskUploadFile> imple
Util.silentClose(file);
}
private void setUploadStatus(int val) {
synchronized(this) {
uploadStatus = val;
}
public int getUploadStatus() {
return uploadStatus;
}
public int getUploadStatus() {
int ret;
synchronized(this) {
ret = uploadStatus;
}
return ret;
public void setProgressCallback(BiConsumer<Long, Long> onProgress) {
this.onProgress = onProgress;
}
}

View File

@ -13,6 +13,7 @@ import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.UUID;
import java.util.function.Consumer;
@ -278,6 +279,39 @@ public class ServerClient extends AbstractClient {
sendPacket(pkt);
}
@PacketHandler(PacketID.DELETE)
public void handleDelete(DataInputStream dis) throws IOException {
String fname = readString(dis);
int status = 2;
if(!Util.isFileNameInvalid(fname)) {
File file = new File(userDir, fname);
if(file.exists() && file.isFile()) {
try {
long sz = Files.size(file.toPath());
if(file.delete()) {
quota -= sz;
if(quota < 0)
quota = 0;
saveQuota();
status = 0;
}
} catch(IOException ex) {
Log.errorEx("Couldn't get size of file %s of user %s for removal", ex, file.getAbsolutePath(), uuid.toString());
}
} else
status = 1;
}
OutgoingPacket ret = new OutgoingPacket();
ret.writeByte(PacketID.DELETE.ordinal());
ret.writeByte(status);
sendPacket(ret);
}
private void finishUpload(int status) {
if(currentFile != null) {
OutgoingPacket pkt = new OutgoingPacket();
@ -289,14 +323,17 @@ public class ServerClient extends AbstractClient {
currentFile = null;
quota += currentFileSize;
saveQuota();
}
}
try {
DataOutputStream dos = new DataOutputStream(new FileOutputStream(new File(userDir, ".quota")));
dos.writeLong(quota);
Util.silentClose(dos);
} catch(IOException ex) {
Log.errorEx("Could not save quota data for user %s", ex, uuid.toString());
}
private void saveQuota() {
try {
DataOutputStream dos = new DataOutputStream(new FileOutputStream(new File(userDir, ".quota")));
dos.writeLong(quota);
Util.silentClose(dos);
} catch(IOException ex) {
Log.errorEx("Could not save quota data for user %s", ex, uuid.toString());
}
}

View File

@ -23,7 +23,8 @@
"webdisplays:redctrl1",
"webdisplays:redctrl2",
"webdisplays:stonekey",
"webdisplays:keyboard"
"webdisplays:keyboard",
"webdisplays:server"
]
}
}

View File

@ -122,10 +122,14 @@ webdisplays.server.timeout=Query timed out. Check logs.
webdisplays.server.ownername=Owner name: %s
webdisplays.server.owneruuid=Owner UUID:
webdisplays.server.quota=%s/%s used
webdisplays.server.urlarg=Missing file name argument
webdisplays.server.fnamearg=Missing file name argument
webdisplays.server.nameerr=Invalid file name
webdisplays.server.urlcopied=Copied URL to clipboard.
webdisplays.server.notfound=File not found
webdisplays.server.upload.uploading=Uploading...
webdisplays.server.upload.done=Done
webdisplays.server.upload.exists=Error: File exists
webdisplays.server.upload.quota=Error: File size exceeds quota
webdisplays.server.help.help=Displays this text
webdisplays.server.help.clear=Clears the screen
webdisplays.server.help.exit=Leaves this console
@ -135,3 +139,4 @@ webdisplays.server.help.quota=Displays the storage quota
webdisplays.server.help.ls=Lists the files on this server
webdisplays.server.help.url=Copies a file URL into your clipboard
webdisplays.server.help.upload=Opens the upload wizard
webdisplays.server.help.rm=Deletes a file

View File

@ -1,17 +1,16 @@
{
"type": "minecraft:crafting_shaped",
"pattern": [
"GGG",
"GSG",
"GGG"
"PG"
],
"key": {
"G": {
"item": "minecraft:dye",
"data": 8
},
"S": {
"item": "webdisplays:screen"
"P": {
"item": "webdisplays:craftcomp",
"data": 2
}
},
"result": {

View File

@ -0,0 +1,20 @@
{
"type": "minecraft:crafting_shaped",
"pattern": [
"PG"
],
"key": {
"G": {
"item": "minecraft:dye",
"data": 2
},
"P": {
"item": "webdisplays:craftcomp",
"data": 2
}
},
"result": {
"item": "webdisplays:peripheral",
"data": 11
}
}