mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-02-11 00:20:05 +00:00
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:
parent
239bd769df
commit
0ffd5fcf85
@ -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;
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ import java.io.IOException;
|
||||
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 @@ public interface IMount
|
||||
{
|
||||
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 ) );
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import java.io.IOException;
|
||||
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 @@ public interface IWritableMount extends IMount
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,10 @@ import java.io.BufferedReader;
|
||||
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 class FSAPI implements ILuaAPI
|
||||
"getFreeSpace",
|
||||
"find",
|
||||
"getDir",
|
||||
"getCapacity",
|
||||
"attributes",
|
||||
};
|
||||
}
|
||||
|
||||
@ -315,9 +321,8 @@ public class FSAPI implements ILuaAPI
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
case 14:
|
||||
case 14: // find
|
||||
{
|
||||
// find
|
||||
String path = getString( args, 0 );
|
||||
try
|
||||
{
|
||||
@ -329,12 +334,42 @@ public class FSAPI implements ILuaAPI
|
||||
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;
|
||||
|
@ -12,6 +12,7 @@ import javax.annotation.Nonnull;
|
||||
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 class ComboMount implements IMount
|
||||
}
|
||||
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" );
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,10 @@ import java.nio.channels.*;
|
||||
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 class FileMount implements IWritableMount
|
||||
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 class FileMount implements IWritableMount
|
||||
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 );
|
||||
|
@ -7,7 +7,6 @@ package dan200.computercraft.core.filesystem;
|
||||
|
||||
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.Channel;
|
||||
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 class FileSystem
|
||||
{
|
||||
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 class FileSystem
|
||||
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 class FileSystem
|
||||
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 class FileSystem
|
||||
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 class FileSystem
|
||||
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() )
|
||||
|
@ -23,6 +23,8 @@ import java.lang.ref.ReferenceQueue;
|
||||
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 class JarMount implements IMount
|
||||
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 @@ public class JarMount implements IMount
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 );
|
||||
}
|
||||
}
|
@ -11,43 +11,42 @@ import javax.annotation.Nonnull;
|
||||
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 class SubMount implements IMount
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ public class ComputerTestDelegate
|
||||
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<>();
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user