diff --git a/src/main/java/dan200/computercraft/client/gui/ComputerScreenBase.java b/src/main/java/dan200/computercraft/client/gui/ComputerScreenBase.java index 4a0e40946..c45bdc855 100644 --- a/src/main/java/dan200/computercraft/client/gui/ComputerScreenBase.java +++ b/src/main/java/dan200/computercraft/client/gui/ComputerScreenBase.java @@ -6,20 +6,40 @@ package dan200.computercraft.client.gui; import com.mojang.blaze3d.matrix.MatrixStack; +import dan200.computercraft.ComputerCraft; import dan200.computercraft.client.gui.widgets.ComputerSidebar; import dan200.computercraft.client.gui.widgets.WidgetTerminal; import dan200.computercraft.shared.computer.core.ClientComputer; import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.inventory.ContainerComputerBase; +import dan200.computercraft.shared.computer.upload.FileUpload; +import dan200.computercraft.shared.computer.upload.UploadResult; +import dan200.computercraft.shared.network.NetworkHandler; +import dan200.computercraft.shared.network.server.ContinueUploadMessage; +import dan200.computercraft.shared.network.server.UploadFileMessage; import net.minecraft.client.gui.screen.inventory.ContainerScreen; import net.minecraft.entity.player.PlayerInventory; import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TranslationTextComponent; import org.lwjgl.glfw.GLFW; import javax.annotation.Nonnull; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; public abstract class ComputerScreenBase extends ContainerScreen { + private static final ITextComponent OK = new TranslationTextComponent( "gui.ok" ); + private static final ITextComponent CANCEL = new TranslationTextComponent( "gui.cancel" ); + private static final ITextComponent OVERWRITE = new TranslationTextComponent( "gui.computercraft.upload.overwrite_button" ); + protected WidgetTerminal terminal; protected final ClientComputer computer; protected final ComputerFamily family; @@ -95,4 +115,94 @@ public abstract class ComputerScreenBase extend { // Skip rendering labels. } + + @Override + public void onFilesDrop( @Nonnull List files ) + { + if( files.isEmpty() ) return; + + if( computer == null || !computer.isOn() ) + { + alert( UploadResult.FAILED_TITLE, UploadResult.COMPUTER_OFF_MSG ); + return; + } + + long size = 0; + + List toUpload = new ArrayList<>(); + for( Path file : files ) + { + // TODO: Recurse directories? If so, we probably want to shunt this off-thread. + if( !Files.isRegularFile( file ) ) continue; + + try( SeekableByteChannel sbc = Files.newByteChannel( file ) ) + { + long fileSize = sbc.size(); + if( fileSize > UploadFileMessage.MAX_SIZE || (size += fileSize) >= UploadFileMessage.MAX_SIZE ) + { + alert( UploadResult.FAILED_TITLE, UploadResult.TOO_MUCH_MSG ); + return; + } + + ByteBuffer buffer = ByteBuffer.allocateDirect( (int) fileSize ); + sbc.read( buffer ); + buffer.flip(); + + toUpload.add( new FileUpload( file.getFileName().toString(), buffer ) ); + } + catch( IOException e ) + { + ComputerCraft.log.error( "Failed uploading files", e ); + alert( UploadResult.FAILED_TITLE, new TranslationTextComponent( "computercraft.gui.upload.failed.generic", e.getMessage() ) ); + } + } + + if( toUpload.size() > 0 ) + { + NetworkHandler.sendToServer( new UploadFileMessage( computer.getInstanceID(), toUpload ) ); + } + } + + public void uploadResult( UploadResult result, ITextComponent message ) + { + switch( result ) + { + case SUCCESS: + alert( UploadResult.SUCCESS_TITLE, message ); + break; + case ERROR: + alert( UploadResult.FAILED_TITLE, message ); + break; + case CONFIRM_OVERWRITE: + OptionScreen.show( + minecraft, UploadResult.UPLOAD_OVERWRITE, message, + Arrays.asList( + OptionScreen.newButton( CANCEL, b -> cancelUpload() ), + OptionScreen.newButton( OVERWRITE, b -> continueUpload() ) + ), + this::cancelUpload + ); + break; + } + } + + private void continueUpload() + { + if( minecraft.screen instanceof OptionScreen ) ((OptionScreen) minecraft.screen).disable(); + NetworkHandler.sendToServer( new ContinueUploadMessage( computer.getInstanceID(), true ) ); + } + + private void cancelUpload() + { + minecraft.setScreen( this ); + NetworkHandler.sendToServer( new ContinueUploadMessage( computer.getInstanceID(), false ) ); + } + + private void alert( ITextComponent title, ITextComponent message ) + { + OptionScreen.show( minecraft, title, message, + Collections.singletonList( OptionScreen.newButton( OK, b -> minecraft.setScreen( this ) ) ), + () -> minecraft.setScreen( this ) + ); + } } diff --git a/src/main/java/dan200/computercraft/client/gui/OptionScreen.java b/src/main/java/dan200/computercraft/client/gui/OptionScreen.java new file mode 100644 index 000000000..90daf7d65 --- /dev/null +++ b/src/main/java/dan200/computercraft/client/gui/OptionScreen.java @@ -0,0 +1,127 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.client.gui; + +import com.mojang.blaze3d.matrix.MatrixStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.IBidiRenderer; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.widget.Widget; +import net.minecraft.client.gui.widget.button.Button; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.ITextComponent; + +import javax.annotation.Nonnull; +import java.util.List; + +public final class OptionScreen extends Screen +{ + private static final ResourceLocation BACKGROUND = new ResourceLocation( "computercraft", "textures/gui/blank_screen.png" ); + + public static final int BUTTON_WIDTH = 100; + public static final int BUTTON_HEIGHT = 20; + + private static final int PADDING = 16; + private static final int FONT_HEIGHT = 9; + + private int x; + private int y; + private int innerWidth; + private int innerHeight; + + private IBidiRenderer messageRenderer; + private final ITextComponent message; + private final List buttons; + private final Runnable exit; + + private final Screen originalScreen; + + private OptionScreen( ITextComponent title, ITextComponent message, List buttons, Runnable exit, Screen originalScreen ) + { + super( title ); + this.message = message; + this.buttons = buttons; + this.exit = exit; + this.originalScreen = originalScreen; + } + + public static void show( Minecraft minecraft, ITextComponent title, ITextComponent message, List buttons, Runnable exit ) + { + minecraft.setScreen( new OptionScreen( title, message, buttons, exit, unwrap( minecraft.screen ) ) ); + } + + public static Screen unwrap( Screen screen ) + { + return screen instanceof OptionScreen ? ((OptionScreen) screen).getOriginalScreen() : screen; + } + + @Override + public void init() + { + super.init(); + + int buttonWidth = BUTTON_WIDTH * buttons.size() + PADDING * (buttons.size() - 1); + int innerWidth = this.innerWidth = Math.max( 256, buttonWidth + PADDING * 2 ); + + messageRenderer = IBidiRenderer.create( font, message, innerWidth - PADDING * 2 ); + + int textHeight = messageRenderer.getLineCount() * FONT_HEIGHT + PADDING * 2; + innerHeight = textHeight + (buttons.isEmpty() ? 0 : buttons.get( 0 ).getHeight()) + PADDING; + + x = (width - innerWidth) / 2; + y = (height - innerHeight) / 2; + + int x = (width - buttonWidth) / 2; + for( Widget button : buttons ) + { + button.x = x; + button.y = y + textHeight; + addButton( button ); + + x += BUTTON_WIDTH + PADDING; + } + } + + @Override + public void render( @Nonnull MatrixStack transform, int mouseX, int mouseY, float partialTicks ) + { + renderBackground( transform ); + + // Render the actual texture. + minecraft.textureManager.bind( BACKGROUND ); + blit( transform, x, y, 0, 0, innerWidth, PADDING ); + blit( transform, + x, y + PADDING, 0, PADDING, innerWidth, innerHeight - PADDING * 2, + innerWidth, PADDING + ); + blit( transform, x, y + innerHeight - PADDING, 0, 256 - PADDING, innerWidth, PADDING ); + + messageRenderer.renderLeftAlignedNoShadow( transform, x + PADDING, y + PADDING, FONT_HEIGHT, 0x404040 ); + super.render( transform, mouseX, mouseY, partialTicks ); + } + + @Override + public void onClose() + { + exit.run(); + } + + public static Widget newButton( ITextComponent component, Button.IPressable clicked ) + { + return new Button( 0, 0, BUTTON_WIDTH, BUTTON_HEIGHT, component, clicked ); + } + + public void disable() + { + for( Widget widget : buttons ) widget.active = false; + } + + @Nonnull + public Screen getOriginalScreen() + { + return originalScreen; + } +} diff --git a/src/main/java/dan200/computercraft/shared/computer/core/IContainerComputer.java b/src/main/java/dan200/computercraft/shared/computer/core/IContainerComputer.java index 164bce6ae..62515bc52 100644 --- a/src/main/java/dan200/computercraft/shared/computer/core/IContainerComputer.java +++ b/src/main/java/dan200/computercraft/shared/computer/core/IContainerComputer.java @@ -5,16 +5,18 @@ */ package dan200.computercraft.shared.computer.core; +import dan200.computercraft.shared.computer.upload.FileUpload; +import net.minecraft.entity.player.ServerPlayerEntity; import net.minecraft.inventory.container.Container; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.List; /** * An instance of {@link Container} which provides a computer. You should implement this * if you provide custom computers/GUIs to interact with them. */ -@FunctionalInterface public interface IContainerComputer { /** @@ -33,8 +35,21 @@ public interface IContainerComputer * @return This container's input. */ @Nonnull - default InputState getInput() - { - return new InputState( this ); - } + InputState getInput(); + + /** + * Attempt to upload a series of files to this computer. + * + * @param uploader The player uploading files. + * @param files The files to upload. + */ + void upload( @Nonnull ServerPlayerEntity uploader, @Nonnull List files ); + + /** + * Continue an upload. + * + * @param uploader The player uploading files. + * @param overwrite Whether the files should be overwritten or not. + */ + void continueUpload( @Nonnull ServerPlayerEntity uploader, boolean overwrite ); } diff --git a/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerComputerBase.java b/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerComputerBase.java index 492aafab7..3283a7ca2 100644 --- a/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerComputerBase.java +++ b/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerComputerBase.java @@ -6,23 +6,41 @@ package dan200.computercraft.shared.computer.inventory; import dan200.computercraft.ComputerCraft; +import dan200.computercraft.core.filesystem.FileSystem; +import dan200.computercraft.core.filesystem.FileSystemException; +import dan200.computercraft.core.filesystem.FileSystemWrapper; import dan200.computercraft.shared.computer.core.*; +import dan200.computercraft.shared.computer.upload.FileUpload; +import dan200.computercraft.shared.computer.upload.UploadResult; +import dan200.computercraft.shared.network.NetworkHandler; +import dan200.computercraft.shared.network.client.UploadResultMessage; import dan200.computercraft.shared.network.container.ComputerContainerData; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.entity.player.ServerPlayerEntity; import net.minecraft.inventory.container.Container; import net.minecraft.inventory.container.ContainerType; +import net.minecraft.util.text.TranslationTextComponent; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.io.IOException; +import java.nio.channels.WritableByteChannel; +import java.util.ArrayList; +import java.util.List; +import java.util.StringJoiner; +import java.util.function.Function; import java.util.function.Predicate; public class ContainerComputerBase extends Container implements IContainerComputer { + private static final String LIST_PREFIX = "\n \u2022 "; + private final Predicate canUse; private final IComputer computer; private final ComputerFamily family; private final InputState input = new InputState( this ); + private List toUpload; protected ContainerComputerBase( ContainerType type, int id, Predicate canUse, IComputer computer, ComputerFamily family ) { @@ -73,6 +91,86 @@ public class ContainerComputerBase extends Container implements IContainerComput return input; } + @Override + public void upload( @Nonnull ServerPlayerEntity uploader, @Nonnull List files ) + { + UploadResultMessage message = upload( files, false ); + NetworkHandler.sendToPlayer( uploader, message ); + } + + @Override + public void continueUpload( @Nonnull ServerPlayerEntity uploader, boolean overwrite ) + { + List files = this.toUpload; + toUpload = null; + if( files == null || files.isEmpty() || !overwrite ) return; + + UploadResultMessage message = upload( files, true ); + NetworkHandler.sendToPlayer( uploader, message ); + } + + @Nonnull + private UploadResultMessage upload( @Nonnull List files, boolean forceOverwrite ) + { + ServerComputer computer = (ServerComputer) getComputer(); + if( computer == null ) return UploadResultMessage.COMPUTER_OFF; + + FileSystem fs = computer.getComputer().getEnvironment().getFileSystem(); + if( fs == null ) return UploadResultMessage.COMPUTER_OFF; + + try + { + List overwrite = new ArrayList<>(); + for( FileUpload upload : files ) + { + if( !fs.exists( upload.getName() ) ) continue; + if( fs.isDir( upload.getName() ) ) + { + return new UploadResultMessage( + UploadResult.ERROR, + new TranslationTextComponent( "gui.computercraft.upload.failed.overwrite_dir", upload.getName() ) + ); + } + + overwrite.add( upload.getName() ); + } + + if( !overwrite.isEmpty() && !forceOverwrite ) + { + StringJoiner joiner = new StringJoiner( LIST_PREFIX, LIST_PREFIX, "" ); + for( String value : overwrite ) joiner.add( value ); + + toUpload = files; + return new UploadResultMessage( + UploadResult.CONFIRM_OVERWRITE, + new TranslationTextComponent( "gui.computercraft.upload.overwrite.detail", joiner.toString() ) + ); + } + + long availableSpace = fs.getFreeSpace( "/" ); + long neededSpace = 0; + for( FileUpload upload : files ) neededSpace += Math.max( 512, upload.getBytes().remaining() ); + if( neededSpace > availableSpace ) return UploadResultMessage.OUT_OF_SPACE; + + for( FileUpload file : files ) + { + try( FileSystemWrapper channel = fs.openForWrite( file.getName(), false, Function.identity() ) ) + { + channel.get().write( file.getBytes() ); + } + } + + return new UploadResultMessage( + UploadResult.SUCCESS, new TranslationTextComponent( "gui.computercraft.upload.success.msg", files.size() ) + ); + } + catch( FileSystemException | IOException e ) + { + ComputerCraft.log.error( "Error uploading files", e ); + return new UploadResultMessage( UploadResult.ERROR, new TranslationTextComponent( "computercraft.gui.upload.failed.generic", e.getMessage() ) ); + } + } + @Override public void removed( @Nonnull PlayerEntity player ) { diff --git a/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerViewComputer.java b/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerViewComputer.java index 733049a6e..4612b46be 100644 --- a/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerViewComputer.java +++ b/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerViewComputer.java @@ -9,7 +9,6 @@ import dan200.computercraft.ComputerCraft; import dan200.computercraft.shared.Registry; import dan200.computercraft.shared.computer.blocks.TileCommandComputer; import dan200.computercraft.shared.computer.core.ComputerFamily; -import dan200.computercraft.shared.computer.core.IContainerComputer; import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.network.container.ViewComputerContainerData; import net.minecraft.entity.player.PlayerEntity; @@ -17,7 +16,7 @@ import net.minecraft.entity.player.PlayerInventory; import javax.annotation.Nonnull; -public class ContainerViewComputer extends ContainerComputerBase implements IContainerComputer +public class ContainerViewComputer extends ContainerComputerBase { private final int width; private final int height; diff --git a/src/main/java/dan200/computercraft/shared/computer/upload/FileUpload.java b/src/main/java/dan200/computercraft/shared/computer/upload/FileUpload.java new file mode 100644 index 000000000..93757d6a7 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/computer/upload/FileUpload.java @@ -0,0 +1,30 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.shared.computer.upload; + +import java.nio.ByteBuffer; + +public class FileUpload +{ + private final String name; + private final ByteBuffer bytes; + + public FileUpload( String name, ByteBuffer bytes ) + { + this.name = name; + this.bytes = bytes; + } + + public String getName() + { + return name; + } + + public ByteBuffer getBytes() + { + return bytes; + } +} diff --git a/src/main/java/dan200/computercraft/shared/computer/upload/UploadResult.java b/src/main/java/dan200/computercraft/shared/computer/upload/UploadResult.java new file mode 100644 index 000000000..794aa71e1 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/computer/upload/UploadResult.java @@ -0,0 +1,25 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.shared.computer.upload; + +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TranslationTextComponent; + +public enum UploadResult +{ + SUCCESS, + ERROR, + CONFIRM_OVERWRITE; + + public static final ITextComponent SUCCESS_TITLE = new TranslationTextComponent( "gui.computercraft.upload.success" ); + + public static final ITextComponent FAILED_TITLE = new TranslationTextComponent( "gui.computercraft.upload.failed" ); + public static final ITextComponent COMPUTER_OFF_MSG = new TranslationTextComponent( "gui.computercraft.upload.failed.computer_off" ); + public static final ITextComponent OUT_OF_SPACE_MSG = new TranslationTextComponent( "gui.computercraft.upload.failed.out_of_space" ); + public static final ITextComponent TOO_MUCH_MSG = new TranslationTextComponent( "gui.computercraft.upload.failed.too_much" ); + + public static final ITextComponent UPLOAD_OVERWRITE = new TranslationTextComponent( "gui.computercraft.upload.overwrite" ); +} diff --git a/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java b/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java index 238a7700d..dae1bf134 100644 --- a/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java +++ b/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java @@ -48,6 +48,8 @@ public final class NetworkHandler registerMainThread( 2, NetworkDirection.PLAY_TO_SERVER, RequestComputerMessage.class, RequestComputerMessage::new ); registerMainThread( 3, NetworkDirection.PLAY_TO_SERVER, KeyEventServerMessage.class, KeyEventServerMessage::new ); registerMainThread( 4, NetworkDirection.PLAY_TO_SERVER, MouseEventServerMessage.class, MouseEventServerMessage::new ); + registerMainThread( 5, NetworkDirection.PLAY_TO_SERVER, UploadFileMessage.class, UploadFileMessage::new ); + registerMainThread( 6, NetworkDirection.PLAY_TO_SERVER, ContinueUploadMessage.class, ContinueUploadMessage::new ); // Client messages registerMainThread( 10, NetworkDirection.PLAY_TO_CLIENT, ChatTableClientMessage.class, ChatTableClientMessage::new ); @@ -59,6 +61,7 @@ public final class NetworkHandler registerMainThread( 16, NetworkDirection.PLAY_TO_CLIENT, SpeakerPlayClientMessage.class, SpeakerPlayClientMessage::new ); registerMainThread( 17, NetworkDirection.PLAY_TO_CLIENT, SpeakerStopClientMessage.class, SpeakerStopClientMessage::new ); registerMainThread( 18, NetworkDirection.PLAY_TO_CLIENT, SpeakerMoveClientMessage.class, SpeakerMoveClientMessage::new ); + registerMainThread( 19, NetworkDirection.PLAY_TO_CLIENT, UploadResultMessage.class, UploadResultMessage::new ); } public static void sendToPlayer( PlayerEntity player, NetworkMessage packet ) diff --git a/src/main/java/dan200/computercraft/shared/network/client/UploadResultMessage.java b/src/main/java/dan200/computercraft/shared/network/client/UploadResultMessage.java new file mode 100644 index 000000000..42c0221b8 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/network/client/UploadResultMessage.java @@ -0,0 +1,58 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.shared.network.client; + +import dan200.computercraft.client.gui.ComputerScreenBase; +import dan200.computercraft.client.gui.OptionScreen; +import dan200.computercraft.shared.computer.upload.UploadResult; +import dan200.computercraft.shared.network.NetworkMessage; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.text.ITextComponent; +import net.minecraftforge.fml.network.NetworkEvent; + +import javax.annotation.Nonnull; + +public class UploadResultMessage implements NetworkMessage +{ + public static final UploadResultMessage COMPUTER_OFF = new UploadResultMessage( UploadResult.ERROR, UploadResult.COMPUTER_OFF_MSG ); + public static final UploadResultMessage OUT_OF_SPACE = new UploadResultMessage( UploadResult.ERROR, UploadResult.OUT_OF_SPACE_MSG ); + + private final UploadResult result; + private final ITextComponent message; + + public UploadResultMessage( UploadResult result, ITextComponent message ) + { + this.result = result; + this.message = message; + } + + public UploadResultMessage( @Nonnull PacketBuffer buf ) + { + result = buf.readEnum( UploadResult.class ); + message = buf.readComponent(); + } + + @Override + public void toBytes( @Nonnull PacketBuffer buf ) + { + buf.writeEnum( result ); + buf.writeComponent( message ); + } + + @Override + public void handle( NetworkEvent.Context context ) + { + Minecraft minecraft = Minecraft.getInstance(); + + Screen screen = OptionScreen.unwrap( minecraft.screen ); + if( screen instanceof ComputerScreenBase ) + { + ((ComputerScreenBase) screen).uploadResult( result, message ); + } + } +} diff --git a/src/main/java/dan200/computercraft/shared/network/server/ComputerActionServerMessage.java b/src/main/java/dan200/computercraft/shared/network/server/ComputerActionServerMessage.java index f65559bba..4f02603d8 100644 --- a/src/main/java/dan200/computercraft/shared/network/server/ComputerActionServerMessage.java +++ b/src/main/java/dan200/computercraft/shared/network/server/ComputerActionServerMessage.java @@ -8,6 +8,7 @@ package dan200.computercraft.shared.network.server; import dan200.computercraft.shared.computer.core.IContainerComputer; import dan200.computercraft.shared.computer.core.ServerComputer; import net.minecraft.network.PacketBuffer; +import net.minecraftforge.fml.network.NetworkEvent; import javax.annotation.Nonnull; @@ -35,7 +36,7 @@ public class ComputerActionServerMessage extends ComputerServerMessage } @Override - protected void handle( @Nonnull ServerComputer computer, @Nonnull IContainerComputer container ) + protected void handle( NetworkEvent.Context context, @Nonnull ServerComputer computer, @Nonnull IContainerComputer container ) { switch( action ) { diff --git a/src/main/java/dan200/computercraft/shared/network/server/ComputerServerMessage.java b/src/main/java/dan200/computercraft/shared/network/server/ComputerServerMessage.java index 27c071416..1c8076e10 100644 --- a/src/main/java/dan200/computercraft/shared/network/server/ComputerServerMessage.java +++ b/src/main/java/dan200/computercraft/shared/network/server/ComputerServerMessage.java @@ -49,8 +49,8 @@ public abstract class ComputerServerMessage implements NetworkMessage IContainerComputer container = computer.getContainer( context.getSender() ); if( container == null ) return; - handle( computer, container ); + handle( context, computer, container ); } - protected abstract void handle( @Nonnull ServerComputer computer, @Nonnull IContainerComputer container ); + protected abstract void handle( NetworkEvent.Context context, @Nonnull ServerComputer computer, @Nonnull IContainerComputer container ); } diff --git a/src/main/java/dan200/computercraft/shared/network/server/ContinueUploadMessage.java b/src/main/java/dan200/computercraft/shared/network/server/ContinueUploadMessage.java new file mode 100644 index 000000000..85ae4b978 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/network/server/ContinueUploadMessage.java @@ -0,0 +1,45 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.shared.network.server; + +import dan200.computercraft.shared.computer.core.IContainerComputer; +import dan200.computercraft.shared.computer.core.ServerComputer; +import net.minecraft.entity.player.ServerPlayerEntity; +import net.minecraft.network.PacketBuffer; +import net.minecraftforge.fml.network.NetworkEvent; + +import javax.annotation.Nonnull; + +public class ContinueUploadMessage extends ComputerServerMessage +{ + private final boolean overwrite; + + public ContinueUploadMessage( int instanceId, boolean overwrite ) + { + super( instanceId ); + this.overwrite = overwrite; + } + + public ContinueUploadMessage( @Nonnull PacketBuffer buf ) + { + super( buf ); + overwrite = buf.readBoolean(); + } + + @Override + public void toBytes( @Nonnull PacketBuffer buf ) + { + super.toBytes( buf ); + buf.writeBoolean( overwrite ); + } + + @Override + protected void handle( NetworkEvent.Context context, @Nonnull ServerComputer computer, @Nonnull IContainerComputer container ) + { + ServerPlayerEntity player = context.getSender(); + if( player != null ) container.continueUpload( player, overwrite ); + } +} diff --git a/src/main/java/dan200/computercraft/shared/network/server/KeyEventServerMessage.java b/src/main/java/dan200/computercraft/shared/network/server/KeyEventServerMessage.java index 506127bb5..7dbca405d 100644 --- a/src/main/java/dan200/computercraft/shared/network/server/KeyEventServerMessage.java +++ b/src/main/java/dan200/computercraft/shared/network/server/KeyEventServerMessage.java @@ -9,6 +9,7 @@ import dan200.computercraft.shared.computer.core.IContainerComputer; import dan200.computercraft.shared.computer.core.InputState; import dan200.computercraft.shared.computer.core.ServerComputer; import net.minecraft.network.PacketBuffer; +import net.minecraftforge.fml.network.NetworkEvent; import javax.annotation.Nonnull; @@ -44,7 +45,7 @@ public class KeyEventServerMessage extends ComputerServerMessage } @Override - protected void handle( @Nonnull ServerComputer computer, @Nonnull IContainerComputer container ) + protected void handle( NetworkEvent.Context context, @Nonnull ServerComputer computer, @Nonnull IContainerComputer container ) { InputState input = container.getInput(); if( type == TYPE_UP ) diff --git a/src/main/java/dan200/computercraft/shared/network/server/MouseEventServerMessage.java b/src/main/java/dan200/computercraft/shared/network/server/MouseEventServerMessage.java index 2fa73fb63..8daa6d779 100644 --- a/src/main/java/dan200/computercraft/shared/network/server/MouseEventServerMessage.java +++ b/src/main/java/dan200/computercraft/shared/network/server/MouseEventServerMessage.java @@ -9,6 +9,7 @@ import dan200.computercraft.shared.computer.core.IContainerComputer; import dan200.computercraft.shared.computer.core.InputState; import dan200.computercraft.shared.computer.core.ServerComputer; import net.minecraft.network.PacketBuffer; +import net.minecraftforge.fml.network.NetworkEvent; import javax.annotation.Nonnull; @@ -53,7 +54,7 @@ public class MouseEventServerMessage extends ComputerServerMessage } @Override - protected void handle( @Nonnull ServerComputer computer, @Nonnull IContainerComputer container ) + protected void handle( NetworkEvent.Context context, @Nonnull ServerComputer computer, @Nonnull IContainerComputer container ) { InputState input = container.getInput(); switch( type ) diff --git a/src/main/java/dan200/computercraft/shared/network/server/QueueEventServerMessage.java b/src/main/java/dan200/computercraft/shared/network/server/QueueEventServerMessage.java index e6e45f6dd..5f1c093e7 100644 --- a/src/main/java/dan200/computercraft/shared/network/server/QueueEventServerMessage.java +++ b/src/main/java/dan200/computercraft/shared/network/server/QueueEventServerMessage.java @@ -10,6 +10,7 @@ import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.util.NBTUtil; import net.minecraft.nbt.CompoundNBT; import net.minecraft.network.PacketBuffer; +import net.minecraftforge.fml.network.NetworkEvent; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -50,7 +51,7 @@ public class QueueEventServerMessage extends ComputerServerMessage } @Override - protected void handle( @Nonnull ServerComputer computer, @Nonnull IContainerComputer container ) + protected void handle( NetworkEvent.Context context, @Nonnull ServerComputer computer, @Nonnull IContainerComputer container ) { computer.queueEvent( event, args ); } diff --git a/src/main/java/dan200/computercraft/shared/network/server/UploadFileMessage.java b/src/main/java/dan200/computercraft/shared/network/server/UploadFileMessage.java new file mode 100644 index 000000000..edc073758 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/network/server/UploadFileMessage.java @@ -0,0 +1,69 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.shared.network.server; + +import dan200.computercraft.shared.computer.core.IContainerComputer; +import dan200.computercraft.shared.computer.core.ServerComputer; +import dan200.computercraft.shared.computer.upload.FileUpload; +import net.minecraft.entity.player.ServerPlayerEntity; +import net.minecraft.network.PacketBuffer; +import net.minecraftforge.fml.network.NetworkEvent; + +import javax.annotation.Nonnull; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +public class UploadFileMessage extends ComputerServerMessage +{ + public static final int MAX_SIZE = 30 * 1024; // Max packet size is 32767. TODO: Bump this in the future + private final List files; + + public UploadFileMessage( int instanceId, List files ) + { + super( instanceId ); + this.files = files; + } + + public UploadFileMessage( @Nonnull PacketBuffer buf ) + { + super( buf ); + int nFiles = buf.readVarInt(); + List files = this.files = new ArrayList<>( nFiles ); + for( int i = 0; i < nFiles; i++ ) + { + String name = buf.readUtf( 32767 ); + int size = buf.readVarInt(); + if( size > MAX_SIZE ) break; + + ByteBuffer buffer = ByteBuffer.allocateDirect( size ); + buf.readBytes( buffer ); + buffer.flip(); + + files.add( new FileUpload( name, buffer ) ); + } + } + + @Override + public void toBytes( @Nonnull PacketBuffer buf ) + { + super.toBytes( buf ); + buf.writeVarInt( files.size() ); + for( FileUpload file : files ) + { + buf.writeUtf( file.getName() ); + buf.writeVarInt( file.getBytes().remaining() ); + buf.writeBytes( file.getBytes() ); + } + } + + @Override + protected void handle( NetworkEvent.Context context, @Nonnull ServerComputer computer, @Nonnull IContainerComputer container ) + { + ServerPlayerEntity player = context.getSender(); + if( player != null ) container.upload( player, files ); + } +} diff --git a/src/main/resources/assets/computercraft/lang/en_us.json b/src/main/resources/assets/computercraft/lang/en_us.json index 755148279..8fa9209b1 100644 --- a/src/main/resources/assets/computercraft/lang/en_us.json +++ b/src/main/resources/assets/computercraft/lang/en_us.json @@ -116,5 +116,16 @@ "gui.computercraft.tooltip.turn_off": "Turn this computer off", "gui.computercraft.tooltip.turn_off.key": "Hold Ctrl+S", "gui.computercraft.tooltip.terminate": "Stop the currently running code", - "gui.computercraft.tooltip.terminate.key": "Hold Ctrl+T" + "gui.computercraft.tooltip.terminate.key": "Hold Ctrl+T", + "gui.computercraft.upload.success": "Upload Succeeded", + "gui.computercraft.upload.success.msg": "%d files uploaded.", + "gui.computercraft.upload.failed": "Upload Failed", + "gui.computercraft.upload.failed.out_of_space": "Not enough space on the computer for these files.", + "gui.computercraft.upload.failed.computer_off": "You must turn the computer on before uploading files.", + "gui.computercraft.upload.failed.too_much": "Your files are too large to be uploaded.", + "gui.computercraft.upload.failed.overwrite_dir": "Cannot upload %s, as there is already a directory with the same name.", + "computercraft.gui.upload.failed.generic": "Uploading files failed (%s)", + "gui.computercraft.upload.overwrite": "Files would be overwritten", + "gui.computercraft.upload.overwrite.detail": "The following files will be overwritten when uploading. Continue?%s", + "gui.computercraft.upload.overwrite_button": "Overwrite" } diff --git a/src/main/resources/assets/computercraft/textures/gui/blank_screen.png b/src/main/resources/assets/computercraft/textures/gui/blank_screen.png new file mode 100644 index 000000000..6b3288e28 Binary files /dev/null and b/src/main/resources/assets/computercraft/textures/gui/blank_screen.png differ