Add fs.getCapacity and fs.attributes (#365)

- fs.getCapacity just returns the capacity of the current drive, if
   available. This will be nil on rom mounts.
 - fs.attributes returns an lfs like table of various file attributes.
   Currently, this contains:
    - access, modification, created: When this file was last accessed,
      modified and created. Time is measured in milliseconds since the
      epoch, same as os.epoch("utc") and what is accepted by os.date.
    - size: Same as fs.getSize
    - isDir: same as fs.isDir

Closes #262
This commit is contained in:
Jonathan Coates 2020-02-08 21:04:58 +00:00 committed by GitHub
parent 239bd769df
commit 0ffd5fcf85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 664 additions and 349 deletions

View File

@ -0,0 +1,81 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.filesystem;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
/**
* A simple version of {@link BasicFileAttributes}, which provides what information a {@link IMount} already exposes.
*/
final class FileAttributes implements BasicFileAttributes
{
private static final FileTime EPOCH = FileTime.from( Instant.EPOCH );
private final boolean isDirectory;
private final long size;
FileAttributes( boolean isDirectory, long size )
{
this.isDirectory = isDirectory;
this.size = size;
}
@Override
public FileTime lastModifiedTime()
{
return EPOCH;
}
@Override
public FileTime lastAccessTime()
{
return EPOCH;
}
@Override
public FileTime creationTime()
{
return EPOCH;
}
@Override
public boolean isRegularFile()
{
return !isDirectory;
}
@Override
public boolean isDirectory()
{
return isDirectory;
}
@Override
public boolean isSymbolicLink()
{
return false;
}
@Override
public boolean isOther()
{
return false;
}
@Override
public long size()
{
return size;
}
@Override
public Object fileKey()
{
return null;
}
}

View File

@ -14,6 +14,7 @@
import java.io.InputStream;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
/**
@ -93,4 +94,18 @@ default ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IO
{
return Channels.newChannel( openForRead( path ) );
}
/**
* Get attributes about the given file.
*
* @param path The path to query.
* @return File attributes for the given file.
* @throws IOException If the file does not exist, or attributes could not be fetched.
*/
@Nonnull
default BasicFileAttributes getAttributes( @Nonnull String path ) throws IOException
{
if( !exists( path ) ) throw new FileOperationException( path, "No such file" );
return new FileAttributes( isDirectory( path ), getSize( path ) );
}
}

View File

@ -14,6 +14,7 @@
import java.io.OutputStream;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
import java.util.OptionalLong;
/**
* Represents a part of a virtual filesystem that can be mounted onto a computer using {@link IComputerAccess#mount(String, IMount)}
@ -105,4 +106,16 @@ default WritableByteChannel openChannelForAppend( @Nonnull String path ) throws
* @throws IOException If the remaining space could not be computed.
*/
long getRemainingSpace() throws IOException;
/**
* Get the capacity of this mount. This should be equal to the size of all files/directories on this mount, minus
* the {@link #getRemainingSpace()}.
*
* @return The capacity of this mount, in bytes.
*/
@Nonnull
default OptionalLong getCapacity()
{
return OptionalLong.empty();
}
}

View File

