Skip to content

Commit e555f9f

Browse files
committed
Merge pull request dan200#575 from SquidDev-CC/ComputerCraft/feature/file-seeking
Rewrite file systems to use ByteChannels
2 parents 822db6e + 518eefb commit e555f9f

23 files changed

+1383
-974
lines changed

src/main/java/dan200/computercraft/ComputerCraft.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import dan200.computercraft.core.apis.AddressPredicate;
2727
import dan200.computercraft.core.filesystem.ComboMount;
2828
import dan200.computercraft.core.filesystem.FileMount;
29-
import dan200.computercraft.core.filesystem.JarMount;
29+
import dan200.computercraft.core.filesystem.FileSystemMount;
3030
import dan200.computercraft.core.terminal.Terminal;
3131
import dan200.computercraft.core.tracking.Tracking;
3232
import dan200.computercraft.shared.command.CommandComputer;
@@ -97,6 +97,9 @@
9797
import java.net.MalformedURLException;
9898
import java.net.URISyntaxException;
9999
import java.net.URL;
100+
import java.nio.file.FileSystem;
101+
import java.nio.file.FileSystems;
102+
import java.nio.file.ProviderNotFoundException;
100103
import java.util.*;
101104
import java.util.function.Function;
102105
import java.util.zip.ZipEntry;
@@ -922,11 +925,12 @@ public static IMount createResourceMount( Class<?> modClass, String domain, Stri
922925
{
923926
try
924927
{
925-
IMount jarMount = new JarMount( modJar, subPath );
926-
mounts.add( jarMount );
928+
FileSystem fs = FileSystems.newFileSystem( modJar.toPath(), ComputerCraft.class.getClassLoader() );
929+
mounts.add( new FileSystemMount( fs, subPath ) );
927930
}
928-
catch( IOException e )
931+
catch( IOException | ProviderNotFoundException | ServiceConfigurationError e )
929932
{
933+
ComputerCraft.log.error( "Could not load mount from mod jar", e );
930934
// Ignore
931935
}
932936
}
@@ -944,7 +948,7 @@ public static IMount createResourceMount( Class<?> modClass, String domain, Stri
944948
if( !resourcePack.isDirectory() )
945949
{
946950
// Mount a resource pack from a jar
947-
IMount resourcePackMount = new JarMount( resourcePack, subPath );
951+
IMount resourcePackMount = new FileSystemMount( FileSystems.getFileSystem( resourcePack.toURI() ), subPath );
948952
mounts.add( resourcePackMount );
949953
}
950954
else
@@ -960,7 +964,7 @@ public static IMount createResourceMount( Class<?> modClass, String domain, Stri
960964
}
961965
catch( IOException e )
962966
{
963-
// Ignore
967+
ComputerCraft.log.error( "Could not load resource pack '" + resourcePack1 + "'", e );
964968
}
965969
}
966970
}
@@ -1109,7 +1113,7 @@ public static ITurtleUpgrade getTurtleUpgrade( String id )
11091113
{
11101114
return turtleProxy.getTurtleUpgrade( id );
11111115
}
1112-
1116+
11131117
public static ITurtleUpgrade getTurtleUpgrade( int legacyID )
11141118
{
11151119
return turtleProxy.getTurtleUpgrade( legacyID );

src/main/java/dan200/computercraft/api/filesystem/IMount.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import javax.annotation.Nonnull;
1414
import java.io.IOException;
1515
import java.io.InputStream;
16+
import java.nio.channels.Channels;
17+
import java.nio.channels.ReadableByteChannel;
1618
import java.util.List;
1719

1820
/**
@@ -72,7 +74,25 @@ public interface IMount
7274
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
7375
* @return A stream representing the contents of the file.
7476
* @throws IOException If the file does not exist, or could not be opened.
77+
* @deprecated Use {@link #openChannelForRead(String)} instead
7578
*/
7679
@Nonnull
80+
@Deprecated
7781
InputStream openForRead( @Nonnull String path ) throws IOException;
82+
83+
/**
84+
* Opens a file with a given path, and returns an {@link ReadableByteChannel} representing its contents.
85+
*
86+
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
87+
* @return A channel representing the contents of the file. If the channel implements
88+
* {@link java.nio.channels.SeekableByteChannel}, one will be able to seek to arbitrary positions when using binary
89+
* mode.
90+
* @throws IOException If the file does not exist, or could not be opened.
91+
*/
92+
@Nonnull
93+
@SuppressWarnings("deprecation")
94+
default ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
95+
{
96+
return Channels.newChannel( openForRead( path ) );
97+
}
7898
}

src/main/java/dan200/computercraft/api/filesystem/IWritableMount.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import javax.annotation.Nonnull;
1414
import java.io.IOException;
1515
import java.io.OutputStream;
16+
import java.nio.channels.Channels;
17+
import java.nio.channels.WritableByteChannel;
1618

