From 6ccffe9742b7891598860475206fcb8caecdaa75 Mon Sep 17 00:00:00 2001 From: SquidDev Date: Fri, 12 May 2017 22:49:44 +0100 Subject: [PATCH] Refactor the filesystem and HTTP code - Move the encoding/decoding from the Filesystem implementation to the individual handles. - Move each handle into an core.apis.handles package from the main fs API. - Move the HTTP response to inherit from these handles. - Allow binary handles' read function to accept a number, specifying how many characters to read - these will be returned as a Lua string. - Add readAll to binary handles - Allow binary handles' write function to accept a string which is decoded into the individual bytes. - Add "binary" argument to http.request and friends in order to return a binary handle. - Ensure file handles are open when reading from/writing to them. - Return the error message when opening a file fails. --- .../dan200/computercraft/core/apis/FSAPI.java | 318 +----------------- .../computercraft/core/apis/HTTPAPI.java | 108 +++--- .../computercraft/core/apis/HTTPRequest.java | 80 ++--- .../core/apis/handles/BinaryInputHandle.java | 93 +++++ .../core/apis/handles/BinaryOutputHandle.java | 82 +++++ .../core/apis/handles/EncodedInputHandle.java | 105 ++++++ .../apis/handles/EncodedOutputHandle.java | 124 +++++++ .../core/apis/handles/HandleGeneric.java | 35 ++ .../core/filesystem/FileSystem.java | 210 +++--------- .../core/filesystem/IMountedFile.java | 13 - .../core/filesystem/IMountedFileBinary.java | 16 - .../core/filesystem/IMountedFileNormal.java | 16 - .../core/lua/LuaJLuaMachine.java | 6 + .../computercraft/shared/util/StringUtil.java | 13 + .../assets/computercraft/lua/bios.lua | 16 +- 15 files changed, 593 insertions(+), 642 deletions(-) create mode 100644 src/main/java/dan200/computercraft/core/apis/handles/BinaryInputHandle.java create mode 100644 src/main/java/dan200/computercraft/core/apis/handles/BinaryOutputHandle.java create mode 100644 src/main/java/dan200/computercraft/core/apis/handles/EncodedInputHandle.java create mode 100644 src/main/java/dan200/computercraft/core/apis/handles/EncodedOutputHandle.java create mode 100644 src/main/java/dan200/computercraft/core/apis/handles/HandleGeneric.java delete mode 100644 src/main/java/dan200/computercraft/core/filesystem/IMountedFile.java delete mode 100644 src/main/java/dan200/computercraft/core/filesystem/IMountedFileBinary.java delete mode 100644 src/main/java/dan200/computercraft/core/filesystem/IMountedFileNormal.java diff --git a/src/main/java/dan200/computercraft/core/apis/FSAPI.java b/src/main/java/dan200/computercraft/core/apis/FSAPI.java index 9ba39144a..d4d38a1d9 100644 --- a/src/main/java/dan200/computercraft/core/apis/FSAPI.java +++ b/src/main/java/dan200/computercraft/core/apis/FSAPI.java @@ -7,15 +7,17 @@ package dan200.computercraft.core.apis; import dan200.computercraft.api.lua.ILuaContext; -import dan200.computercraft.api.lua.ILuaObject; import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.core.apis.handles.BinaryInputHandle; +import dan200.computercraft.core.apis.handles.BinaryOutputHandle; +import dan200.computercraft.core.apis.handles.EncodedInputHandle; +import dan200.computercraft.core.apis.handles.EncodedOutputHandle; import dan200.computercraft.core.filesystem.FileSystem; import dan200.computercraft.core.filesystem.FileSystemException; -import dan200.computercraft.core.filesystem.IMountedFileBinary; -import dan200.computercraft.core.filesystem.IMountedFileNormal; import javax.annotation.Nonnull; -import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.HashMap; import java.util.Map; @@ -259,33 +261,33 @@ public class FSAPI implements ILuaAPI try { if( mode.equals( "r" ) ) { // Open the file for reading, then create a wrapper around the reader - IMountedFileNormal reader = m_fileSystem.openForRead( path ); - return wrapBufferedReader( reader ); + InputStream reader = m_fileSystem.openForRead( path ); + return new Object[] { new EncodedInputHandle( reader ) }; } else if( mode.equals( "w" ) ) { // Open the file for writing, then create a wrapper around the writer - IMountedFileNormal writer = m_fileSystem.openForWrite( path, false ); - return wrapBufferedWriter( writer ); + OutputStream writer = m_fileSystem.openForWrite( path, false ); + return new Object[] { new EncodedOutputHandle( writer ) }; } else if( mode.equals( "a" ) ) { // Open the file for appending, then create a wrapper around the writer - IMountedFileNormal writer = m_fileSystem.openForWrite( path, true ); - return wrapBufferedWriter( writer ); + OutputStream writer = m_fileSystem.openForWrite( path, true ); + return new Object[] { new EncodedOutputHandle( writer ) }; } else if( mode.equals( "rb" ) ) { // Open the file for binary reading, then create a wrapper around the reader - IMountedFileBinary reader = m_fileSystem.openForBinaryRead( path ); - return wrapInputStream( reader ); + InputStream reader = m_fileSystem.openForRead( path ); + return new Object[] { new BinaryInputHandle( reader ) }; } else if( mode.equals( "wb" ) ) { // Open the file for binary writing, then create a wrapper around the writer - IMountedFileBinary writer = m_fileSystem.openForBinaryWrite( path, false ); - return wrapOutputStream( writer ); + OutputStream writer = m_fileSystem.openForWrite( path, false ); + return new Object[] { new BinaryOutputHandle( writer ) }; } else if( mode.equals( "ab" ) ) { // Open the file for binary appending, then create a wrapper around the reader - IMountedFileBinary writer = m_fileSystem.openForBinaryWrite( path, true ); - return wrapOutputStream( writer ); + OutputStream writer = m_fileSystem.openForWrite( path, true ); + return new Object[] { new BinaryOutputHandle( writer ) }; } else { throw new LuaException( "Unsupported mode" ); @@ -368,288 +370,4 @@ public class FSAPI implements ILuaAPI } } } - - private static Object[] wrapBufferedReader( final IMountedFileNormal reader ) - { - return new Object[] { new ILuaObject() { - private boolean open = true; - - @Nonnull - @Override - public String[] getMethodNames() - { - return new String[] { - "readLine", - "readAll", - "close" - }; - } - - @Override - public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException - { - switch( method ) - { - case 0: - { - // readLine - if( !open ) throw new LuaException( "attempt to use a closed file" ); - try { - String line = reader.readLine(); - if( line != null ) { - return new Object[] { line }; - } else { - return null; - } - } catch( IOException e ) { - return null; - } - } - case 1: - { - // readAll - if( !open ) throw new LuaException( "attempt to use a closed file" ); - try { - StringBuilder result = new StringBuilder( "" ); - String line = reader.readLine(); - while( line != null ) { - result.append( line ); - line = reader.readLine(); - if( line != null ) { - result.append( "\n" ); - } - } - return new Object[] { result.toString() }; - } catch( IOException e ) { - return null; - } - } - case 2: - { - // close - try { - reader.close(); - open = false; - return null; - } catch( IOException e ) { - return null; - } - } - default: - { - return null; - } - } - } - } }; - } - - private static Object[] wrapBufferedWriter( final IMountedFileNormal writer ) - { - return new Object[] { new ILuaObject() { - private boolean open = true; - - @Nonnull - @Override - public String[] getMethodNames() - { - return new String[] { - "write", - "writeLine", - "close", - "flush" - }; - } - - @Override - public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException - { - switch( method ) - { - case 0: - { - // write - if( !open ) throw new LuaException( "attempt to use a closed file" ); - String text; - if( args.length > 0 && args[0] != null ) { - text = args[0].toString(); - } else { - text = ""; - } - try { - writer.write( text, 0, text.length(), false ); - return null; - } catch( IOException e ) { - throw new LuaException( e.getMessage() ); - } - } - case 1: - { - // writeLine - if( !open ) throw new LuaException( "attempt to use a closed file" ); - - String text; - if( args.length > 0 && args[0] != null ) { - text = args[0].toString(); - } else { - text = ""; - } - try { - writer.write( text, 0, text.length(), true ); - return null; - } catch( IOException e ) { - throw new LuaException( e.getMessage() ); - } - } - case 2: - { - // close - try { - writer.close(); - open = false; - return null; - } catch( IOException e ) { - return null; - } - } - case 3: - { - try { - if( !open ) throw new LuaException( "attempt to use a closed file" ); - writer.flush(); - return null; - } catch ( IOException e ) { - return null; - } - } - default: - { - assert( false ); - return null; - } - } - } - } }; - } - - private static Object[] wrapInputStream( final IMountedFileBinary reader ) - { - - return new Object[] { new ILuaObject() { - private boolean open = true; - - @Nonnull - @Override - public String[] getMethodNames() { - return new String[] { - "read", - "close" - }; - } - - @Override - public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args) throws LuaException { - switch( method ) - { - case 0: - { - // read - if( !open ) throw new LuaException( "attempt to use a closed file" ); - try { - int b = reader.read(); - if( b != -1 ) { - return new Object[] { b }; - } else { - return null; - } - } catch( IOException e ) { - return null; - } - } - case 1: - { - //close - try { - reader.close(); - open = false; - return null; - } catch( IOException e ) { - return null; - } - } - default: - { - assert( false ); - return null; - } - } - } - }}; - } - - private static Object[] wrapOutputStream( final IMountedFileBinary writer ) - { - - return new Object[] { new ILuaObject() { - private boolean open = true; - - @Nonnull - @Override - public String[] getMethodNames() { - return new String[] { - "write", - "close", - "flush" - }; - } - - @Override - public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args) throws LuaException { - switch( method ) - { - case 0: - { - // write - if( !open ) throw new LuaException( "attempt to use a closed file" ); - try { - if( args.length > 0 && args[0] instanceof Number ) - { - int number = ((Number)args[0]).intValue(); - writer.write( number ); - } - return null; - } catch( IOException e ) { - throw new LuaException(e.getMessage()); - } - } - case 1: - { - //close - try { - writer.close(); - open = false; - return null; - } catch( IOException e ) { - return null; - } - } - case 2: - { - if( !open ) throw new LuaException( "attempt to use a closed file" ); - try { - writer.flush(); - return null; - } catch ( IOException e ) { - return null; - } - } - default: - { - assert( false ); - return null; - } - } - } - }}; - } } diff --git a/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java b/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java index ccb4da866..71b6119f1 100644 --- a/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java +++ b/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java @@ -9,10 +9,11 @@ package dan200.computercraft.core.apis; import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaObject; import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.core.apis.handles.BinaryInputHandle; +import dan200.computercraft.core.apis.handles.EncodedInputHandle; import javax.annotation.Nonnull; -import java.io.BufferedReader; -import java.io.IOException; +import java.io.InputStream; import java.util.*; public class HTTPAPI implements ILuaAPI @@ -52,15 +53,21 @@ public class HTTPAPI implements ILuaAPI final String url = h.getURL(); if( h.wasSuccessful() ) { // Queue the "http_success" event - final BufferedReader contents = h.getContents(); - final Object result = wrapBufferedReader( contents, h.getResponseCode(), h.getResponseHeaders() ); + InputStream contents = h.getContents(); + Object result = wrapStream( + h.isBinary() ? new BinaryInputHandle( contents ) : new EncodedInputHandle( contents, h.getEncoding() ), + h.getResponseCode(), h.getResponseHeaders() + ); m_apiEnvironment.queueEvent( "http_success", new Object[] { url, result } ); } else { // Queue the "http_failure" event - BufferedReader contents = h.getContents(); + InputStream contents = h.getContents(); Object result = null; if( contents != null ) { - result = wrapBufferedReader( contents, h.getResponseCode(), h.getResponseHeaders() ); + result = wrapStream( + h.isBinary() ? new BinaryInputHandle( contents ) : new EncodedInputHandle( contents, h.getEncoding() ), + h.getResponseCode(), h.getResponseHeaders() + ); } m_apiEnvironment.queueEvent( "http_failure", new Object[]{ url, "Could not connect", result } ); } @@ -69,80 +76,40 @@ public class HTTPAPI implements ILuaAPI } } } - - private static ILuaObject wrapBufferedReader( final BufferedReader reader, final int responseCode, final Map responseHeaders ) + + private static ILuaObject wrapStream( final ILuaObject reader, final int responseCode, final Map responseHeaders ) { - return new ILuaObject() { - private boolean open = true; + String[] oldMethods = reader.getMethodNames(); + final int methodOffset = oldMethods.length; + + final String[] newMethods = Arrays.copyOf( oldMethods, oldMethods.length + 2 ); + newMethods[ methodOffset + 0 ] = "getResponseCode"; + newMethods[ methodOffset + 1 ] = "getResponseHeaders"; + + return new ILuaObject() + { @Nonnull @Override public String[] getMethodNames() { - return new String[] { - "readLine", - "readAll", - "close", - "getResponseCode", - "getResponseHeaders", - }; + return newMethods; } - + @Override - public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException + public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException, InterruptedException { - switch( method ) + if( method < methodOffset ) + { + return reader.callMethod( context, method, args ); + } + switch( method - methodOffset ) { case 0: - { - // readLine - if( !open ) throw new LuaException( "attempt to use a closed response" ); - try { - String line = reader.readLine(); - if( line != null ) { - return new Object[] { line }; - } else { - return null; - } - } catch( IOException e ) { - return null; - } - } - case 1: - { - // readAll - if( !open ) throw new LuaException( "attempt to use a closed response" ); - try { - StringBuilder result = new StringBuilder( "" ); - String line = reader.readLine(); - while( line != null ) { - result.append( line ); - line = reader.readLine(); - if( line != null ) { - result.append( "\n" ); - } - } - return new Object[] { result.toString() }; - } catch( IOException e ) { - return null; - } - } - case 2: - { - // close - try { - reader.close(); - open = false; - return null; - } catch( IOException e ) { - return null; - } - } - case 3: { // getResponseCode return new Object[] { responseCode }; } - case 4: + case 1: { // getResponseHeaders return new Object[] { responseHeaders }; @@ -216,11 +183,18 @@ public class HTTPAPI implements ILuaAPI } } } + + // Get binary + boolean binary = false; + if( args.length >= 4 ) + { + binary = args[ 3 ] != null && !args[ 3 ].equals( Boolean.FALSE ); + } // Make the request try { - HTTPRequest request = new HTTPRequest( urlString, postString, headers ); + HTTPRequest request = new HTTPRequest( urlString, postString, headers, binary ); synchronized( m_httpRequests ) { m_httpRequests.add( request ); diff --git a/src/main/java/dan200/computercraft/core/apis/HTTPRequest.java b/src/main/java/dan200/computercraft/core/apis/HTTPRequest.java index d4d36dabd..9a7182b63 100644 --- a/src/main/java/dan200/computercraft/core/apis/HTTPRequest.java +++ b/src/main/java/dan200/computercraft/core/apis/HTTPRequest.java @@ -7,6 +7,7 @@ package dan200.computercraft.core.apis; import com.google.common.base.Joiner; +import com.google.common.io.ByteStreams; import dan200.computercraft.ComputerCraft; import java.io.*; @@ -60,11 +61,12 @@ public class HTTPRequest return url; } - public HTTPRequest( String url, final String postText, final Map headers ) throws HTTPRequestException + public HTTPRequest( String url, final String postText, final Map headers, boolean binary ) throws HTTPRequestException { // Parse the URL m_urlString = url; m_url = checkURL( m_urlString ); + m_binary = binary; // Start the thread m_cancelled = false; @@ -136,54 +138,10 @@ public class HTTPRequest is = connection.getErrorStream(); responseSuccess = false; } - InputStreamReader isr; - try - { - String contentEncoding = connection.getContentEncoding(); - if( contentEncoding != null ) - { - try - { - isr = new InputStreamReader( is, contentEncoding ); - } - catch( UnsupportedEncodingException e ) - { - isr = new InputStreamReader( is, "UTF-8" ); - } - } - else - { - isr = new InputStreamReader( is, "UTF-8" ); - } - } - catch( UnsupportedEncodingException e ) - { - isr = new InputStreamReader( is ); - } - - // Download the contents - BufferedReader reader = new BufferedReader( isr ); - StringBuilder result = new StringBuilder(); - while( true ) - { - synchronized( m_lock ) - { - if( m_cancelled ) - { - break; - } - } - - String line = reader.readLine(); - if( line == null ) - { - break; - } - result.append( line ); - result.append( '\n' ); - } - reader.close(); - + + byte[] result = ByteStreams.toByteArray( is ); + is.close(); + synchronized( m_lock ) { if( m_cancelled ) @@ -198,8 +156,9 @@ public class HTTPRequest // We completed m_complete = true; m_success = responseSuccess; - m_result = result.toString(); + m_result = result; m_responseCode = connection.getResponseCode(); + m_encoding = connection.getContentEncoding(); Joiner joiner = Joiner.on( ',' ); Map headers = m_responseHeaders = new HashMap(); @@ -264,20 +223,29 @@ public class HTTPRequest return m_success; } } - - public BufferedReader getContents() + + public boolean isBinary() { - String result; + return m_binary; + } + + public InputStream getContents() + { + byte[] result; synchronized(m_lock) { result = m_result; } if( result != null ) { - return new BufferedReader( new StringReader( result ) ); + return new ByteArrayInputStream( result ); } return null; } + public String getEncoding() { + return m_encoding; + } + private final Object m_lock = new Object(); private final URL m_url; private final String m_urlString; @@ -285,7 +253,9 @@ public class HTTPRequest private boolean m_complete; private boolean m_cancelled; private boolean m_success; - private String m_result; + private String m_encoding; + private byte[] m_result; + private boolean m_binary; private int m_responseCode; private Map m_responseHeaders; } diff --git a/src/main/java/dan200/computercraft/core/apis/handles/BinaryInputHandle.java b/src/main/java/dan200/computercraft/core/apis/handles/BinaryInputHandle.java new file mode 100644 index 000000000..3ad196c06 --- /dev/null +++ b/src/main/java/dan200/computercraft/core/apis/handles/BinaryInputHandle.java @@ -0,0 +1,93 @@ +package dan200.computercraft.core.apis.handles; + +import com.google.common.io.ByteStreams; +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +public class BinaryInputHandle extends HandleGeneric +{ + private final InputStream m_stream; + + public BinaryInputHandle( InputStream reader ) + { + super( reader ); + this.m_stream = reader; + } + + @Nonnull + @Override + public String[] getMethodNames() + { + return new String[] { + "read", + "readAll", + "close", + }; + } + + @Override + public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException + { + switch( method ) + { + case 0: + // read + checkOpen(); + try + { + if( args.length > 0 && args[ 0 ] != null ) + { + if( !(args[ 0 ] instanceof Number) ) + { + throw new LuaException( "Expected number" ); + } + + int count = ((Number) args[ 0 ]).intValue(); + + if( count <= 0 || count >= 1024 * 16 ) + { + throw new LuaException( "Count out of range" ); + } + + byte[] bytes = new byte[ count ]; + count = m_stream.read( bytes ); + if( count < 0 ) return null; + if( count < bytes.length ) bytes = Arrays.copyOf( bytes, count ); + return new Object[] { bytes }; + } + else + { + int b = m_stream.read(); + return b == -1 ? null : new Object[] { b }; + } + } + catch( IOException e ) + { + return null; + } + case 1: + // readAll + checkOpen(); + try + { + byte[] out = ByteStreams.toByteArray( m_stream ); + return out == null ? null : new Object[] { out }; + } + catch( IOException e ) + { + return null; + } + case 2: + //close + close(); + return null; + default: + return null; + } + } +} diff --git a/src/main/java/dan200/computercraft/core/apis/handles/BinaryOutputHandle.java b/src/main/java/dan200/computercraft/core/apis/handles/BinaryOutputHandle.java new file mode 100644 index 000000000..63c148718 --- /dev/null +++ b/src/main/java/dan200/computercraft/core/apis/handles/BinaryOutputHandle.java @@ -0,0 +1,82 @@ +package dan200.computercraft.core.apis.handles; + +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.shared.util.StringUtil; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.io.OutputStream; + +public class BinaryOutputHandle extends HandleGeneric +{ + private final OutputStream m_writer; + + public BinaryOutputHandle( OutputStream writer ) + { + super( writer ); + this.m_writer = writer; + } + + @Nonnull + @Override + public String[] getMethodNames() + { + return new String[] { + "write", + "flush", + "close", + }; + } + + @Override + public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException + { + switch( method ) + { + case 0: + // write + checkOpen(); + try + { + if( args.length > 0 && args[ 0 ] instanceof Number ) + { + int number = ((Number) args[ 0 ]).intValue(); + m_writer.write( number ); + } + else if( args.length > 0 && args[ 0 ] instanceof String ) + { + String value = (String) args[ 0 ]; + m_writer.write( StringUtil.encodeString( value ) ); + } + else + { + throw new LuaException( "Expected number" ); + } + return null; + } + catch( IOException e ) + { + throw new LuaException( e.getMessage() ); + } + case 1: + // flush + checkOpen(); + try + { + m_writer.flush(); + return null; + } + catch( IOException e ) + { + return null; + } + case 2: + //close + close(); + return null; + default: + return null; + } + } +} diff --git a/src/main/java/dan200/computercraft/core/apis/handles/EncodedInputHandle.java b/src/main/java/dan200/computercraft/core/apis/handles/EncodedInputHandle.java new file mode 100644 index 000000000..09c4cd498 --- /dev/null +++ b/src/main/java/dan200/computercraft/core/apis/handles/EncodedInputHandle.java @@ -0,0 +1,105 @@ +package dan200.computercraft.core.apis.handles; + +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; + +import javax.annotation.Nonnull; +import java.io.*; + +public class EncodedInputHandle extends HandleGeneric +{ + private final BufferedReader m_reader; + + public EncodedInputHandle( BufferedReader reader ) + { + super( reader ); + this.m_reader = reader; + } + + public EncodedInputHandle( InputStream stream ) + { + this( stream, "UTF-8" ); + } + + public EncodedInputHandle( InputStream stream, String encoding ) + { + super( stream ); + if( encoding == null ) encoding = "UTF-8"; + InputStreamReader streamReader; + try + { + streamReader = new InputStreamReader( stream, encoding ); + } + catch( UnsupportedEncodingException e ) + { + streamReader = new InputStreamReader( stream ); + } + this.m_reader = new BufferedReader( streamReader ); + } + + @Nonnull + @Override + public String[] getMethodNames() + { + return new String[] { + "readLine", + "readAll", + "close", + }; + } + + @Override + public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException + { + switch( method ) + { + case 0: + // readLine + checkOpen(); + try + { + String line = m_reader.readLine(); + if( line != null ) + { + return new Object[] { line }; + } + else + { + return null; + } + } + catch( IOException e ) + { + return null; + } + case 1: + // readAll + checkOpen(); + try + { + StringBuilder result = new StringBuilder( "" ); + String line = m_reader.readLine(); + while( line != null ) + { + result.append( line ); + line = m_reader.readLine(); + if( line != null ) + { + result.append( "\n" ); + } + } + return new Object[] { result.toString() }; + } + catch( IOException e ) + { + return null; + } + case 2: + // close + close(); + return null; + default: + return null; + } + } +} diff --git a/src/main/java/dan200/computercraft/core/apis/handles/EncodedOutputHandle.java b/src/main/java/dan200/computercraft/core/apis/handles/EncodedOutputHandle.java new file mode 100644 index 000000000..61db07fa4 --- /dev/null +++ b/src/main/java/dan200/computercraft/core/apis/handles/EncodedOutputHandle.java @@ -0,0 +1,124 @@ +package dan200.computercraft.core.apis.handles; + +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; + +import javax.annotation.Nonnull; +import java.io.*; + +public class EncodedOutputHandle extends HandleGeneric +{ + private final BufferedWriter m_writer; + + public EncodedOutputHandle( BufferedWriter writer ) + { + super( writer ); + this.m_writer = writer; + } + + public EncodedOutputHandle( OutputStream stream ) + { + this( stream, "UTF-8" ); + } + + public EncodedOutputHandle( OutputStream stream, String encoding ) + { + super( stream ); + if( encoding == null ) encoding = "UTF-8"; + OutputStreamWriter streamWriter; + try + { + streamWriter = new OutputStreamWriter( stream, encoding ); + } + catch( UnsupportedEncodingException e ) + { + streamWriter = new OutputStreamWriter( stream ); + } + this.m_writer = new BufferedWriter( streamWriter ); + } + + @Nonnull + @Override + public String[] getMethodNames() + { + return new String[] { + "write", + "writeLine", + "flush", + "close", + }; + } + + @Override + public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException + { + switch( method ) + { + case 0: + { + // write + checkOpen(); + String text; + if( args.length > 0 && args[ 0 ] != null ) + { + text = args[ 0 ].toString(); + } + else + { + text = ""; + } + try + { + m_writer.write( text, 0, text.length() ); + return null; + } + catch( IOException e ) + { + throw new LuaException( e.getMessage() ); + } + } + case 1: + { + // writeLine + checkOpen(); + String text; + if( args.length > 0 && args[ 0 ] != null ) + { + text = args[ 0 ].toString(); + } + else + { + text = ""; + } + try + { + m_writer.write( text, 0, text.length() ); + m_writer.newLine(); + return null; + } + catch( IOException e ) + { + throw new LuaException( e.getMessage() ); + } + } + case 2: + // flush + checkOpen(); + try + { + m_writer.flush(); + return null; + } + catch( IOException e ) + { + return null; + } + case 3: + // close + close(); + return null; + default: + return null; + } + } +} diff --git a/src/main/java/dan200/computercraft/core/apis/handles/HandleGeneric.java b/src/main/java/dan200/computercraft/core/apis/handles/HandleGeneric.java new file mode 100644 index 000000000..c1cbc68e7 --- /dev/null +++ b/src/main/java/dan200/computercraft/core/apis/handles/HandleGeneric.java @@ -0,0 +1,35 @@ +package dan200.computercraft.core.apis.handles; + +import dan200.computercraft.api.lua.ILuaObject; +import dan200.computercraft.api.lua.LuaException; + +import java.io.Closeable; +import java.io.IOException; + +public abstract class HandleGeneric implements ILuaObject +{ + protected final Closeable m_closable; + protected boolean m_open = true; + + public HandleGeneric( Closeable m_closable ) + { + this.m_closable = m_closable; + } + + protected void checkOpen() throws LuaException + { + if( !m_open ) throw new LuaException( "attempt to use a closed file" ); + } + + protected void close() + { + try + { + m_closable.close(); + m_open = false; + } + catch( IOException ignored ) + { + } + } +} diff --git a/src/main/java/dan200/computercraft/core/filesystem/FileSystem.java b/src/main/java/dan200/computercraft/core/filesystem/FileSystem.java index 5b9e8693d..b6a6d870b 100644 --- a/src/main/java/dan200/computercraft/core/filesystem/FileSystem.java +++ b/src/main/java/dan200/computercraft/core/filesystem/FileSystem.java @@ -9,6 +9,7 @@ package dan200.computercraft.core.filesystem; import dan200.computercraft.ComputerCraft; import dan200.computercraft.api.filesystem.IMount; import dan200.computercraft.api.filesystem.IWritableMount; +import net.minecraftforge.fml.common.FMLLog; import java.io.*; import java.util.*; @@ -291,7 +292,7 @@ public class FileSystem } private final Map m_mounts = new HashMap(); - private final Set m_openFiles = Collections.newSetFromMap( new WeakHashMap() ); + private final Set m_openFiles = Collections.newSetFromMap( new WeakHashMap() ); public FileSystem( String rootLabel, IMount rootMount ) throws FileSystemException { @@ -308,7 +309,7 @@ public class FileSystem // Close all dangling open files synchronized( m_openFiles ) { - for(IMountedFile file : m_openFiles) + for( Closeable file : m_openFiles ) { try { file.close(); @@ -647,7 +648,7 @@ public class FileSystem } } - private synchronized T openFile(T file, Closeable handle) throws FileSystemException + private synchronized T openFile( T file, Closeable handle ) throws FileSystemException { synchronized( m_openFiles ) { @@ -665,199 +666,44 @@ public class FileSystem throw new FileSystemException("Too many files already open"); } - m_openFiles.add( file ); + m_openFiles.add( handle ); return file; } } - private synchronized void closeFile( IMountedFile file, Closeable handle ) throws IOException + private synchronized void closeFile( Closeable handle ) throws IOException { synchronized( m_openFiles ) { - m_openFiles.remove( file ); - if( handle != null ) - { - handle.close(); - } + m_openFiles.remove( handle ); + handle.close(); } } - public synchronized IMountedFileNormal openForRead( String path ) throws FileSystemException + public synchronized InputStream openForRead( String path ) throws FileSystemException { path = sanitizePath ( path ); MountWrapper mount = getMount( path ); InputStream stream = mount.openForRead( path ); if( stream != null ) { - InputStreamReader isr; - try - { - isr = new InputStreamReader( stream, "UTF-8" ); - } - catch( UnsupportedEncodingException e ) - { - isr = new InputStreamReader( stream ); - } - final BufferedReader reader = new BufferedReader( isr ); - IMountedFileNormal file = new IMountedFileNormal() - { - @Override - public String readLine() throws IOException - { - return reader.readLine(); - } - - @Override - public void write(String s, int off, int len, boolean newLine) throws IOException - { - throw new UnsupportedOperationException(); - } - - @Override - public void close() throws IOException - { - closeFile( this, reader ); - } - - @Override - public void flush() throws IOException - { - throw new UnsupportedOperationException(); - } - }; - return openFile( file, reader ); + return openFile( new ClosingInputStream( stream ), stream ); } return null; } - - public synchronized IMountedFileNormal openForWrite( String path, boolean append ) throws FileSystemException + + public synchronized OutputStream openForWrite( String path, boolean append ) throws FileSystemException { path = sanitizePath ( path ); MountWrapper mount = getMount( path ); OutputStream stream = append ? mount.openForAppend( path ) : mount.openForWrite( path ); if( stream != null ) { - OutputStreamWriter osw; - try - { - osw = new OutputStreamWriter( stream, "UTF-8" ); - } - catch( UnsupportedEncodingException e ) - { - osw = new OutputStreamWriter( stream ); - } - final BufferedWriter writer = new BufferedWriter( osw ); - IMountedFileNormal file = new IMountedFileNormal() - { - @Override - public String readLine() throws IOException - { - throw new UnsupportedOperationException(); - } - - @Override - public void write( String s, int off, int len, boolean newLine ) throws IOException - { - writer.write( s, off, len ); - if( newLine ) - { - writer.newLine(); - } - } - - @Override - public void close() throws IOException - { - closeFile( this, writer ); - } - - @Override - public void flush() throws IOException - { - writer.flush(); - } - }; - return openFile( file, writer ); + return openFile( new ClosingOutputStream( stream ), stream ); } return null; } - public synchronized IMountedFileBinary openForBinaryRead( String path ) throws FileSystemException - { - path = sanitizePath ( path ); - MountWrapper mount = getMount( path ); - final InputStream stream = mount.openForRead( path ); - if( stream != null ) - { - IMountedFileBinary file = new IMountedFileBinary() - { - @Override - public int read() throws IOException - { - return stream.read(); - } - - @Override - public void write(int i) throws IOException - { - throw new UnsupportedOperationException(); - } - - @Override - public void close() throws IOException - { - closeFile( this, stream ); - } - - @Override - public void flush() throws IOException - { - throw new UnsupportedOperationException(); - } - }; - return openFile( file, stream ); - } - return null; - } - - public synchronized IMountedFileBinary openForBinaryWrite( String path, boolean append ) throws FileSystemException - { - path = sanitizePath ( path ); - MountWrapper mount = getMount( path ); - final OutputStream stream = append ? mount.openForAppend( path ) : mount.openForWrite( path ); - if( stream != null ) - { - IMountedFileBinary file = new IMountedFileBinary() - { - @Override - public int read() throws IOException - { - throw new UnsupportedOperationException(); - } - - @Override - public void write(int i) throws IOException - { - stream.write(i); - } - - @Override - public void close() throws IOException - { - closeFile( this, stream ); - } - - @Override - public void flush() throws IOException - { - stream.flush(); - } - }; - return openFile( file, stream ); - } - return null; - } - public long getFreeSpace( String path ) throws FileSystemException { path = sanitizePath( path ); @@ -1010,4 +856,34 @@ public class FileSystem return local; } } + + private class ClosingInputStream extends FilterInputStream + { + protected ClosingInputStream( InputStream in ) + { + super( in ); + } + + @Override + public void close() throws IOException + { + super.close(); + closeFile( in ); + } + } + + private class ClosingOutputStream extends FilterOutputStream + { + protected ClosingOutputStream( OutputStream out ) + { + super( out ); + } + + @Override + public void close() throws IOException + { + super.close(); + closeFile( out ); + } + } } diff --git a/src/main/java/dan200/computercraft/core/filesystem/IMountedFile.java b/src/main/java/dan200/computercraft/core/filesystem/IMountedFile.java deleted file mode 100644 index b04389eb2..000000000 --- a/src/main/java/dan200/computercraft/core/filesystem/IMountedFile.java +++ /dev/null @@ -1,13 +0,0 @@ -/* - * This file is part of ComputerCraft - http://www.computercraft.info - * Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission. - * Send enquiries to dratcliffe@gmail.com - */ - -package dan200.computercraft.core.filesystem; - -import java.io.IOException; - -public interface IMountedFile { - void close() throws IOException; -} diff --git a/src/main/java/dan200/computercraft/core/filesystem/IMountedFileBinary.java b/src/main/java/dan200/computercraft/core/filesystem/IMountedFileBinary.java deleted file mode 100644 index a028c9656..000000000 --- a/src/main/java/dan200/computercraft/core/filesystem/IMountedFileBinary.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * This file is part of ComputerCraft - http://www.computercraft.info - * Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission. - * Send enquiries to dratcliffe@gmail.com - */ - -package dan200.computercraft.core.filesystem; - -import java.io.IOException; - -public interface IMountedFileBinary extends IMountedFile { - int read() throws IOException; - void write( int i ) throws IOException; - void close() throws IOException; - void flush() throws IOException; -} diff --git a/src/main/java/dan200/computercraft/core/filesystem/IMountedFileNormal.java b/src/main/java/dan200/computercraft/core/filesystem/IMountedFileNormal.java deleted file mode 100644 index 0435bdaf9..000000000 --- a/src/main/java/dan200/computercraft/core/filesystem/IMountedFileNormal.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * This file is part of ComputerCraft - http://www.computercraft.info - * Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission. - * Send enquiries to dratcliffe@gmail.com - */ - -package dan200.computercraft.core.filesystem; - -import java.io.IOException; - -public interface IMountedFileNormal extends IMountedFile { - String readLine() throws IOException; - void write( String s, int off, int len, boolean newLine ) throws IOException; - void close() throws IOException; - void flush() throws IOException; -} diff --git a/src/main/java/dan200/computercraft/core/lua/LuaJLuaMachine.java b/src/main/java/dan200/computercraft/core/lua/LuaJLuaMachine.java index 933ca3291..c5592c103 100644 --- a/src/main/java/dan200/computercraft/core/lua/LuaJLuaMachine.java +++ b/src/main/java/dan200/computercraft/core/lua/LuaJLuaMachine.java @@ -23,6 +23,7 @@ import org.luaj.vm2.lib.jse.JsePlatform; import javax.annotation.Nonnull; import java.io.*; +import java.util.Arrays; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Map; @@ -509,6 +510,11 @@ public class LuaJLuaMachine implements ILuaMachine String s = object.toString(); return LuaValue.valueOf( s ); } + else if( object instanceof byte[] ) + { + byte[] b = (byte[]) object; + return LuaValue.valueOf( Arrays.copyOf( b, b.length ) ); + } else if( object instanceof Map ) { // Table: diff --git a/src/main/java/dan200/computercraft/shared/util/StringUtil.java b/src/main/java/dan200/computercraft/shared/util/StringUtil.java index 66e66a5c1..01b1d964c 100644 --- a/src/main/java/dan200/computercraft/shared/util/StringUtil.java +++ b/src/main/java/dan200/computercraft/shared/util/StringUtil.java @@ -41,4 +41,17 @@ public class StringUtil { return net.minecraft.util.text.translation.I18n.translateToLocalFormatted( key, format ); } + + public static byte[] encodeString( String string ) + { + byte[] chars = new byte[ string.length() ]; + + for( int i = 0; i < chars.length; ++i ) + { + char c = string.charAt( i ); + chars[ i ] = c < 256 ? (byte) c : 63; + } + + return chars; + } } diff --git a/src/main/resources/assets/computercraft/lua/bios.lua b/src/main/resources/assets/computercraft/lua/bios.lua index 60b1eaf18..1a89c952e 100644 --- a/src/main/resources/assets/computercraft/lua/bios.lua +++ b/src/main/resources/assets/computercraft/lua/bios.lua @@ -639,8 +639,8 @@ end if http then local nativeHTTPRequest = http.request - local function wrapRequest( _url, _post, _headers ) - local ok, err = nativeHTTPRequest( _url, _post, _headers ) + local function wrapRequest( _url, _post, _headers, _binary ) + local ok, err = nativeHTTPRequest( _url, _post, _headers, _binary ) if ok then while true do local event, param1, param2, param3 = os.pullEvent() @@ -654,16 +654,16 @@ if http then return nil, err end - http.get = function( _url, _headers ) - return wrapRequest( _url, nil, _headers ) + http.get = function( _url, _headers, _binary) + return wrapRequest( _url, nil, _headers, _binary) end - http.post = function( _url, _post, _headers ) - return wrapRequest( _url, _post or "", _headers ) + http.post = function( _url, _post, _headers, _binary) + return wrapRequest( _url, _post or "", _headers, _binary) end - http.request = function( _url, _post, _headers ) - local ok, err = nativeHTTPRequest( _url, _post, _headers ) + http.request = function( _url, _post, _headers, _binary ) + local ok, err = nativeHTTPRequest( _url, _post, _headers, _binary ) if not ok then os.queueEvent( "http_failure", _url, err ) end