1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-12-14 04:00:30 +00:00

Split uploaded files across multiple packets

Still not 100% sure of the correctness here, but it appears to work.
Closes #887.
This commit is contained in:
Jonathan Coates 2021-08-29 16:58:02 +01:00
parent 340ade170f
commit 9e82209aab
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
15 changed files with 371 additions and 58 deletions

View File

@ -144,22 +144,43 @@ public abstract class ComputerScreenBase<T extends ContainerComputerBase> extend
return; return;
} }
String name = file.getFileName().toString();
if( name.length() > UploadFileMessage.MAX_FILE_NAME )
{
alert( UploadResult.FAILED_TITLE, new TranslationTextComponent( "gui.computercraft.upload.failed.name_too_long" ) );
return;
}
ByteBuffer buffer = ByteBuffer.allocateDirect( (int) fileSize ); ByteBuffer buffer = ByteBuffer.allocateDirect( (int) fileSize );
sbc.read( buffer ); sbc.read( buffer );
buffer.flip(); buffer.flip();
toUpload.add( new FileUpload( file.getFileName().toString(), buffer ) ); byte[] digest = FileUpload.getDigest( buffer );
if( digest == null )
{
alert( UploadResult.FAILED_TITLE, new TranslationTextComponent( "gui.computercraft.upload.failed.corrupted" ) );
return;
}
buffer.rewind();
toUpload.add( new FileUpload( name, buffer, digest ) );
} }
catch( IOException e ) catch( IOException e )
{ {
ComputerCraft.log.error( "Failed uploading files", e ); ComputerCraft.log.error( "Failed uploading files", e );
alert( UploadResult.FAILED_TITLE, new TranslationTextComponent( "computercraft.gui.upload.failed.generic", e.getMessage() ) ); alert( UploadResult.FAILED_TITLE, new TranslationTextComponent( "gui.computercraft.upload.failed.generic", "Cannot compute checksum" ) );
} }
} }
if( toUpload.size() > UploadFileMessage.MAX_FILES )
{
alert( UploadResult.FAILED_TITLE, new TranslationTextComponent( "gui.computercraft.upload.failed.too_many_files" ) );
return;
}
if( toUpload.size() > 0 ) if( toUpload.size() > 0 )
{ {
NetworkHandler.sendToServer( new UploadFileMessage( computer.getInstanceID(), toUpload ) ); UploadFileMessage.send( computer.getInstanceID(), toUpload );
} }
} }

View File

@ -461,9 +461,9 @@ public class FSAPI implements ILuaAPI
* @param path The path to check the free space for. * @param path The path to check the free space for.
* @return The amount of free space available, in bytes. * @return The amount of free space available, in bytes.
* @throws LuaException If the path doesn't exist. * @throws LuaException If the path doesn't exist.
* @see #getCapacity To get the capacity of this drive.
* @cc.treturn number|"unlimited" The amount of free space available, in bytes, or "unlimited". * @cc.treturn number|"unlimited" The amount of free space available, in bytes, or "unlimited".
* @cc.since 1.4 * @cc.since 1.4
* @see #getCapacity To get the capacity of this drive.
*/ */
@LuaFunction @LuaFunction
public final Object getFreeSpace( String path ) throws LuaException public final Object getFreeSpace( String path ) throws LuaException
@ -512,10 +512,10 @@ public class FSAPI implements ILuaAPI
* @param path The path of the drive to get. * @param path The path of the drive to get.
* @return The drive's capacity. * @return The drive's capacity.
* @throws LuaException If the capacity cannot be determined. * @throws LuaException If the capacity cannot be determined.
* @see #getFreeSpace To get the free space available on this drive.
* @cc.treturn number|nil This drive's capacity. This will be nil for "read-only" drives, such as the ROM or * @cc.treturn number|nil This drive's capacity. This will be nil for "read-only" drives, such as the ROM or
* treasure disks. * treasure disks.
* @cc.since 1.87.0 * @cc.since 1.87.0
* @see #getFreeSpace To get the free space available on this drive.
*/ */
@LuaFunction @LuaFunction
public final Object getCapacity( String path ) throws LuaException public final Object getCapacity( String path ) throws LuaException
@ -544,11 +544,11 @@ public class FSAPI implements ILuaAPI
* @return The resulting attributes. * @return The resulting attributes.
* @throws LuaException If the path does not exist. * @throws LuaException If the path does not exist.
* @cc.treturn { size = number, isDir = boolean, isReadOnly = boolean, created = number, modified = number } The resulting attributes. * @cc.treturn { size = number, isDir = boolean, isReadOnly = boolean, created = number, modified = number } The resulting attributes.
* @see #getSize If you only care about the file's size.
* @see #isDir If you only care whether a path is a directory or not.
* @cc.since 1.87.0 * @cc.since 1.87.0
* @cc.changed 1.91.0 Renamed `modification` field to `modified`. * @cc.changed 1.91.0 Renamed `modification` field to `modified`.
* @cc.changed 1.95.2 Added `isReadOnly` to attributes. * @cc.changed 1.95.2 Added `isReadOnly` to attributes.
* @see #getSize If you only care about the file's size.
* @see #isDir If you only care whether a path is a directory or not.
*/ */
@LuaFunction @LuaFunction
public final Map<String, Object> attributes( String path ) throws LuaException public final Map<String, Object> attributes( String path ) throws LuaException