1719
/**
1820
* Represents a part of a virtual filesystem that can be mounted onto a computer using {@link IComputerAccess#mount(String, IMount)}
@@ -50,20 +52,54 @@ public interface IWritableMount extends IMount
5052
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
5153
* @return A stream for writing to
5254
* @throws IOException If the file could not be opened for writing.
55+
* @deprecated Use {@link #openStreamForWrite(String)} instead.
5356
*/
5457
@Nonnull
58+
@Deprecated
5559
OutputStream openForWrite( @Nonnull String path ) throws IOException;
5660

61+
/**
62+
* Opens a file with a given path, and returns an {@link OutputStream} for writing to it.
63+
*
64+
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
65+
* @return A stream for writing to. If the channel implements {@link java.nio.channels.SeekableByteChannel}, one
66+
* will be able to seek to arbitrary positions when using binary mode.
67+
* @throws IOException If the file could not be opened for writing.
68+
*/
69+
@Nonnull
70+
@SuppressWarnings("deprecation")
71+
default WritableByteChannel openStreamForWrite( @Nonnull String path ) throws IOException
72+
{
73+
return Channels.newChannel( openForWrite( path ) );
74+
}
75+
5776
/**
5877
* Opens a file with a given path, and returns an {@link OutputStream} for appending to it.
5978
*
6079
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
6180
* @return A stream for writing to.
6281
* @throws IOException If the file could not be opened for writing.
82+
* @deprecated Use {@link #openStreamForAppend(String)} instead.
6383
*/
6484
@Nonnull
85+
@Deprecated
6586
OutputStream openForAppend( @Nonnull String path ) throws IOException;
6687

