mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-31 05:33:00 +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:
		| @@ -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) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jonathan Coates
					Jonathan Coates