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:
parent
340ade170f
commit
9e82209aab
@ -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 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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 )
|
||||||
|
@ -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
|
||||||
|
@ -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 );
|
||||||
}
|
}
|
||||||
|
@ -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() ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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" );
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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"
|
||||||
|
@ -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": "無線モデム",
|
||||||
|
@ -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": "Перезаписать"
|
||||||
|
Loading…
Reference in New Issue
Block a user