88+
/**
89+
* Opens a file with a given path, and returns an {@link OutputStream} for appending to it.
90+
*
91+
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
92+
* @return A stream for writing to. If the channel implements {@link java.nio.channels.SeekableByteChannel}, one
93+
* will be able to seek to arbitrary positions when using binary mode.
94+
* @throws IOException If the file could not be opened for writing.
95+
*/
96+
@Nonnull
97+
@SuppressWarnings("deprecation")
98+
default WritableByteChannel openStreamForAppend( @Nonnull String path ) throws IOException
99+
{
100+
return Channels.newChannel( openForAppend( path ) );
101+
}
102+
67103
/**
68104
* Get the amount of free space on the mount, in bytes. You should decrease this value as the user writes to the
69105
* mount, and write operations should fail once it reaches zero.

src/main/java/dan200/computercraft/core/apis/FSAPI.java

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,23 @@
99
import dan200.computercraft.api.lua.ILuaAPI;
1010
import dan200.computercraft.api.lua.ILuaContext;
1111
import dan200.computercraft.api.lua.LuaException;
12-
import dan200.computercraft.core.apis.handles.BinaryInputHandle;
13-
import dan200.computercraft.core.apis.handles.BinaryOutputHandle;
14-
import dan200.computercraft.core.apis.handles.EncodedInputHandle;
15-
import dan200.computercraft.core.apis.handles.EncodedOutputHandle;
12+
import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
13+
import dan200.computercraft.core.apis.handles.BinaryWritableHandle;
14+
import dan200.computercraft.core.apis.handles.EncodedReadableHandle;
15+
import dan200.computercraft.core.apis.handles.EncodedWritableHandle;
1616
import dan200.computercraft.core.filesystem.FileSystem;
1717
import dan200.computercraft.core.filesystem.FileSystemException;
18+
import dan200.computercraft.core.filesystem.FileSystemWrapper;
1819
import dan200.computercraft.core.tracking.TrackingField;
1920

2021
import javax.annotation.Nonnull;
21-
import java.io.InputStream;
22-
import java.io.OutputStream;
22+
import java.io.BufferedReader;
23+
import java.io.BufferedWriter;
24+
import java.nio.channels.ReadableByteChannel;
25+
import java.nio.channels.WritableByteChannel;
2326
import java.util.HashMap;
2427
import java.util.Map;
28+
import java.util.function.Function;
2529

2630
import static dan200.computercraft.core.apis.ArgumentHelper.getString;
2731

@@ -221,38 +225,38 @@ public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull O
221225
case "r":
222226
{
223227
// Open the file for reading, then create a wrapper around the reader
224-
InputStream reader = m_fileSystem.openForRead( path );
225-
return new Object[] { new EncodedInputHandle( reader ) };
228+
FileSystemWrapper<BufferedReader> reader = m_fileSystem.openForRead( path, EncodedReadableHandle::openUtf8 );
229+
return new Object[] { new EncodedReadableHandle( reader.get(), reader ) };
226230
}
227231
case "w":
228232
{
229233
// Open the file for writing, then create a wrapper around the writer
230-
OutputStream writer = m_fileSystem.openForWrite( path, false );
231-
return new Object[] { new EncodedOutputHandle( writer ) };
234+
FileSystemWrapper<BufferedWriter> writer = m_fileSystem.openForWrite( path, false, EncodedWritableHandle::openUtf8 );
235+
return new Object[] { new EncodedWritableHandle( writer.get(), writer ) };
232236
}
233237
case "a":
234238
{
235239
// Open the file for appending, then create a wrapper around the writer
236-
OutputStream writer = m_fileSystem.openForWrite( path, true );
237-
return new Object[] { new EncodedOutputHandle( writer ) };
240+
FileSystemWrapper<BufferedWriter> writer = m_fileSystem.openForWrite( path, true, EncodedWritableHandle::openUtf8 );
241+
return new Object[] { new EncodedWritableHandle( writer.get(), writer ) };
238242
}
239243
case "rb":
240244
{
241245
// Open the file for binary reading, then create a wrapper around the reader
242-
InputStream reader = m_fileSystem.openForRead( path );
243-
return new Object[] { new BinaryInputHandle( reader ) };
246+
FileSystemWrapper<ReadableByteChannel> reader = m_fileSystem.openForRead( path, Function.identity() );
247+
return new Object[] { new BinaryReadableHandle( reader.get(), reader ) };
244248
}
245249
case "wb":
246250
{
247251
// Open the file for binary writing, then create a wrapper around the writer
248-
OutputStream writer = m_fileSystem.openForWrite( path, false );
249-
return new Object[] { new BinaryOutputHandle( writer ) };
252+
FileSystemWrapper<WritableByteChannel> writer = m_fileSystem.openForWrite( path, false, Function.identity() );
253+
return new Object[] { new BinaryWritableHandle( writer.get(), writer ) };
250254
}
251255
case "ab":
252256
{
253257
// Open the file for binary appending, then create a wrapper around the reader
254-
OutputStream writer = m_fileSystem.openForWrite( path, true );
255-
return new Object[] { new BinaryOutputHandle( writer ) };
258+
FileSystemWrapper<WritableByteChannel> writer = m_fileSystem.openForWrite( path, true, Function.identity() );
259+
return new Object[] { new BinaryWritableHandle( writer.get(), writer ) };
256260
}
257261
default:
258262
throw new LuaException( "Unsupported mode" );
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package dan200.computercraft.core.apis.handles;
2+
3+
import com.google.common.base.Preconditions;
4+
5+
import java.io.IOException;
6+
import java.nio.ByteBuffer;
7+
import java.nio.channels.ClosedChannelException;
8+
import java.nio.channels.NonWritableChannelException;
9+
import java.nio.channels.SeekableByteChannel;
10+
11+
/**
12+
* A seekable, readable byte channel which is backed by a simple byte array.
13+
*/
14+
public class ArrayByteChannel implements SeekableByteChannel
15+
{
16+
private boolean closed = false;
17+
private int position = 0;
18+
19+
private final byte[] backing;
20+
21+
public ArrayByteChannel( byte[] backing )
22+
{
23+
this.backing = backing;
24+
}
25+
26+
@Override
27+
public int read( ByteBuffer destination ) throws IOException
28+
{
29+
if( closed ) throw new ClosedChannelException();
30+
Preconditions.checkNotNull( destination, "destination" );
31+
32+
if( position >= backing.length ) return -1;
33+
34+
int remaining = Math.min( backing.length - position, destination.remaining() );
35+
destination.put( backing, position, remaining );
36+
position += remaining;
37+
return remaining;
38+
}
39+
40+
@Override
41+
public int write( ByteBuffer src ) throws IOException
42+
{
43+
if( closed ) throw new ClosedChannelException();
44+
throw new NonWritableChannelException();
45+
}
46+
47+
@Override
48+
public long position() throws IOException
49+
{
50+
if( closed ) throw new ClosedChannelException();
51+
return 0;
52+
}
53+
54+
@Override
55+
public SeekableByteChannel position( long newPosition ) throws IOException
56+
{
57+
if( closed ) throw new ClosedChannelException();
58+
if( newPosition < 0 || newPosition > Integer.MAX_VALUE )
59+
{
60+
throw new IllegalArgumentException( "Position out of bounds" );
61+
}
62+
position = (int) newPosition;
63+
return this;
64+
}
65+
66+
@Override
67+
public long size() throws IOException
68+
{
69+
if( closed ) throw new ClosedChannelException();
70+
return backing.length;
71+
}
72+
73+
@Override
74+
public SeekableByteChannel truncate( long size ) throws IOException
75+
{
76+
if( closed ) throw new ClosedChannelException();
77+
throw new NonWritableChannelException();
78+
}
79+
80+
@Override
81+
public boolean isOpen()
82+
{
83+
return !closed;
84+
}
85+
86+
@Override
87+
public void close()
88+
{
89+
closed = true;
90+
}
91+
}

0 commit comments

Comments
 (0)