View File

@ -212,8 +212,8 @@ public class OSAPI implements ILuaAPI
* @return The ID of the new alarm. This can be used to filter the * @return The ID of the new alarm. This can be used to filter the
* {@code alarm} event, or {@link #cancelAlarm cancel the alarm}. * {@code alarm} event, or {@link #cancelAlarm cancel the alarm}.
* @throws LuaException If the time is out of range. * @throws LuaException If the time is out of range.
* @see #cancelAlarm To cancel an alarm.
* @cc.since 1.2 * @cc.since 1.2
* @see #cancelAlarm To cancel an alarm.
*/ */
@LuaFunction @LuaFunction
public final int setAlarm( double time ) throws LuaException public final int setAlarm( double time ) throws LuaException
@ -233,8 +233,8 @@ public class OSAPI implements ILuaAPI
* alarm from firing. * alarm from firing.
* *
* @param token The ID of the alarm to cancel. * @param token The ID of the alarm to cancel.
* @see #setAlarm To set an alarm.
* @cc.since 1.2 * @cc.since 1.2
* @see #setAlarm To set an alarm.
*/ */
@LuaFunction @LuaFunction
public final void cancelAlarm( int token ) public final void cancelAlarm( int token )
@ -330,7 +330,6 @@ public class OSAPI implements ILuaAPI
* @return The hour of the selected locale, or a UNIX timestamp from the table, depending on the argument passed in. * @return The hour of the selected locale, or a UNIX timestamp from the table, depending on the argument passed in.
* @throws LuaException If an invalid locale is passed. * @throws LuaException If an invalid locale is passed.
* @cc.tparam [opt] string|table locale The locale of the time, or a table filled by {@code os.date("*t")} to decode. Defaults to {@code ingame} locale if not specified. * @cc.tparam [opt] string|table locale The locale of the time, or a table filled by {@code os.date("*t")} to decode. Defaults to {@code ingame} locale if not specified.
* @see #date To get a date table that can be converted with this function.
* @cc.see textutils.formatTime To convert times into a user-readable string. * @cc.see textutils.formatTime To convert times into a user-readable string.
* @cc.usage Print the current in-game time. * @cc.usage Print the current in-game time.
* <pre>{@code * <pre>{@code
@ -340,6 +339,7 @@ public class OSAPI implements ILuaAPI
* @cc.changed 1.80pr1 Add support for getting the local local and UTC time. * @cc.changed 1.80pr1 Add support for getting the local local and UTC time.
* @cc.changed 1.82.0 Arguments are now case insensitive. * @cc.changed 1.82.0 Arguments are now case insensitive.
* @cc.changed 1.83.0 {@link #time(IArguments)} now accepts table arguments and converts them to UNIX timestamps. * @cc.changed 1.83.0 {@link #time(IArguments)} now accepts table arguments and converts them to UNIX timestamps.
* @see #date To get a date table that can be converted with this function.
*/ */
@LuaFunction @LuaFunction
public final Object time( IArguments args ) throws LuaException public final Object time( IArguments args ) throws LuaException

View File

@ -137,8 +137,8 @@ public class RedstoneAPI implements ILuaAPI
* *
* @param side The side to get. * @param side The side to get.
* @return The output signal strength, between 0 and 15. * @return The output signal strength, between 0 and 15.
* @see #setAnalogOutput
* @cc.since 1.51 * @cc.since 1.51
* @see #setAnalogOutput
*/ */
@LuaFunction( { "getAnalogOutput", "getAnalogueOutput" } ) @LuaFunction( { "getAnalogOutput", "getAnalogueOutput" } )
public final int getAnalogOutput( ComputerSide side ) public final int getAnalogOutput( ComputerSide side )

View File

@ -46,8 +46,8 @@ public class TermAPI extends TermMethods implements ILuaAPI
* @cc.treturn number The red channel, will be between 0 and 1. * @cc.treturn number The red channel, will be between 0 and 1.
* @cc.treturn number The green channel, will be between 0 and 1. * @cc.treturn number The green channel, will be between 0 and 1.
* @cc.treturn number The blue channel, will be between 0 and 1. * @cc.treturn number The blue channel, will be between 0 and 1.
* @see TermMethods#setPaletteColour(IArguments) To change the palette colour.
* @cc.since 1.81.0 * @cc.since 1.81.0
* @see TermMethods#setPaletteColour(IArguments) To change the palette colour.
*/ */
@LuaFunction( { "nativePaletteColour", "nativePaletteColor" } ) @LuaFunction( { "nativePaletteColour", "nativePaletteColor" } )
public final Object[] nativePaletteColour( int colour ) throws LuaException public final Object[] nativePaletteColour( int colour ) throws LuaException

View File

@ -5,6 +5,7 @@
*/ */
package dan200.computercraft.shared.computer.core; package dan200.computercraft.shared.computer.core;
import dan200.computercraft.shared.computer.upload.FileSlice;
import dan200.computercraft.shared.computer.upload.FileUpload; import dan200.computercraft.shared.computer.upload.FileUpload;
import net.minecraft.entity.player.ServerPlayerEntity; import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.inventory.container.Container; import net.minecraft.inventory.container.Container;
@ -12,6 +13,7 @@ import net.minecraft.inventory.container.Container;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.List; import java.util.List;
import java.util.UUID;
/** /**
* An instance of {@link Container} which provides a computer. You should implement this * An instance of {@link Container} which provides a computer. You should implement this
@ -38,12 +40,28 @@ public interface IContainerComputer
InputState getInput(); InputState getInput();
/** /**
* Attempt to upload a series of files to this computer. * Start a file upload into this container.
* *
* @param uploader The player uploading files. * @param uploadId The unique ID of this upload.
* @param files The files to upload. * @param files The files to upload.
*/ */
void upload( @Nonnull ServerPlayerEntity uploader, @Nonnull List<FileUpload> files ); void startUpload( @Nonnull UUID uploadId, @Nonnull List<FileUpload> files );
/**
* Append more data to partially uploaded files.
*
* @param uploadId The unique ID of this upload.
* @param slices Additional parts of file data to upload.
*/
void continueUpload( @Nonnull UUID uploadId, @Nonnull List<FileSlice> slices );
/**
* Finish off an upload. This either writes the uploaded files or
*
* @param uploader The player uploading files.
* @param uploadId The unique ID of this upload.
*/
void finishUpload( @Nonnull ServerPlayerEntity uploader, @Nonnull UUID uploadId );
/** /**
* Continue an upload. * Continue an upload.
@ -51,5 +69,5 @@ public interface IContainerComputer
* @param uploader The player uploading files. * @param uploader The player uploading files.
* @param overwrite Whether the files should be overwritten or not. * @param overwrite Whether the files should be overwritten or not.
*/ */
void continueUpload( @Nonnull ServerPlayerEntity uploader, boolean overwrite ); void confirmUpload( @Nonnull ServerPlayerEntity uploader, boolean overwrite );
} }

View File

@ -10,6 +10,7 @@ import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.filesystem.FileSystemException; import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.filesystem.FileSystemWrapper; import dan200.computercraft.core.filesystem.FileSystemWrapper;
import dan200.computercraft.shared.computer.core.*; import dan200.computercraft.shared.computer.core.*;
import dan200.computercraft.shared.computer.upload.FileSlice;
import dan200.computercraft.shared.computer.upload.FileUpload; import dan200.computercraft.shared.computer.upload.FileUpload;
import dan200.computercraft.shared.computer.upload.UploadResult; import dan200.computercraft.shared.computer.upload.UploadResult;
import dan200.computercraft.shared.network.NetworkHandler; import dan200.computercraft.shared.network.NetworkHandler;
@ -29,6 +30,7 @@ import java.nio.channels.WritableByteChannel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.StringJoiner; import java.util.StringJoiner;
import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -40,6 +42,8 @@ public class ContainerComputerBase extends Container implements IContainerComput
private final IComputer computer; private final IComputer computer;
private final ComputerFamily family; private final ComputerFamily family;
private final InputState input = new InputState( this ); private final InputState input = new InputState( this );
private UUID toUploadId;
private List<FileUpload> toUpload; private List<FileUpload> toUpload;
protected ContainerComputerBase( ContainerType<? extends ContainerComputerBase> type, int id, Predicate<PlayerEntity> canUse, IComputer computer, ComputerFamily family ) protected ContainerComputerBase( ContainerType<? extends ContainerComputerBase> type, int id, Predicate<PlayerEntity> canUse, IComputer computer, ComputerFamily family )
@ -92,25 +96,52 @@ public class ContainerComputerBase extends Container implements IContainerComput
} }
@Override @Override
public void upload( @Nonnull ServerPlayerEntity uploader, @Nonnull List<FileUpload> files ) public void startUpload( @Nonnull UUID uuid, @Nonnull List<FileUpload> files )
{ {
UploadResultMessage message = upload( files, false ); toUploadId = uuid;
toUpload = files;
}
@Override
public void continueUpload( @Nonnull UUID uploadId, @Nonnull List<FileSlice> slices )
{
if( toUploadId == null || toUpload == null || !toUploadId.equals( uploadId ) )
{
ComputerCraft.log.warn( "Invalid continueUpload call, skipping." );
return;
}
for( FileSlice slice : slices ) slice.apply( toUpload );
}
@Override
public void finishUpload( @Nonnull ServerPlayerEntity uploader, @Nonnull UUID uploadId )
{
if( toUploadId == null || toUpload == null || toUpload.isEmpty() || !toUploadId.equals( uploadId ) )
{
ComputerCraft.log.warn( "Invalid finishUpload call, skipping." );
return;
}
UploadResultMessage message = finishUpload( false );
NetworkHandler.sendToPlayer( uploader, message ); NetworkHandler.sendToPlayer( uploader, message );
} }
@Override @Override
public void continueUpload( @Nonnull ServerPlayerEntity uploader, boolean overwrite ) public void confirmUpload( @Nonnull ServerPlayerEntity uploader, boolean overwrite )
{ {
List<FileUpload> files = this.toUpload; if( toUploadId == null || toUpload == null || toUpload.isEmpty() )
toUpload = null; {
if( files == null || files.isEmpty() || !overwrite ) return; ComputerCraft.log.warn( "Invalid finishUpload call, skipping." );
return;
}
UploadResultMessage message = upload( files, true ); UploadResultMessage message = finishUpload( true );
NetworkHandler.sendToPlayer( uploader, message ); NetworkHandler.sendToPlayer( uploader, message );
} }
@Nonnull @Nonnull
private UploadResultMessage upload( @Nonnull List<FileUpload> files, boolean forceOverwrite ) private UploadResultMessage finishUpload( boolean forceOverwrite )
{ {
ServerComputer computer = (ServerComputer) getComputer(); ServerComputer computer = (ServerComputer) getComputer();
if( computer == null ) return UploadResultMessage.COMPUTER_OFF; if( computer == null ) return UploadResultMessage.COMPUTER_OFF;
@ -118,9 +149,20 @@ public class ContainerComputerBase extends Container implements IContainerComput
FileSystem fs = computer.getComputer().getEnvironment().getFileSystem(); FileSystem fs = computer.getComputer().getEnvironment().getFileSystem();
if( fs == null ) return UploadResultMessage.COMPUTER_OFF; if( fs == null ) return UploadResultMessage.COMPUTER_OFF;
for( FileUpload upload : toUpload )
{
if( !upload.checksumMatches() )
{
ComputerCraft.log.warn( "Checksum failed to match for {}.", upload.getName() );
return new UploadResultMessage( UploadResult.ERROR, new TranslationTextComponent( "gui.computercraft.upload.failed.corrupted" ) );
}
}
try try
{ {
List<String> overwrite = new ArrayList<>(); List<String> overwrite = new ArrayList<>();
List<FileUpload> files = toUpload;
toUpload = null;
for( FileUpload upload : files ) for( FileUpload upload : files )
{ {
if( !fs.exists( upload.getName() ) ) continue; if( !fs.exists( upload.getName() ) ) continue;
@ -139,7 +181,6 @@ public class ContainerComputerBase extends Container implements IContainerComput
{ {
StringJoiner joiner = new StringJoiner( LIST_PREFIX, LIST_PREFIX, "" ); StringJoiner joiner = new StringJoiner( LIST_PREFIX, LIST_PREFIX, "" );
for( String value : overwrite ) joiner.add( value ); for( String value : overwrite ) joiner.add( value );
toUpload = files; toUpload = files;
return new UploadResultMessage( return new UploadResultMessage(
UploadResult.CONFIRM_OVERWRITE, UploadResult.CONFIRM_OVERWRITE,
@ -167,7 +208,7 @@ public class ContainerComputerBase extends Container implements IContainerComput
catch( FileSystemException | IOException e ) catch( FileSystemException | IOException e )
{ {
ComputerCraft.log.error( "Error uploading files", e ); ComputerCraft.log.error( "Error uploading files", e );
return new UploadResultMessage( UploadResult.ERROR, new TranslationTextComponent( "computercraft.gui.upload.failed.generic", e.getMessage() ) ); return new UploadResultMessage( UploadResult.ERROR, new TranslationTextComponent( "gui.computercraft.upload.failed.generic", e.getMessage() ) );
} }
} }

View File

@ -0,0 +1,63 @@
/*
* 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 dan200.computercraft.ComputerCraft;
import java.nio.ByteBuffer;
import java.util.List;
public class FileSlice
{
private final int fileId;
private final int offset;
private final ByteBuffer bytes;
public FileSlice( int fileId, int offset, ByteBuffer bytes )
{
this.fileId = fileId;
this.offset = offset;
this.bytes = bytes;
}
public int getFileId()
{
return fileId;
}
public int getOffset()
{
return offset;
}
public ByteBuffer getBytes()
{
return bytes;
}
public void apply( List<FileUpload> files )
{
if( fileId < 0 || fileId >= files.size() )
{
ComputerCraft.log.warn( "File ID is out-of-bounds (0 <= {} < {})", fileId, files.size() );
return;
}
ByteBuffer file = files.get( fileId ).getBytes();
if( offset < 0 || offset + bytes.remaining() > file.capacity() )
{
ComputerCraft.log.warn( "File offset is out-of-bounds (0 <= {} <= {})", offset, file.capacity() - offset );
return;
}
bytes.rewind();
file.position( offset );
file.put( bytes );
file.rewind();
if( bytes.remaining() != 0 ) throw new IllegalStateException( "Should have read the whole buffer" );
}
}

View File

@ -5,26 +5,76 @@
*/ */
package dan200.computercraft.shared.computer.upload; package dan200.computercraft.shared.computer.upload;
import dan200.computercraft.ComputerCraft;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class FileUpload public class FileUpload
{ {
private final String name; public static final int CHECKSUM_LENGTH = 32;
private final ByteBuffer bytes;
public FileUpload( String name, ByteBuffer bytes ) private final String name;
private final int length;
private final ByteBuffer bytes;
private final byte[] checksum;
public FileUpload( String name, ByteBuffer bytes, byte[] checksum )
{ {
this.name = name; this.name = name;
this.bytes = bytes; this.bytes = bytes;
length = bytes.remaining();
this.checksum = checksum;
} }
@Nonnull
public String getName() public String getName()
{ {
return name; return name;
} }
@Nonnull
public ByteBuffer getBytes() public ByteBuffer getBytes()
{ {
return bytes; return bytes;
} }
public int getLength()
{
return length;
}
@Nonnull
public byte[] getChecksum()
{
return checksum;
}
public boolean checksumMatches()
{
// This is meant to be a checksum. Doesn't need to be cryptographically secure, hence non constant time.
byte[] digest = getDigest( bytes );
return digest != null && Arrays.equals( checksum, digest );
}
@Nullable
public static byte[] getDigest( ByteBuffer bytes )
{
try
{
bytes.rewind();
MessageDigest digest = MessageDigest.getInstance( "SHA-256" );
digest.update( bytes );
return digest.digest();
}
catch( NoSuchAlgorithmException e )
{
ComputerCraft.log.warn( "Failed to compute digest ({})", e.toString() );
return null;
}
}
} }

View File

@ -40,6 +40,6 @@ public class ContinueUploadMessage extends ComputerServerMessage
protected void handle( NetworkEvent.Context context, @Nonnull ServerComputer computer, @Nonnull IContainerComputer container ) protected void handle( NetworkEvent.Context context, @Nonnull ServerComputer computer, @Nonnull IContainerComputer container )
{ {
ServerPlayerEntity player = context.getSender(); ServerPlayerEntity player = context.getSender();
if( player != null ) container.continueUpload( player, overwrite ); if( player != null ) container.confirmUpload( player, overwrite );
} }
} }

View File

@ -5,9 +5,13 @@
*/ */
package dan200.computercraft.shared.network.server; package dan200.computercraft.shared.network.server;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.computer.core.IContainerComputer; import dan200.computercraft.shared.computer.core.IContainerComputer;
import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.upload.FileSlice;
import dan200.computercraft.shared.computer.upload.FileUpload; import dan200.computercraft.shared.computer.upload.FileUpload;
import dan200.computercraft.shared.network.NetworkHandler;
import io.netty.handler.codec.DecoderException;
import net.minecraft.entity.player.ServerPlayerEntity; import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.network.PacketBuffer; import net.minecraft.network.PacketBuffer;
import net.minecraftforge.fml.network.NetworkEvent; import net.minecraftforge.fml.network.NetworkEvent;
@ -16,34 +20,81 @@ import javax.annotation.Nonnull;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID;
public class UploadFileMessage extends ComputerServerMessage public class UploadFileMessage extends ComputerServerMessage
{ {
public static final int MAX_SIZE = 30 * 1024; // Max packet size is 32767. TODO: Bump this in the future public static final int MAX_SIZE = 512 * 1024;
private final List<FileUpload> files; static final int MAX_PACKET_SIZE = 30 * 1024; // Max packet size is 32767.
public UploadFileMessage( int instanceId, List<FileUpload> files ) public static final int MAX_FILES = 32;
public static final int MAX_FILE_NAME = 128;
private static final int FLAG_FIRST = 1;
private static final int FLAG_LAST = 2;
private final UUID uuid;
private final int flag;
private final List<FileUpload> files;
private final List<FileSlice> slices;
UploadFileMessage( int instanceId, UUID uuid, int flag, List<FileUpload> files, List<FileSlice> slices )
{ {
super( instanceId ); super( instanceId );
this.uuid = uuid;
this.flag = flag;
this.files = files; this.files = files;
this.slices = slices;
} }
public UploadFileMessage( @Nonnull PacketBuffer buf ) public UploadFileMessage( @Nonnull PacketBuffer buf )
{ {
super( buf ); super( buf );
int nFiles = buf.readVarInt(); uuid = buf.readUUID();
List<FileUpload> files = this.files = new ArrayList<>( nFiles ); int flag = this.flag = buf.readByte();
for( int i = 0; i < nFiles; i++ )
int totalSize = 0;
if( (flag & FLAG_FIRST) != 0 )
{ {
String name = buf.readUtf( 32767 ); int nFiles = buf.readVarInt();
int size = buf.readVarInt(); if( nFiles >= MAX_FILES ) throw new DecoderException( "Too many files" );
if( size > MAX_SIZE ) break;
List<FileUpload> files = this.files = new ArrayList<>( nFiles );
for( int i = 0; i < nFiles; i++ )
{
String name = buf.readUtf( MAX_FILE_NAME );
int size = buf.readVarInt();
if( size > MAX_SIZE || (totalSize += size) >= MAX_SIZE )
{
throw new DecoderException( "Files are too large" );
}
byte[] digest = new byte[FileUpload.CHECKSUM_LENGTH];
buf.readBytes( digest );
files.add( new FileUpload( name, ByteBuffer.allocateDirect( size ), digest ) );
}
}
else
{
files = null;
}
int nSlices = buf.readVarInt();
List<FileSlice> slices = this.slices = new ArrayList<>( nSlices );
for( int i = 0; i < nSlices; i++ )
{
int fileId = buf.readUnsignedByte();
int offset = buf.readVarInt();
int size = buf.readUnsignedShort();
if( size > MAX_PACKET_SIZE ) throw new DecoderException( "File is too large" );
ByteBuffer buffer = ByteBuffer.allocateDirect( size ); ByteBuffer buffer = ByteBuffer.allocateDirect( size );
buf.readBytes( buffer ); buf.readBytes( buffer );
buffer.flip(); buffer.flip();
files.add( new FileUpload( name, buffer ) ); slices.add( new FileSlice( fileId, offset, buffer ) );
} }
} }
@ -51,19 +102,85 @@ public class UploadFileMessage extends ComputerServerMessage
public void toBytes( @Nonnull PacketBuffer buf ) public void toBytes( @Nonnull PacketBuffer buf )
{ {
super.toBytes( buf ); super.toBytes( buf );
buf.writeVarInt( files.size() ); buf.writeUUID( uuid );
for( FileUpload file : files ) buf.writeByte( flag );
if( (flag & FLAG_FIRST) != 0 )
{ {
buf.writeUtf( file.getName() ); buf.writeVarInt( files.size() );
buf.writeVarInt( file.getBytes().remaining() ); for( FileUpload file : files )
buf.writeBytes( file.getBytes() ); {
buf.writeUtf( file.getName(), MAX_FILE_NAME );
buf.writeVarInt( file.getLength() );
buf.writeBytes( file.getChecksum() );
}
} }
buf.writeVarInt( slices.size() );
for( FileSlice slice : slices )
{
buf.writeByte( slice.getFileId() );
buf.writeVarInt( slice.getOffset() );
slice.getBytes().rewind();
buf.writeShort( slice.getBytes().remaining() );
buf.writeBytes( slice.getBytes() );
}
}
public static void send( int instanceId, List<FileUpload> files )
{
UUID uuid = UUID.randomUUID();
int remaining = MAX_PACKET_SIZE;
for( FileUpload file : files ) remaining -= file.getName().length() * 4 + FileUpload.CHECKSUM_LENGTH;
boolean first = true;
List<FileSlice> slices = new ArrayList<>( files.size() );
for( int fileId = 0; fileId < files.size(); fileId++ )
{
FileUpload file = files.get( fileId );
ByteBuffer contents = file.getBytes();
int capacity = contents.limit();
int currentOffset = 0;
while( currentOffset < capacity )
{
if( remaining <= 0 )
{
NetworkHandler.sendToServer( first
? new UploadFileMessage( instanceId, uuid, FLAG_FIRST, files, new ArrayList<>( slices ) )
: new UploadFileMessage( instanceId, uuid, 0, null, new ArrayList<>( slices ) ) );
slices.clear();
remaining = MAX_PACKET_SIZE;
first = false;
}
int canWrite = Math.min( remaining, capacity - currentOffset );
ComputerCraft.log.info( "Adding slice from {} to {}", currentOffset, currentOffset + canWrite - 1 );
contents.position( currentOffset ).limit( currentOffset + canWrite );
slices.add( new FileSlice( fileId, currentOffset, contents.slice() ) );
currentOffset += canWrite;
}
contents.position( 0 ).limit( capacity );
}
NetworkHandler.sendToServer( first
? new UploadFileMessage( instanceId, uuid, FLAG_FIRST | FLAG_LAST, files, new ArrayList<>( slices ) )
: new UploadFileMessage( instanceId, uuid, FLAG_LAST, null, new ArrayList<>( slices ) ) );
} }
@Override @Override
protected void handle( NetworkEvent.Context context, @Nonnull ServerComputer computer, @Nonnull IContainerComputer container ) protected void handle( NetworkEvent.Context context, @Nonnull ServerComputer computer, @Nonnull IContainerComputer container )
{ {
ServerPlayerEntity player = context.getSender(); ServerPlayerEntity player = context.getSender();
if( player != null ) container.upload( player, files ); if( player != null )
{
if( (flag & FLAG_FIRST) != 0 ) container.startUpload( uuid, files );
container.continueUpload( uuid, slices );
if( (flag & FLAG_LAST) != 0 ) container.finishUpload( player, uuid );
}
} }
} }

View File

@ -212,8 +212,8 @@ public class TurtleAPI implements ILuaAPI
* @cc.tparam [opt] string text When placing a sign, set its contents to this text. * @cc.tparam [opt] string text When placing a sign, set its contents to this text.
* @cc.treturn boolean Whether the block could be placed. * @cc.treturn boolean Whether the block could be placed.
* @cc.treturn string|nil The reason the block was not placed. * @cc.treturn string|nil The reason the block was not placed.
* @see #place For more information about placing items.
* @cc.since 1.4 * @cc.since 1.4
* @see #place For more information about placing items.
*/ */
@LuaFunction @LuaFunction
public final MethodResult placeUp( IArguments args ) public final MethodResult placeUp( IArguments args )
@ -229,8 +229,8 @@ public class TurtleAPI implements ILuaAPI
* @cc.tparam [opt] string text When placing a sign, set its contents to this text. * @cc.tparam [opt] string text When placing a sign, set its contents to this text.
* @cc.treturn boolean Whether the block could be placed. * @cc.treturn boolean Whether the block could be placed.
* @cc.treturn string|nil The reason the block was not placed. * @cc.treturn string|nil The reason the block was not placed.
* @see #place For more information about placing items.
* @cc.since 1.4 * @cc.since 1.4
* @see #place For more information about placing items.
*/ */
@LuaFunction @LuaFunction
public final MethodResult placeDown( IArguments args ) public final MethodResult placeDown( IArguments args )
@ -247,8 +247,8 @@ public class TurtleAPI implements ILuaAPI
* @throws LuaException If dropping an invalid number of items. * @throws LuaException If dropping an invalid number of items.
* @cc.treturn boolean Whether items were dropped. * @cc.treturn boolean Whether items were dropped.
* @cc.treturn string|nil The reason the no items were dropped. * @cc.treturn string|nil The reason the no items were dropped.
* @see #select
* @cc.since 1.31 * @cc.since 1.31
* @see #select
*/ */
@LuaFunction @LuaFunction
public final MethodResult drop( Optional<Integer> count ) throws LuaException public final MethodResult drop( Optional<Integer> count ) throws LuaException
@ -265,8 +265,8 @@ public class TurtleAPI implements ILuaAPI
* @throws LuaException If dropping an invalid number of items. * @throws LuaException If dropping an invalid number of items.
* @cc.treturn boolean Whether items were dropped. * @cc.treturn boolean Whether items were dropped.
* @cc.treturn string|nil The reason the no items were dropped. * @cc.treturn string|nil The reason the no items were dropped.
* @see #select
* @cc.since 1.4 * @cc.since 1.4
* @see #select
*/ */
@LuaFunction @LuaFunction
public final MethodResult dropUp( Optional<Integer> count ) throws LuaException public final MethodResult dropUp( Optional<Integer> count ) throws LuaException
@ -283,8 +283,8 @@ public class TurtleAPI implements ILuaAPI
* @throws LuaException If dropping an invalid number of items. * @throws LuaException If dropping an invalid number of items.
* @cc.treturn boolean Whether items were dropped. * @cc.treturn boolean Whether items were dropped.
* @cc.treturn string|nil The reason the no items were dropped. * @cc.treturn string|nil The reason the no items were dropped.
* @see #select
* @cc.since 1.4 * @cc.since 1.4
* @see #select
*/ */
@LuaFunction @LuaFunction
public final MethodResult dropDown( Optional<Integer> count ) throws LuaException public final MethodResult dropDown( Optional<Integer> count ) throws LuaException
@ -528,9 +528,9 @@ public class TurtleAPI implements ILuaAPI
* @return The fuel level, or "unlimited". * @return The fuel level, or "unlimited".
* @cc.treturn [1] number The current amount of fuel a turtle this turtle has. * @cc.treturn [1] number The current amount of fuel a turtle this turtle has.
* @cc.treturn [2] "unlimited" If turtles do not consume fuel when moving. * @cc.treturn [2] "unlimited" If turtles do not consume fuel when moving.
* @cc.since 1.4
* @see #getFuelLimit() * @see #getFuelLimit()
* @see #refuel(Optional) * @see #refuel(Optional)
* @cc.since 1.4
*/ */
@LuaFunction @LuaFunction
public final Object getFuelLevel() public final Object getFuelLevel()
@ -571,9 +571,9 @@ public class TurtleAPI implements ILuaAPI
* local is_fuel, reason = turtle.refuel(0) * local is_fuel, reason = turtle.refuel(0)
* if not is_fuel then printError(reason) end * if not is_fuel then printError(reason) end
* }</pre> * }</pre>
* @cc.since 1.4
* @see #getFuelLevel() * @see #getFuelLevel()
* @see #getFuelLimit() * @see #getFuelLimit()
* @cc.since 1.4
*/ */
@LuaFunction @LuaFunction
public final MethodResult refuel( Optional<Integer> countA ) throws LuaException public final MethodResult refuel( Optional<Integer> countA ) throws LuaException
@ -621,8 +621,8 @@ public class TurtleAPI implements ILuaAPI
* Get the currently selected slot. * Get the currently selected slot.
* *
* @return The current slot. * @return The current slot.
* @see #select
* @cc.since 1.6 * @cc.since 1.6
* @see #select
*/ */
@LuaFunction @LuaFunction
public final int getSelectedSlot() public final int getSelectedSlot()
@ -638,9 +638,9 @@ public class TurtleAPI implements ILuaAPI
* @return The limit, or "unlimited". * @return The limit, or "unlimited".
* @cc.treturn [1] number The maximum amount of fuel a turtle can hold. * @cc.treturn [1] number The maximum amount of fuel a turtle can hold.
* @cc.treturn [2] "unlimited" If turtles do not consume fuel when moving. * @cc.treturn [2] "unlimited" If turtles do not consume fuel when moving.
* @cc.since 1.6
* @see #getFuelLevel() * @see #getFuelLevel()
* @see #refuel(Optional) * @see #refuel(Optional)
* @cc.since 1.6
*/ */
@LuaFunction @LuaFunction
public final Object getFuelLimit() public final Object getFuelLimit()

View File

@ -123,8 +123,11 @@
"gui.computercraft.upload.failed.out_of_space": "Not enough space on the computer for these files.", "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.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.too_much": "Your files are too large to be uploaded.",
"gui.computercraft.upload.failed.name_too_long": "File names are too long to be uploaded.",
"gui.computercraft.upload.failed.too_many_files": "Cannot upload this many files.",
"gui.computercraft.upload.failed.overwrite_dir": "Cannot upload %s, as there is already a directory with the same name.", "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.failed.generic": "Uploading files failed (%s)",
"gui.computercraft.upload.failed.corrupted": "Files corrupted when uploading. Please try again.",
"gui.computercraft.upload.overwrite": "Files would be overwritten", "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.detail": "The following files will be overwritten when uploading. Continue?%s",
"gui.computercraft.upload.overwrite_button": "Overwrite" "gui.computercraft.upload.overwrite_button": "Overwrite"

View File

@ -103,7 +103,7 @@
"gui.computercraft.upload.failed.out_of_space": "これらのファイルに必要なスペースがコンピュータ上にありません。", "gui.computercraft.upload.failed.out_of_space": "これらのファイルに必要なスペースがコンピュータ上にありません。",
"gui.computercraft.upload.failed.too_much": "アップロードするにはファイルが大きスギます。", "gui.computercraft.upload.failed.too_much": "アップロードするにはファイルが大きスギます。",
"gui.computercraft.upload.failed.overwrite_dir": "同じ名前のディレクトリがすでにあるため、%s をアップロードできません。", "gui.computercraft.upload.failed.overwrite_dir": "同じ名前のディレクトリがすでにあるため、%s をアップロードできません。",
"computercraft.gui.upload.failed.generic": "ファイルのアップロードに失敗しました(%s)", "gui.computercraft.upload.failed.generic": "ファイルのアップロードに失敗しました(%s)",
"gui.computercraft.upload.overwrite": "ファイルは上書きされます", "gui.computercraft.upload.overwrite": "ファイルは上書きされます",
"gui.computercraft.upload.overwrite_button": "上書き", "gui.computercraft.upload.overwrite_button": "上書き",
"block.computercraft.wireless_modem_normal": "無線モデム", "block.computercraft.wireless_modem_normal": "無線モデム",

View File

@ -124,7 +124,7 @@
"gui.computercraft.upload.failed.computer_off": "Ты должен включить компьютер перед загрузой файлов.", "gui.computercraft.upload.failed.computer_off": "Ты должен включить компьютер перед загрузой файлов.",
"gui.computercraft.upload.failed.too_much": "Твои файлы слишком большие для загрузки.", "gui.computercraft.upload.failed.too_much": "Твои файлы слишком большие для загрузки.",
"gui.computercraft.upload.failed.overwrite_dir": "Нельзя загрузить %s, поскольку папка с таким же названием уже существует.", "gui.computercraft.upload.failed.overwrite_dir": "Нельзя загрузить %s, поскольку папка с таким же названием уже существует.",
"computercraft.gui.upload.failed.generic": "Загрузка файлов не удалась (%s)", "gui.computercraft.upload.failed.generic": "Загрузка файлов не удалась (%s)",
"gui.computercraft.upload.overwrite": "Файлы будут перезаписаны", "gui.computercraft.upload.overwrite": "Файлы будут перезаписаны",
"gui.computercraft.upload.overwrite.detail": "При загрузке следующие файлы будут перезаписаны. Продолжить?%s", "gui.computercraft.upload.overwrite.detail": "При загрузке следующие файлы будут перезаписаны. Продолжить?%s",
"gui.computercraft.upload.overwrite_button": "Перезаписать" "gui.computercraft.upload.overwrite_button": "Перезаписать"