From 5fa01f8b9608dc11776f325510b4a7619c72830e Mon Sep 17 00:00:00 2001 From: SquidDev Date: Fri, 16 Nov 2018 13:02:48 +0000 Subject: [PATCH] Truncate files when writing them This is performed by default, but as we don't pass any options, this flag is removed. Closes #75 --- .gitignore | 12 +-- .../core/filesystem/FileMount.java | 18 ++-- .../computercraft/core/FileSystemTest.java | 58 ++++++++++++ .../core/apis/ObjectWrapper.java | 90 +++++++++++++++++++ 4 files changed, 167 insertions(+), 11 deletions(-) create mode 100644 src/test/java/dan200/computercraft/core/FileSystemTest.java create mode 100644 src/test/java/dan200/computercraft/core/apis/ObjectWrapper.java diff --git a/.gitignore b/.gitignore index c6aaace67..f4810fde1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,12 @@ -build -out -run -deploy +/build +/out +/run *.ipr *.iws *.iml .idea .gradle -luaj-2.0.3/lib -luaj-2.0.3/*.jar +/luaj-2.0.3/lib +/luaj-2.0.3/*.jar *.DS_Store +/test-files diff --git a/src/main/java/dan200/computercraft/core/filesystem/FileMount.java b/src/main/java/dan200/computercraft/core/filesystem/FileMount.java index 98ee516f9..135aa8885 100644 --- a/src/main/java/dan200/computercraft/core/filesystem/FileMount.java +++ b/src/main/java/dan200/computercraft/core/filesystem/FileMount.java @@ -6,6 +6,7 @@ package dan200.computercraft.core.filesystem; +import com.google.common.collect.Sets; import dan200.computercraft.api.filesystem.IWritableMount; import javax.annotation.Nonnull; @@ -13,12 +14,18 @@ import java.nio.ByteBuffer; import java.nio.channels.*; import java.nio.file.Files; +import java.nio.file.OpenOption; import java.nio.file.StandardOpenOption; +import java.util.Collections; import java.util.List; +import java.util.Set; public class FileMount implements IWritableMount { private static final int MINIMUM_FILE_SIZE = 500; + private static final Set READ_OPTIONS = Collections.singleton( StandardOpenOption.READ ); + private static final Set WRITE_OPTIONS = Sets.newHashSet( StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING ); + private static final Set APPEND_OPTIONS = Sets.newHashSet( StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.APPEND ); private class WritableCountingChannel implements WritableByteChannel { @@ -252,7 +259,7 @@ public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOE File file = getRealPath( path ); if( file.exists() && !file.isDirectory() ) { - return FileChannel.open( file.toPath(), StandardOpenOption.READ ); + return FileChannel.open( file.toPath(), READ_OPTIONS ); } } throw new IOException( "/" + path + ": No such file" ); @@ -386,8 +393,7 @@ public WritableByteChannel openChannelForWrite( @Nonnull String path ) throws IO m_usedSpace -= Math.max( file.length(), MINIMUM_FILE_SIZE ); m_usedSpace += MINIMUM_FILE_SIZE; } - return new SeekableCountingChannel( Files.newByteChannel( file.toPath(), StandardOpenOption.WRITE, StandardOpenOption.CREATE ), - MINIMUM_FILE_SIZE ); + return new SeekableCountingChannel( Files.newByteChannel( file.toPath(), WRITE_OPTIONS ), MINIMUM_FILE_SIZE ); } } @@ -409,8 +415,10 @@ else if( file.isDirectory() ) else { // Allowing seeking when appending is not recommended, so we use a separate channel. - return new WritableCountingChannel( Files.newByteChannel( file.toPath(), StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.APPEND ), - Math.max( MINIMUM_FILE_SIZE - file.length(), 0 ) ); + return new WritableCountingChannel( + Files.newByteChannel( file.toPath(), APPEND_OPTIONS ), + Math.max( MINIMUM_FILE_SIZE - file.length(), 0 ) + ); } } else diff --git a/src/test/java/dan200/computercraft/core/FileSystemTest.java b/src/test/java/dan200/computercraft/core/FileSystemTest.java new file mode 100644 index 000000000..508e3c075 --- /dev/null +++ b/src/test/java/dan200/computercraft/core/FileSystemTest.java @@ -0,0 +1,58 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ + +package dan200.computercraft.core; + +import com.google.common.io.Files; +import dan200.computercraft.api.filesystem.IWritableMount; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.core.apis.ObjectWrapper; +import dan200.computercraft.core.apis.handles.EncodedWritableHandle; +import dan200.computercraft.core.filesystem.FileMount; +import dan200.computercraft.core.filesystem.FileSystem; +import dan200.computercraft.core.filesystem.FileSystemException; +import dan200.computercraft.core.filesystem.FileSystemWrapper; +import org.junit.Test; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static org.junit.Assert.assertEquals; + +public class FileSystemTest +{ + private static final File ROOT = new File( "test-files/filesystem" ); + + /** + * Ensures writing a file truncates it. + */ + @Test + public void testWriteTruncates() throws FileSystemException, LuaException, IOException + { + IWritableMount writableMount = new FileMount( ROOT, 1000000 ); + FileSystem fs = new FileSystem( "hdd", writableMount ); + + { + FileSystemWrapper writer = fs.openForWrite( "out.txt", false, EncodedWritableHandle::openUtf8 ); + ObjectWrapper wrapper = new ObjectWrapper( new EncodedWritableHandle( writer.get(), writer ) ); + wrapper.call( "write", "This is a long line" ); + wrapper.call( "close" ); + } + + assertEquals( "This is a long line", Files.toString( new File( ROOT, "out.txt" ), StandardCharsets.UTF_8 ) ); + + { + FileSystemWrapper writer = fs.openForWrite( "out.txt", false, EncodedWritableHandle::openUtf8 ); + ObjectWrapper wrapper = new ObjectWrapper( new EncodedWritableHandle( writer.get(), writer ) ); + wrapper.call( "write", "Tiny line" ); + wrapper.call( "close" ); + } + + assertEquals( "Tiny line", Files.toString( new File( ROOT, "out.txt" ), StandardCharsets.UTF_8 ) ); + } +} diff --git a/src/test/java/dan200/computercraft/core/apis/ObjectWrapper.java b/src/test/java/dan200/computercraft/core/apis/ObjectWrapper.java new file mode 100644 index 000000000..781a62547 --- /dev/null +++ b/src/test/java/dan200/computercraft/core/apis/ObjectWrapper.java @@ -0,0 +1,90 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ + +package dan200.computercraft.core.apis; + +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.ILuaObject; +import dan200.computercraft.api.lua.ILuaTask; +import dan200.computercraft.api.lua.LuaException; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ObjectWrapper implements ILuaContext +{ + private final ILuaObject object; + private final String[] methods; + + public ObjectWrapper( ILuaObject object ) + { + this.object = object; + this.methods = object.getMethodNames(); + } + + private int findMethod( String method ) + { + for( int i = 0; i < methods.length; i++ ) + { + if( method.equals( methods[i] ) ) return i; + } + return -1; + } + + public boolean hasMethod( String method ) + { + return findMethod( method ) >= 0; + } + + public Object[] call( String name, Object... args ) throws LuaException + { + int method = findMethod( name ); + if( method < 0 ) throw new IllegalStateException( "No such method '" + name + "'" ); + + try + { + return object.callMethod( this, method, args ); + } + catch( InterruptedException e ) + { + throw new IllegalStateException( "Should never be interrupted", e ); + } + } + + @Nonnull + @Override + public Object[] pullEvent( @Nullable String filter ) + { + throw new IllegalStateException( "Method should never yield" ); + } + + @Nonnull + @Override + public Object[] pullEventRaw( @Nullable String filter ) + { + throw new IllegalStateException( "Method should never yield" ); + } + + @Nonnull + @Override + public Object[] yield( @Nullable Object[] arguments ) + { + throw new IllegalStateException( "Method should never yield" ); + } + + @Nullable + @Override + public Object[] executeMainThreadTask( @Nonnull ILuaTask task ) + { + throw new IllegalStateException( "Method should never yield" ); + } + + @Override + public long issueMainThreadTask( @Nonnull ILuaTask task ) + { + throw new IllegalStateException( "Method should never queue events" ); + } +}