@ -22,6 +22,10 @@
import java.io.BufferedWriter;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.Map;
import java.util.OptionalLong;
import java.util.function.Function;
import static dan200.computercraft.api.lua.ArgumentHelper.getString;
@ -76,6 +80,8 @@ public String[] getMethodNames()
"getFreeSpace",
"find",
"getDir",
"getCapacity",
"attributes",
};
}
@ -315,9 +321,8 @@ public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull O
throw new LuaException( e.getMessage() );
}
}
case 14:
case 14: // find
{
// find
String path = getString( args, 0 );
try
{
@ -329,12 +334,42 @@ public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull O
throw new LuaException( e.getMessage() );
}
}
case 15:
case 15: // getDir
{
// getDir
String path = getString( args, 0 );
return new Object[] { FileSystem.getDirectory( path ) };
}
case 16: // getCapacity
{
String path = getString( args, 0 );
try
{
OptionalLong capacity = m_fileSystem.getCapacity( path );
return new Object[] { capacity.isPresent() ? capacity.getAsLong() : null };
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
case 17: // attributes
{
String path = getString( args, 0 );
try
{
BasicFileAttributes attributes = m_fileSystem.getAttributes( path );
Map<String, Object> result = new HashMap<>();
result.put( "modification", attributes.lastModifiedTime().toMillis() );
result.put( "created", attributes.creationTime().toMillis() );
result.put( "size", attributes.isDirectory() ? 0 : attributes.size() );
result.put( "isDir", attributes.isDirectory() );
return new Object[] { result };
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
default:
assert false;
return null;

View File

@ -12,6 +12,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@ -143,4 +144,19 @@ public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOE
}
throw new FileOperationException( path, "No such file" );
}
@Nonnull
@Override
public BasicFileAttributes getAttributes( @Nonnull String path ) throws IOException
{
for( int i = m_parts.length - 1; i >= 0; --i )
{
IMount part = m_parts[i];
if( part.exists( path ) && !part.isDirectory( path ) )
{
return part.getAttributes( path );
}
}
throw new FileOperationException( path, "No such file" );
}
}

View File

@ -16,8 +16,10 @@
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collections;
import java.util.List;
import java.util.OptionalLong;
import java.util.Set;
public class FileMount implements IWritableMount
@ -224,6 +226,19 @@ public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOE
throw new FileOperationException( path, "No such file" );
}
@Nonnull
@Override
public BasicFileAttributes getAttributes( @Nonnull String path ) throws IOException
{
if( created() )
{
File file = getRealPath( path );
if( file.exists() ) return Files.readAttributes( file.toPath(), BasicFileAttributes.class );
}
throw new FileOperationException( path, "No such file" );
}
// IWritableMount implementation
@Override
@ -360,6 +375,13 @@ public long getRemainingSpace()
return Math.max( m_capacity - m_usedSpace, 0 );
}
@Nonnull
@Override
public OptionalLong getCapacity()
{
return OptionalLong.of( m_capacity - MINIMUM_FILE_SIZE );
}
private File getRealPath( String path )
{
return new File( m_rootPath, path );

View File

@ -7,7 +7,6 @@
import com.google.common.io.ByteStreams;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.FileOperationException;
import dan200.computercraft.api.filesystem.IFileSystem;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
@ -23,6 +22,7 @@
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.AccessDeniedException;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Pattern;
@ -37,303 +37,8 @@ public class FileSystem
*/
private static final int MAX_COPY_DEPTH = 128;
private static class MountWrapper
{
private String m_label;
private String m_location;
private IMount m_mount;
private IWritableMount m_writableMount;
MountWrapper( String label, String location, IMount mount )
{
m_label = label;
m_location = location;
m_mount = mount;
m_writableMount = null;
}
MountWrapper( String label, String location, IWritableMount mount )
{
this( label, location, (IMount) mount );
m_writableMount = mount;
}
public String getLabel()
{
return m_label;
}
public String getLocation()
{
return m_location;
}
public long getFreeSpace()
{
if( m_writableMount == null )
{
return 0;
}
try
{
return m_writableMount.getRemainingSpace();
}
catch( IOException e )
{
return 0;
}
}
public boolean isReadOnly( String path )
{
return m_writableMount == null;
}
// IMount forwarders:
public boolean exists( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
return m_mount.exists( path );
}
catch( IOException e )
{
throw new FileSystemException( e.getMessage() );
}
}
public boolean isDirectory( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
return m_mount.exists( path ) && m_mount.isDirectory( path );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public void list( String path, List<String> contents ) throws FileSystemException
{
path = toLocal( path );
try
{
if( m_mount.exists( path ) && m_mount.isDirectory( path ) )
{
m_mount.list( path, contents );
}
else
{
throw localExceptionOf( path, "Not a directory" );
}
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public long getSize( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
if( m_mount.exists( path ) )
{
if( m_mount.isDirectory( path ) )
{
return 0;
}
else
{
return m_mount.getSize( path );
}
}
else
{
throw localExceptionOf( path, "No such file" );
}
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public ReadableByteChannel openForRead( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
if( m_mount.exists( path ) && !m_mount.isDirectory( path ) )
{
return m_mount.openChannelForRead( path );
}
else
{
throw localExceptionOf( path, "No such file" );
}
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
// IWritableMount forwarders:
public void makeDirectory( String path ) throws FileSystemException
{
if( m_writableMount == null ) throw exceptionOf( path, "Access denied" );
path = toLocal( path );
try
{
if( m_mount.exists( path ) )
{
if( !m_mount.isDirectory( path ) ) throw localExceptionOf( path, "File exists" );
}
else
{
m_writableMount.makeDirectory( path );
}
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public void delete( String path ) throws FileSystemException
{
if( m_writableMount == null ) throw exceptionOf( path, "Access denied" );
try
{
path = toLocal( path );
if( m_mount.exists( path ) )
{
m_writableMount.delete( path );
}
}
catch( AccessDeniedException e )
{
throw new FileSystemException( "Access denied" );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public WritableByteChannel openForWrite( String path ) throws FileSystemException
{
if( m_writableMount == null ) throw exceptionOf( path, "Access denied" );
path = toLocal( path );
try
{
if( m_mount.exists( path ) && m_mount.isDirectory( path ) )
{
throw localExceptionOf( path, "Cannot write to directory" );
}
else
{
if( !path.isEmpty() )
{
String dir = getDirectory( path );
if( !dir.isEmpty() && !m_mount.exists( path ) )
{
m_writableMount.makeDirectory( dir );
}
}
return m_writableMount.openChannelForWrite( path );
}
}
catch( AccessDeniedException e )
{
throw new FileSystemException( "Access denied" );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public WritableByteChannel openForAppend( String path ) throws FileSystemException
{
if( m_writableMount == null ) throw exceptionOf( path, "Access denied" );
path = toLocal( path );
try
{
if( !m_mount.exists( path ) )
{
if( !path.isEmpty() )
{
String dir = getDirectory( path );
if( !dir.isEmpty() && !m_mount.exists( path ) )
{
m_writableMount.makeDirectory( dir );
}
}
return m_writableMount.openChannelForWrite( path );
}
else if( m_mount.isDirectory( path ) )
{
throw localExceptionOf( path, "Cannot write to directory" );
}
else
{
return m_writableMount.openChannelForAppend( path );
}
}
catch( AccessDeniedException e )
{
throw new FileSystemException( "Access denied" );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
private String toLocal( String path )
{
return FileSystem.toLocal( path, m_location );
}
private FileSystemException localExceptionOf( IOException e )
{
if( !m_location.isEmpty() && e instanceof FileOperationException )
{
FileOperationException ex = (FileOperationException) e;
if( ex.getFilename() != null ) return localExceptionOf( ex.getFilename(), ex.getMessage() );
}
return new FileSystemException( e.getMessage() );
}
private FileSystemException localExceptionOf( String path, String message )
{
if( !m_location.isEmpty() ) path = path.isEmpty() ? m_location : m_location + "/" + path;
return exceptionOf( path, message );
}
private static FileSystemException exceptionOf( String path, String message )
{
return new FileSystemException( "/" + path + ": " + message );
}
}
private final FileSystemWrapperMount m_wrapper = new FileSystemWrapperMount( this );
private final Map<String, MountWrapper> m_mounts = new HashMap<>();
private final Map<String, MountWrapper> mounts = new HashMap<>();
private final HashMap<WeakReference<FileSystemWrapper<?>>, ChannelWrapper<?>> m_openFiles = new HashMap<>();
private final ReferenceQueue<FileSystemWrapper<?>> m_openFileQueue = new ReferenceQueue<>();
@ -363,10 +68,7 @@ public synchronized void mount( String label, String location, IMount mount ) th
{
if( mount == null ) throw new NullPointerException();
location = sanitizePath( location );
if( location.contains( ".." ) )
{
throw new FileSystemException( "Cannot mount below the root" );
}
if( location.contains( ".." ) ) throw new FileSystemException( "Cannot mount below the root" );
mount( new MountWrapper( label, location, mount ) );
}
@ -387,14 +89,13 @@ public synchronized void mountWritable( String label, String location, IWritable
private synchronized void mount( MountWrapper wrapper )
{
String location = wrapper.getLocation();
m_mounts.remove( location );
m_mounts.put( location, wrapper );
mounts.remove( location );
mounts.put( location, wrapper );
}
public synchronized void unmount( String path )
{
path = sanitizePath( path );
m_mounts.remove( path );
mounts.remove( sanitizePath( path ) );
}
public synchronized String combine( String path, String childPath )
@ -438,27 +139,20 @@ public static String getDirectory( String path )
public static String getName( String path )
{
path = sanitizePath( path, true );
if( path.isEmpty() )
{
return "root";
}
if( path.isEmpty() ) return "root";
int lastSlash = path.lastIndexOf( '/' );
if( lastSlash >= 0 )
{
return path.substring( lastSlash + 1 );
}
else
{
return path;
}
return lastSlash >= 0 ? path.substring( lastSlash + 1 ) : path;
}
public synchronized long getSize( String path ) throws FileSystemException
{
path = sanitizePath( path );
MountWrapper mount = getMount( path );
return mount.getSize( path );
return getMount( sanitizePath( path ) ).getSize( sanitizePath( path ) );
}
public synchronized BasicFileAttributes getAttributes( String path ) throws FileSystemException
{
return getMount( sanitizePath( path ) ).getAttributes( sanitizePath( path ) );
}
public synchronized String[] list( String path ) throws FileSystemException
@ -471,7 +165,7 @@ public synchronized String[] list( String path ) throws FileSystemException
mount.list( path, list );
// Add any mounts that are mounted at this location
for( MountWrapper otherMount : m_mounts.values() )
for( MountWrapper otherMount : mounts.values() )
{
if( getDirectory( otherMount.getLocation() ).equals( path ) )
{
@ -733,17 +427,25 @@ public synchronized <T extends Closeable> FileSystemWrapper<T> openForWrite( Str
return null;
}
public long getFreeSpace( String path ) throws FileSystemException
public synchronized long getFreeSpace( String path ) throws FileSystemException
{
path = sanitizePath( path );
MountWrapper mount = getMount( path );
return mount.getFreeSpace();
}
private MountWrapper getMount( String path ) throws FileSystemException
@Nonnull
public synchronized OptionalLong getCapacity( String path ) throws FileSystemException
{
path = sanitizePath( path );
MountWrapper mount = getMount( path );
return mount.getCapacity();
}
private synchronized MountWrapper getMount( String path ) throws FileSystemException
{
// Return the deepest mount that contains a given path
Iterator<MountWrapper> it = m_mounts.values().iterator();
Iterator<MountWrapper> it = mounts.values().iterator();
MountWrapper match = null;
int matchLength = 999;
while( it.hasNext() )

View File

@ -23,6 +23,8 @@
import java.lang.ref.WeakReference;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
@ -221,6 +223,20 @@ public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOE
throw new FileOperationException( path, "No such file" );
}
@Nonnull
@Override
public BasicFileAttributes getAttributes( @Nonnull String path ) throws IOException
{
FileEntry file = get( path );
if( file != null )
{
ZipEntry entry = zip.getEntry( file.path );
if( entry != null ) return new ZipEntryAttributes( entry );
}
throw new FileOperationException( path, "No such file" );
}
private static class FileEntry
{
String path;
@ -261,4 +277,68 @@ private static void cleanup()
Reference<? extends JarMount> next;
while( (next = MOUNT_QUEUE.poll()) != null ) IoUtil.closeQuietly( ((MountReference) next).file );
}
private static class ZipEntryAttributes implements BasicFileAttributes
{
private final ZipEntry entry;
ZipEntryAttributes( ZipEntry entry )
{
this.entry = entry;
}
@Override
public FileTime lastModifiedTime()
{
return entry.getLastModifiedTime();
}
@Override
public FileTime lastAccessTime()
{
return entry.getLastAccessTime();
}
@Override
public FileTime creationTime()
{
return entry.getCreationTime();
}
@Override
public boolean isRegularFile()
{
return !entry.isDirectory();
}
@Override
public boolean isDirectory()
{
return entry.isDirectory();
}
@Override
public boolean isSymbolicLink()
{
return false;
}
@Override
public boolean isOther()
{
return false;
}
@Override
public long size()
{
return entry.getSize();
}
@Override
public Object fileKey()
{
return null;
}
}
}

View File

@ -0,0 +1,312 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.filesystem;
import dan200.computercraft.api.filesystem.FileOperationException;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.AccessDeniedException;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import java.util.OptionalLong;
class MountWrapper
{
private String label;
private String location;
private IMount mount;
private IWritableMount writableMount;
MountWrapper( String label, String location, IMount mount )
{
this.label = label;
this.location = location;
this.mount = mount;
writableMount = null;
}
MountWrapper( String label, String location, IWritableMount mount )
{
this( label, location, (IMount) mount );
writableMount = mount;
}
public String getLabel()
{
return label;
}
public String getLocation()
{
return location;
}
public long getFreeSpace()
{
if( writableMount == null ) return 0;
try
{
return writableMount.getRemainingSpace();
}
catch( IOException e )
{
return 0;
}
}
public OptionalLong getCapacity()
{
return writableMount == null ? OptionalLong.empty() : writableMount.getCapacity();
}
public boolean isReadOnly( String path )
{
return writableMount == null;
}
public boolean exists( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
return mount.exists( path );
}
catch( IOException e )
{
throw new FileSystemException( e.getMessage() );
}
}
public boolean isDirectory( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
return mount.exists( path ) && mount.isDirectory( path );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public void list( String path, List<String> contents ) throws FileSystemException
{
path = toLocal( path );
try
{
if( !mount.exists( path ) || !mount.isDirectory( path ) )
{
throw localExceptionOf( path, "Not a directory" );
}
mount.list( path, contents );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public long getSize( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
if( !mount.exists( path ) ) throw localExceptionOf( path, "No such file" );
return mount.isDirectory( path ) ? 0 : mount.getSize( path );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
@Nonnull
public BasicFileAttributes getAttributes( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
if( !mount.exists( path ) ) throw localExceptionOf( path, "No such file" );
return mount.getAttributes( path );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public ReadableByteChannel openForRead( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
if( mount.exists( path ) && !mount.isDirectory( path ) )
{
return mount.openChannelForRead( path );
}
else
{
throw localExceptionOf( path, "No such file" );
}
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public void makeDirectory( String path ) throws FileSystemException
{
if( writableMount == null ) throw exceptionOf( path, "Access denied" );
path = toLocal( path );
try
{
if( mount.exists( path ) )
{
if( !mount.isDirectory( path ) ) throw localExceptionOf( path, "File exists" );
}
else
{
writableMount.makeDirectory( path );
}
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public void delete( String path ) throws FileSystemException
{
if( writableMount == null ) throw exceptionOf( path, "Access denied" );
try
{
path = toLocal( path );
if( mount.exists( path ) )
{
writableMount.delete( path );
}
}
catch( AccessDeniedException e )
{
throw new FileSystemException( "Access denied" );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public WritableByteChannel openForWrite( String path ) throws FileSystemException
{
if( writableMount == null ) throw exceptionOf( path, "Access denied" );
path = toLocal( path );
try
{
if( mount.exists( path ) && mount.isDirectory( path ) )
{
throw localExceptionOf( path, "Cannot write to directory" );
}
else
{
if( !path.isEmpty() )
{
String dir = FileSystem.getDirectory( path );
if( !dir.isEmpty() && !mount.exists( path ) )
{
writableMount.makeDirectory( dir );
}
}
return writableMount.openChannelForWrite( path );
}
}
catch( AccessDeniedException e )
{
throw new FileSystemException( "Access denied" );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public WritableByteChannel openForAppend( String path ) throws FileSystemException
{
if( writableMount == null ) throw exceptionOf( path, "Access denied" );
path = toLocal( path );
try
{
if( !mount.exists( path ) )
{
if( !path.isEmpty() )
{
String dir = FileSystem.getDirectory( path );
if( !dir.isEmpty() && !mount.exists( path ) )
{
writableMount.makeDirectory( dir );
}
}
return writableMount.openChannelForWrite( path );
}
else if( mount.isDirectory( path ) )
{
throw localExceptionOf( path, "Cannot write to directory" );
}
else
{
return writableMount.openChannelForAppend( path );
}
}
catch( AccessDeniedException e )
{
throw new FileSystemException( "Access denied" );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
private String toLocal( String path )
{
return FileSystem.toLocal( path, location );
}
private FileSystemException localExceptionOf( IOException e )
{
if( !location.isEmpty() && e instanceof FileOperationException )
{
FileOperationException ex = (FileOperationException) e;
if( ex.getFilename() != null ) return localExceptionOf( ex.getFilename(), ex.getMessage() );
}
return new FileSystemException( e.getMessage() );
}
private FileSystemException localExceptionOf( String path, String message )
{
if( !location.isEmpty() ) path = path.isEmpty() ? location : location + "/" + path;
return exceptionOf( path, message );
}
private static FileSystemException exceptionOf( String path, String message )
{
return new FileSystemException( "/" + path + ": " + message );
}
}

View File

@ -11,43 +11,42 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
public class SubMount implements IMount
{
private IMount m_parent;
private String m_subPath;
private IMount parent;
private String subPath;
public SubMount( IMount parent, String subPath )
{
m_parent = parent;
m_subPath = subPath;
this.parent = parent;
this.subPath = subPath;
}
// IMount implementation
@Override
public boolean exists( @Nonnull String path ) throws IOException
{
return m_parent.exists( getFullPath( path ) );
return parent.exists( getFullPath( path ) );
}
@Override
public boolean isDirectory( @Nonnull String path ) throws IOException
{
return m_parent.isDirectory( getFullPath( path ) );
return parent.isDirectory( getFullPath( path ) );
}
@Override
public void list( @Nonnull String path, @Nonnull List<String> contents ) throws IOException
{
m_parent.list( getFullPath( path ), contents );
parent.list( getFullPath( path ), contents );
}
@Override
public long getSize( @Nonnull String path ) throws IOException
{
return m_parent.getSize( getFullPath( path ) );
return parent.getSize( getFullPath( path ) );
}
@Nonnull
@ -55,25 +54,25 @@ public long getSize( @Nonnull String path ) throws IOException
@Deprecated
public InputStream openForRead( @Nonnull String path ) throws IOException
{
return m_parent.openForRead( getFullPath( path ) );
return parent.openForRead( getFullPath( path ) );
}
@Nonnull
@Override
public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
{
return m_parent.openChannelForRead( getFullPath( path ) );
return parent.openChannelForRead( getFullPath( path ) );
}
@Nonnull
@Override
public BasicFileAttributes getAttributes( @Nonnull String path ) throws IOException
{
return parent.getAttributes( getFullPath( path ) );
}
private String getFullPath( String path )
{
if( path.isEmpty() )
{
return m_subPath;
}
else
{
return m_subPath + "/" + path;
}
return path.isEmpty() ? subPath : subPath + "/" + path;
}
}

View File

@ -79,7 +79,7 @@ public void before() throws IOException
ComputerCraft.log = LogManager.getLogger( ComputerCraft.MOD_ID );
Terminal term = new Terminal( 78, 20 );
IWritableMount mount = new FileMount( new File( "test-files/mount" ), Long.MAX_VALUE );
IWritableMount mount = new FileMount( new File( "test-files/mount" ), 10_000_000 );
// Remove any existing files
List<String> children = new ArrayList<>();

View File

@ -149,4 +149,44 @@ describe("The fs library", function()
expect.error(fs.move, "rom", "test-files"):eq("Access denied")
end)
end)
describe("fs.getCapacity", function()
it("returns nil on read-only mounts", function()
expect(fs.getCapacity("rom")):eq(nil)
end)
it("returns the capacity on the root mount", function()
expect(fs.getCapacity("")):eq(10000000)
end)
end)
describe("fs.attributes", function()
it("errors on non-existent files", function()
expect.error(fs.attributes, "xuxu_nao_existe"):eq("/xuxu_nao_existe: No such file")
end)
it("returns information about read-only mounts", function()
expect(fs.attributes("rom")):matches { isDir = true, size = 0 }
end)
it("returns information about files", function()
local now = os.epoch("utc")
fs.delete("/tmp/basic-file")
local h = fs.open("/tmp/basic-file", "w")
h.write("A reasonably sized string")
h.close()
local attributes = fs.attributes("tmp/basic-file")
expect(attributes):matches { isDir = false, size = 25 }
if attributes.created - now >= 1000 then
fail(("Expected created time (%d) to be within 1000ms of now (%d"):format(attributes.created, now))
end
if attributes.modification - now >= 1000 then
fail(("Expected modification time (%d) to be within 1000ms of now (%d"):format(attributes.modification, now))
end
end)
end)
end)