/*
* VFS.java - Virtual filesystem implementation
* :tabSize=4:indentSize=4:noTabs=false:
* :folding=explicit:collapseFolds=1:
*
* Copyright (C) 2000, 2003 Slava Pestov
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.gjt.sp.jedit.io;
//{{{ Imports
import java.awt.Color;
import java.awt.Component;
import java.io.*;
import java.io.Closeable;
import java.util.*;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.gjt.sp.jedit.msg.PropertiesChanged;
import org.gjt.sp.jedit.*;
import org.gjt.sp.jedit.bufferio.BufferLoadRequest;
import org.gjt.sp.jedit.bufferio.BufferSaveRequest;
import org.gjt.sp.jedit.bufferio.BufferInsertRequest;
import org.gjt.sp.util.Log;
import org.gjt.sp.util.ProgressObserver;
import org.gjt.sp.util.IOUtilities;
import org.gjt.sp.util.StandardUtilities;
import org.gjt.sp.util.Task;
import org.gjt.sp.util.ThreadUtilities;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
//}}}
/**
* A virtual filesystem implementation.<p>
*
* Plugins can provide virtual file systems by defining entries in their
* <code>services.xml</code> files like so:
*
* <pre><SERVICE CLASS="org.gjt.sp.jedit.io.VFS" NAME="<i>name</i>">
* new <i>MyVFS</i>();
*</SERVICE></pre>
*
* URLs of the form <code><i>name</i>:<i>path</i></code> will then be handled
* by the VFS named <code><i>name</i></code>.<p>
*
* See {@link org.gjt.sp.jedit.ServiceManager} for details.<p>
*
* <h3>Session objects:</h3>
*
* A session is used to persist things like login information, any network
* sockets, etc. File system implementations that do not need this kind of
* persistence return a dummy object as a session.<p>
*
* Methods whose names are prefixed with "_" expect to be given a
* previously-obtained session object. A session must be obtained with
* this method:
*
* <ul>
* <li>{@link #createVFSSession(String,Component)}</li>
* </ul>
*
* That method should be called from the AWT (EDT) thread, unless
* the filesystem has <code>NON_AWT_SESSION_CAP</code> capability.<p>
*
* When done, the session must be disposed of using
* {@link #_endVFSSession(Object,Component)}.<p>
*
* <h3>Thread safety:</h3>
*
* The following methods cannot be called from an I/O thread:
*
* <ul>
* <li>{@link #createVFSSession(String,Component)} - unless
* <code>NON_AWT_SESSION_CAP</code> capability is set</li>
* <li>{@link #insert(View,Buffer,String)}</li>
* <li>{@link #load(View,Buffer,String,boolean)}</li>
* <li>{@link #save(View,Buffer,String)}</li>
* </ul>
*
* All remaining methods are required to be thread-safe in subclasses.
*
* <h3>Implementing a VFS</h3>
*
* You can override as many or as few methods as you want. Make sure
* {@link #getCapabilities()} returns a value reflecting the functionality
* implemented by your VFS.
*
* @see VFSManager#getVFSForPath(String)
* @see VFSManager#getVFSForProtocol(String)
*
* @author Slava Pestov
* @author $Id: VFS.java 25330 2020-05-09 14:21:52Z kpouer $
*/
public abstract class VFS
{
//{{{ Capabilities
/**
* Read capability.
* @since jEdit 2.6pre2
*/
public static final int READ_CAP = 1 << 0;
/**
* Write capability.
* @since jEdit 2.6pre2
*/
public static final int WRITE_CAP = 1 << 1;
/**
* Browse capability
* @since jEdit 4.3pre11
*
* This was the official API for adding items to a file
* system browser's <b>Plugins</b> menu in jEdit 4.1 and earlier. In
* jEdit 4.2, there is a different way of doing this, you must provide
* a <code>browser.actions.xml</code> file in your plugin JAR, and
* define <code>plugin.<i>class</i>.browser-menu-item</code>
* or <code>plugin.<i>class</i>.browser-menu</code> properties.
* See {@link org.gjt.sp.jedit.EditPlugin} for details.
*/
public static final int BROWSE_CAP = 1 << 2;
/**
* Delete file capability.
* @since jEdit 2.6pre2
*/
public static final int DELETE_CAP = 1 << 3;
/**
* Rename file capability.
* @since jEdit 2.6pre2
*/
public static final int RENAME_CAP = 1 << 4;
/**
* Make directory capability.
* @since jEdit 2.6pre2
*/
public static final int MKDIR_CAP = 1 << 5;
/**
* Low latency capability. If this is not set, then a confirm dialog
* will be shown before doing a directory search in this VFS.
* @since jEdit 4.1pre1
*/
public static final int LOW_LATENCY_CAP = 1 << 6;
/**
* Case insensitive file system capability.
* @since jEdit 4.1pre1
*/
public static final int CASE_INSENSITIVE_CAP = 1 << 7;
/**
* Sessions created outside Event Dispatching Thread -
* file system capability. Set for the file system that does not
* require that <code>createVFSSession</code> is called on edt.
* All systems that do not implement <code>createVFSSession</code>
* should set it, but others may too.
* @since jEdit 5.0pre1
*/
public static final int NON_AWT_SESSION_CAP = 1 << 8;
//}}}
//{{{ Extended attributes
/**
* File type.
* @since jEdit 4.2pre1
*/
public static final String EA_TYPE = "type";
/**
* File status (read only, read write, etc).
* @since jEdit 4.2pre1
*/
public static final String EA_STATUS = "status";
/**
* File size.
* @since jEdit 4.2pre1
*/
public static final String EA_SIZE = "size";
/**
* File last modified date.
* @since jEdit 4.2pre1
*/
public static final String EA_MODIFIED = "modified";
//}}}
public static final int IOBUFSIZE = 32678;
//{{{ VFS constructors
/**
* Creates a new virtual filesystem.
* @param name The name
* @param caps The capabilities
*/
protected VFS(String name, int caps)
{
this.name = name;
this.caps = caps;
// reasonable defaults (?)
extAttrs = new String[] { EA_SIZE, EA_TYPE };
}
/**
* Creates a new virtual filesystem.
* @param name The name
* @param caps The capabilities
* @param extAttrs The extended attributes
* @since jEdit 4.2pre1
*/
protected VFS(String name, int caps, String[] extAttrs)
{
this.name = name;
this.caps = caps;
this.extAttrs = extAttrs;
} //}}}
//{{{ getName() method
/**
* Returns this VFS's name. The name is used to obtain the
* label stored in the <code>vfs.<i>name</i>.label</code>
* property.
*/
public String getName()
{
return name;
} //}}}
//{{{ getCapabilities() method
/**
* Returns the capabilities of this VFS.
* @since jEdit 2.6pre2
*/
public int getCapabilities()
{
return caps;
} //}}}
//{{{ isMarkersFileSupported() method
/**
* Returns if an additional markers file can be saved by this VFS.
* Default is {@code true}.
*
* @since jEdit 4.3pre10
*/
public boolean isMarkersFileSupported()
{
return true;
} //}}}
//{{{ getExtendedAttributes() method
/**
* Returns the extended attributes supported by this VFS.
* @since jEdit 4.2pre1
*/
public String[] getExtendedAttributes()
{
return extAttrs;
} //}}}
//{{{ getFileName() method
/**
* Returns the file name component of the specified path.
* @param path The path
* @since jEdit 3.1pre4
*/
public String getFileName(String path)
{
if(path.equals("/"))
return path;
while(path.endsWith("/") || path.endsWith(File.separator))
path = path.substring(0,path.length() - 1);
int index = Math.max(path.lastIndexOf('/'),
path.lastIndexOf(File.separatorChar));
if(index == -1)
index = path.indexOf(':');
// don't want getFileName("roots:") to return ""
if(index == -1 || index == path.length() - 1)
return path;
return path.substring(index + 1);
} //}}}
//{{{ getFilePath() method
/**
* Returns the path component of the specified VFS path.
* The standard implementation cuts off the protocol
* and the protocol separator character and then delegates
* to eventually present sub-VFS-paths present in the VFS path
* like "jode:archive:/test.zip!/test.txt".
* <p>
* If a VFS implementation can have additional
* information in the VFS path like username / password / host / port
* for FTP VFS or archive filename for archive VFS, this
* method should be overridden to remove those information also.
* The easiest would be to remove those additional information
* and then delegate to {@code super.getFilePath()}.
*
* @param vfsPath The VFS path
* @since jEdit 4.5pre1
*/
public String getFilePath(String vfsPath)
{
if (!MiscUtilities.isURL(vfsPath))
return vfsPath;
String filePath = vfsPath.substring(MiscUtilities.getProtocolOfURL(vfsPath).length() + 1);
return VFSManager.getVFSForPath(filePath).getFilePath(filePath);
} //}}}
//{{{ getParentOfPath() method
/**
* Returns the parent of the specified path. This must be
* overridden to return a non-null value for browsing of this
* filesystem to work.
* @param path The path
* @since jEdit 2.6pre5
*/
@Nonnull
public String getParentOfPath(String path)
{
// ignore last character of path to properly handle
// paths like /foo/bar/
int lastIndex = path.length() - 1;
while(lastIndex > 0
&& (path.charAt(lastIndex) == File.separatorChar
|| path.charAt(lastIndex) == '/'))
{
lastIndex--;
}
int count = Math.max(0,lastIndex);
int index = path.lastIndexOf(File.separatorChar,count);
if(index == -1)
index = path.lastIndexOf('/',count);
if(index == -1)
{
// this ensures that getFileParent("protocol:"), for
// example, is "protocol:" and not "".
index = path.lastIndexOf(':');
}
return path.substring(0,index + 1);
} //}}}
//{{{ constructPath() method
/**
* Constructs a path from the specified directory and
* file name component. This must be overridden to return a
* non-null value, otherwise browsing this filesystem will
* not work.<p>
*
* Unless you are writing a VFS, this method should not be called
* directly. To ensure correct behavior, you <b>must</b> call
* {@link org.gjt.sp.jedit.MiscUtilities#constructPath(String,String)}
* instead.
*
* @param parent The parent directory
* @param path The path
* @since jEdit 2.6pre2
*/
public String constructPath(String parent, String path)
{
return parent + path;
} //}}}
//{{{ getFileSeparator() method
/**
* Returns the file separator used by this VFS.
* @since jEdit 2.6pre9
*/
public char getFileSeparator()
{
return '/';
} //}}}
//{{{ getTwoStageSaveName() method
/**
* Returns a temporary file name based on the given path.
*
* By default jEdit first saves a file to <code>#<i>name</i>#save#</code>
* and then renames it to the original file. However some virtual file
* systems might not support the <code>#</code> character in filenames,
* so this method permits the VFS to override this behavior.
*
* If this method returns <code>null</code>, two stage save will not
* be used for that particular file (introduced in jEdit 4.3pre1).
*
* @param path The path name
* @since jEdit 4.1pre7
*/
@Nullable
public String getTwoStageSaveName(String path)
{
return MiscUtilities.constructPath(getParentOfPath(path),
'#' + getFileName(path) + "#save#");
} //}}}
//{{{ reloadDirectory() method
/**
* Called before a directory is reloaded by the file system browser.
* Can be used to flush a cache, etc.
* @since jEdit 4.0pre3
*/
public void reloadDirectory(String path) {} //}}}
//{{{ createVFSSession() methods
/**
* Creates a VFS session. This method is called from the AWT thread,
* so it should not do any I/O. It could, however, prompt for
* a login name and password, for example. A simpler filesystem
* may set the <code>NON_AWT_SESSION_CAP</code> capability. When set,
* sessions may be obtained from any thread.
* @param path The path in question
* @param comp The component that will parent any dialog boxes shown
* @return The session. The session can be null if there were errors
* @since jEdit 2.6pre3
*/
public Object createVFSSession(String path, Component comp)
{
return new Object();
}
/**
* Same as {@link #createVFSSession}, but may be called from any
* thread. It first checks the <code>NON_AWT_SESSION_CAP</code>
* capability and enters EDT thread if necessary.
*/
public Object createVFSSessionSafe(final String path,
final Component comp)
{
Object session;
if ((getCapabilities() & NON_AWT_SESSION_CAP) != 0)
{
session = createVFSSession(path, comp);
}
else
{
SessionGetter getter = new SessionGetter(path, comp);
ThreadUtilities.runInDispatchThreadAndWait(getter);
session = getter.get();
}
return session;
}
//}}}
//{{{ load() method
/**
* Loads the specified buffer. The default implementation posts
* an I/O request to the I/O thread.
* @param view The view
* @param buffer The buffer
* @param path The path
* @param untitled is the buffer untitled
*/
public boolean load(View view, Buffer buffer, String path, boolean untitled)
{
if((getCapabilities() & READ_CAP) == 0)
{
VFSManager.error(view,path,"vfs.not-supported.load",new String[] { name });
return false;
}
Object session = createVFSSession(path,view);
if(session == null)
return false;
if((getCapabilities() & WRITE_CAP) == 0)
buffer.setReadOnly(true);
Task request = new BufferLoadRequest(view, buffer, session, this, path, untitled);
if(buffer.isTemporary())
// this makes HyperSearch much faster
request.run();
else
// BufferLoadRequest can cause UI interations (for example FTP connection dialog),
// so it should be runned in Dispatch thread
//ThreadUtilities.runInDispatchThread(request);
ThreadUtilities.runInBackground(request);
return true;
} //}}}
//{{{ save() method
/**
* Saves the specifies buffer. The default implementation posts
* an I/O request to the I/O thread.
* @param view The view
* @param buffer The buffer
* @param path The path
*/
public boolean save(View view, Buffer buffer, String path)
{
if((getCapabilities() & WRITE_CAP) == 0)
{
VFSManager.error(view,path,"vfs.not-supported.save",new String[] { name });
return false;
}
Object session = createVFSSession(path,view);
if(session == null)
return false;
/* When doing a 'save as', the path to save to (path)
* will not be the same as the buffer's previous path
* (buffer.getPath()). In that case, we want to create
* a backup of the new path, even if the old path was
* backed up as well (BACKED_UP property set) */
if(!path.equals(buffer.getPath()))
buffer.unsetProperty(Buffer.BACKED_UP);
ThreadUtilities.runInBackground(new BufferSaveRequest(
view,buffer,session,this,path));
return true;
} //}}}
//{{{ copy() methods
/**
* Copy a file to another using VFS.
*
* @param progress the progress observer. It could be null if you don't want to monitor progress. If not null
* you should probably launch this command in a WorkThread
* @param sourceVFS the source VFS
* @param sourceSession the VFS session
* @param sourcePath the source path. It must be a file and must exists
* @param targetVFS the target VFS
* @param targetSession the target session
* @param targetPath the target path.
* If it is a path, it must exists, if it is a file, the parent must
* exists
* @param comp The component that will parent error dialog boxes
* @param canStop could this copy be stopped ?
* @return true if the copy was successful
* @throws IOException IOException If an I/O error occurs
* @since jEdit 4.3pre3
*/
public static boolean copy(ProgressObserver progress, VFS sourceVFS, Object sourceSession,String sourcePath,
VFS targetVFS, Object targetSession,String targetPath, Component comp, boolean canStop)
throws IOException
{
return copy(progress, sourceVFS, sourceSession, sourcePath, targetVFS, targetSession, targetPath,
comp, canStop, true);
}
/**
* Copy a file to another using VFS.
*
* @param progress the progress observer. It could be null if you don't want to monitor progress. If not null
* you should probably launch this command in a WorkThread
* @param sourceVFS the source VFS
* @param sourceSession the VFS session
* @param sourcePath the source path. It must be a file and must exists
* @param targetVFS the target VFS
* @param targetSession the target session
* @param targetPath the target path.
* If it is a path, it must exists, if it is a file, the parent must
* exists
* @param comp The component that will parent error dialog boxes
* @param canStop could this copy be stopped ?
* @param sendVFSUpdate true if you want to send a VFS update after the copy. False otherwise (if you do a lot
* of copy)
* @return true if the copy was successful
* @throws IOException IOException If an I/O error occurs
* @since jEdit 5.0
*/
public static boolean copy(ProgressObserver progress, VFS sourceVFS, Object sourceSession,String sourcePath,
VFS targetVFS, Object targetSession,String targetPath, Component comp, boolean canStop,
boolean sendVFSUpdate)
throws IOException
{
if (sourcePath.equals(targetPath))
{
Log.log(Log.WARNING, VFS.class,
jEdit.getProperty("ioerror.copy-self",
new Object[] { sourcePath }));
return false;
}
if (progress != null)
progress.setStatus("Initializing");
InputStream in = null;
OutputStream out = null;
try
{
VFSFile sourceVFSFile = sourceVFS._getFile(sourceSession, sourcePath, comp);
if (sourceVFSFile == null)
throw new FileNotFoundException("source path " + sourcePath + " doesn't exists");
if (progress != null)
{
progress.setMaximum(sourceVFSFile.getLength());
}
VFSFile targetVFSFile = targetVFS._getFile(targetSession, targetPath, comp);
if (targetVFSFile == null)
{
String parentTargetPath = MiscUtilities.getParentOfPath(targetPath);
VFSFile parentTargetVFSFile = targetVFS._getFile(targetSession, parentTargetPath, comp);
if (parentTargetVFSFile == null)
throw new FileNotFoundException("target path " + parentTargetPath +
" doesn't exists");
if (parentTargetVFSFile.getType() == VFSFile.DIRECTORY)
{
String targetFilename = MiscUtilities.getFileName(targetPath);
targetPath = MiscUtilities.constructPath(parentTargetPath, targetFilename);
}
else
{
throw new IOException("The parent of target path is a file");
}
}
else if (targetVFSFile.getType() == VFSFile.DIRECTORY)
{
if (targetVFSFile.getPath().equals(sourceVFSFile.getPath()))
return false;
targetPath = MiscUtilities.constructPath(targetPath, sourceVFSFile.getName());
}
in = new BufferedInputStream(sourceVFS._createInputStream(sourceSession, sourcePath, false, comp));
out = new BufferedOutputStream(targetVFS._createOutputStream(targetSession, targetPath, comp));
boolean copyResult = IOUtilities.copyStream(IOBUFSIZE, progress, in, out, canStop);
if (sendVFSUpdate)
VFSManager.sendVFSUpdate(targetVFS, targetPath, true);
return copyResult;
}
finally
{
IOUtilities.closeQuietly((Closeable)in);
IOUtilities.closeQuietly((Closeable)out);
}
}
/**
* Copy a file to another using VFS.
*
* @param progress the progress observer. It could be null if you don't want to monitor progress. If not null
* you should probably launch this command in a WorkThread
* @param sourcePath the source path
* @param targetPath the target path
* @param comp The component that will parent error dialog boxes
* @param canStop if true the copy can be stopped
* @param sendVFSUpdate true if you want to send a VFS update after the copy. False otherwise (if you do a lot
* of copy)
* @return true if the copy was successful
* @throws IOException IOException If an I/O error occurs
* @since jEdit 5.0
*/
public static boolean copy(ProgressObserver progress, String sourcePath,String targetPath, Component comp,
boolean canStop, boolean sendVFSUpdate)
throws IOException
{
VFS sourceVFS = VFSManager.getVFSForPath(sourcePath);
VFS targetVFS = VFSManager.getVFSForPath(targetPath);
Object sourceSession = null;
Object targetSession = null;
try
{
sourceSession = sourceVFS.createVFSSession(sourcePath, comp);
if (sourceSession == null)
{
Log.log(Log.WARNING, VFS.class, "Unable to get a valid session from " + sourceVFS +
" for path " + sourcePath);
return false;
}
targetSession = targetVFS.createVFSSession(targetPath, comp);
if (targetSession == null)
{
Log.log(Log.WARNING, VFS.class, "Unable to get a valid session from " + targetVFS +
" for path " + targetPath);
return false;
}
return copy(progress, sourceVFS, sourceSession, sourcePath, targetVFS, targetSession, targetPath,
comp,canStop, sendVFSUpdate);
}
finally
{
if (sourceSession != null)
sourceVFS._endVFSSession(sourceSession, comp);
if (targetSession != null)
targetVFS._endVFSSession(targetSession, comp);
}
}
/**
* Copy a file to another using VFS.
*
* @param progress the progress observer. It could be null if you don't want to monitor progress. If not null
* you should probably launch this command in a WorkThread
* @param sourcePath the source path
* @param targetPath the target path
* @param comp The component that will parent error dialog boxes
* @param canStop if true the copy can be stopped
* @return true if the copy was successful
* @throws IOException IOException If an I/O error occurs
* @since jEdit 4.3pre3
*/
public static boolean copy(ProgressObserver progress, String sourcePath,String targetPath, Component comp, boolean canStop)
throws IOException
{
return copy(progress, sourcePath, targetPath, comp, canStop, true);
} //}}}
//{{{ insert() method
/**
* Inserts a file into the specified buffer. The default implementation
* posts an I/O request to the I/O thread.
* @param view The view
* @param buffer The buffer
* @param path The path
*/
public boolean insert(View view, Buffer buffer, String path)
{
if((getCapabilities() & READ_CAP) == 0)
{
VFSManager.error(view,path,"vfs.not-supported.load",new String[] { name });
return false;
}
Object session = createVFSSession(path,view);
if(session == null)
return false;
ThreadUtilities.runInBackground(new BufferInsertRequest(
view,buffer,session,this,path));
return true;
} //}}}
// A method name that starts with _ requires a session object
//{{{ _canonPath() method
/**
* Returns the canonical form of the specified path name. For example,
* <code>~</code> might be expanded to the user's home directory.
* @param session The session
* @param path The path
* @param comp The component that will parent error dialog boxes
* @exception IOException if an I/O error occurred
* @since jEdit 4.0pre2
*/
public String _canonPath(Object session, String path, Component comp)
throws IOException
{
return path;
} //}}}
//{{{ _listDirectory() method
/**
* A convenience method that matches file names against globs, and can
* optionally list the directory recursively.
* @param session The session
* @param directory The directory. Note that this must be a full
* URL, including the host name, path name, and so on. The
* username and password (if needed by the VFS) is obtained from the
* session instance.
* @param glob Only file names matching this glob will be returned
* @param recursive If true, subdirectories will also be listed.
* @param comp The component that will parent error dialog boxes
* @exception IOException if an I/O error occurred
* @since jEdit 4.1pre1
*/
public String[] _listDirectory(Object session, String directory,
String glob, boolean recursive, Component comp )
throws IOException
{
String[] retval = _listDirectory(session, directory, glob, recursive, comp, true, false);
return retval;
} //}}}
//{{{ _listDirectory() method
/**
* A convenience method that matches file names against globs, and can
* optionally list the directory recursively.
* @param session The session
* @param directory The directory. Note that this must be a full
* URL, including the host name, path name, and so on. The
* username and password (if needed by the VFS) is obtained from the
* session instance.
* @param glob Only file names matching this glob will be returned
* @param recursive If true, subdirectories will also be listed.
* @param comp The component that will parent error dialog boxes
* @exception IOException if an I/O error occurred
* @param skipBinary ignore binary files (do not return them).
* This will slow down the process since it will open the files
* @param skipHidden skips hidden files, directories, and
* backup files. Ignores any file beginning with . or #, or ending with ~
* or .bak
*
*
* @since jEdit 4.3pre5
*/
public String[] _listDirectory(Object session, String directory,
String glob, boolean recursive, Component comp,
boolean skipBinary, boolean skipHidden)
throws IOException
{
VFSFileFilter filter = new GlobVFSFileFilter(glob);
return _listDirectory(session, directory, filter,
recursive, comp, skipBinary,
skipHidden);
} //}}}
//{{{ _listDirectory() method
/**
* A convenience method that filters the directory listing
* according to a filter, and can optionally list the directory
* recursively.
* @param session The session
* @param directory The directory. Note that this must be a full
* URL, including the host name, path name, and so on. The
* username and password (if needed by the VFS) is obtained from the
* session instance.
* @param filter The {@link VFSFileFilter} to use for filtering.
* @param recursive If true, subdirectories will also be listed.
* @param comp The component that will parent error dialog boxes
* @exception IOException if an I/O error occurred
* @param skipBinary ignore binary files (do not return them).
* This will slow down the process since it will open the files
* @param skipHidden skips hidden files, directories, and
* backup files. Ignores any file beginning with . or #, or ending with ~
* or .bak
*
* @since jEdit 4.3pre7
*/
public String[] _listDirectory(Object session, String directory,
VFSFileFilter filter, boolean recursive, Component comp,
boolean skipBinary, boolean skipHidden)
throws IOException
{
List<String> files = new ArrayList<>(100);
listFiles(session,new HashSet<>(), files,directory,filter,
recursive, comp, skipBinary, skipHidden);
String[] retVal = files.toArray(StandardUtilities.EMPTY_STRING_ARRAY);
Arrays.sort(retVal,new StandardUtilities.StringCompare<>(true));
return retVal;
} //}}}
//{{{ _listFiles() method
/**
* Lists the specified directory.
* @param session The session
* @param directory The directory. Note that this must be a full
* URL, including the host name, path name, and so on. The
* username and password (if needed by the VFS) is obtained from the
* session instance.
* @param comp The component that will parent error dialog boxes
* @exception IOException if an I/O error occurred
* @since jEdit 4.3pre2
*/
public VFSFile[] _listFiles(Object session, String directory,
Component comp)
throws IOException
{
VFSManager.error(comp,directory,"vfs.not-supported.list",new String[] { name });
return null;
} //}}}
//{{{ _getFile() method
/**
* Returns the specified directory entry.
* @param session The session get it with {@link VFS#createVFSSession(String, Component)}
* @param path The path
* @param comp The component that will parent error dialog boxes
* @exception IOException if an I/O error occurred
* @return The specified directory entry, or null if it doesn't exist.
* @since jEdit 4.3pre2
*/
@Nullable
public VFSFile _getFile(Object session, String path, Component comp) throws IOException
{
return null;
} //}}}
//{{{ _delete() method
/**
* Deletes the specified URL.
* @param session The VFS session
* @param path The path
* @param comp The component that will parent error dialog boxes
* @exception IOException if an I/O error occurs
* @since jEdit 2.7pre1
*/
public boolean _delete(Object session, String path, Component comp)
throws IOException
{
return false;
} //}}}
//{{{ _rename() method
/**
* Renames the specified URL. Some filesystems might support moving
* URLs between directories, however others may not. Do not rely on
* this behavior.
* @param session The VFS session
* @param from The old path
* @param to The new path
* @param comp The component that will parent error dialog boxes
* @exception IOException if an I/O error occurs
* @since jEdit 2.7pre1
*/
public boolean _rename(Object session, String from, String to,
Component comp) throws IOException
{
return false;
} //}}}
//{{{ _mkdir() method
/**
* Creates a new directory with the specified URL.
* @param session The VFS session
* @param directory The directory
* @param comp The component that will parent error dialog boxes
* @exception IOException if an I/O error occurs
* @since jEdit 2.7pre1
*/
public boolean _mkdir(Object session, String directory, Component comp)
throws IOException
{
return false;
} //}}}
//{{{ _backup() method
/**
* Backs up the specified file. Default implementation in 5.0pre1
* copies the file to the backup directory. Before 5.0pre1 it was
* empty.
* @param session The VFS session
* @param path The path
* @param comp The component that will parent error dialog boxes
* @exception IOException if an I/O error occurs
* @since jEdit 3.2pre2
*/
public void _backup(Object session, String path, Component comp)
throws IOException
{
VFS vfsSrc = VFSManager.getVFSForPath(path);
if (null == vfsSrc._getFile(session, path, comp))
// file to backup does not exist
return;
// maybe the file is not applicable to local filesystem
// but don't worry - for backup purposes it may be out
// of a real filesystem
File backupDir = MiscUtilities.prepareBackupDirectory(path);
if (backupDir == null)
{
Log.log(Log.WARNING, VFS.class, "Backup of remote file "
+ path + " failed, because there is no backup directory.");
return;
}
if (!backupDir.exists())
{
// Usually that means there is no specified backup
// directory.
Log.log(Log.WARNING, VFS.class, "Backup of file " +
path + " failed. Directory " + backupDir +
" does not exist.");
return;
}
File backupFile = MiscUtilities.prepareBackupFile(path, backupDir);
if (backupFile == null)
{
return;
}
// do copy using VFS.copy
VFS vfsDst = VFSManager.getVFSForPath(backupFile.getPath());
Object sessionDst = vfsDst.createVFSSessionSafe(
backupFile.getPath(), comp);
if (sessionDst == null)
{
return;
}
try
{
if (!copy(null, vfsSrc, session, path,
vfsDst, sessionDst, backupFile.getPath(),
comp, true))
{
Log.log(Log.WARNING, VFS.class, "Backup of file " +
path + " failed. Copy to " + backupFile +
" failed.");
}
}
finally
{
vfsDst._endVFSSession(sessionDst, comp);
}
} //}}}
//{{{ _createInputStream() method
/**
* Creates an input stream. This method is called from the I/O
* thread.
* @param session the VFS session
* @param path The path
* @param ignoreErrors If true, file not found errors should be
* ignored
* @param comp The component that will parent error dialog boxes
* @return an inputstream or <code>null</code> if there was a problem
* @exception IOException If an I/O error occurs
* @since jEdit 2.7pre1
*/
public InputStream _createInputStream(Object session,
String path, boolean ignoreErrors, Component comp)
throws IOException
{
VFSManager.error(comp,path,"vfs.not-supported.load",new String[] { name });
return null;
} //}}}
//{{{ _createOutputStream() method
/**
* Creates an output stream. This method is called from the I/O
* thread.
* @param session the VFS session
* @param path The path
* @param comp The component that will parent error dialog boxes
* @exception IOException If an I/O error occurs
* @since jEdit 2.7pre1
*/
public OutputStream _createOutputStream(Object session,
String path, Component comp)
throws IOException
{
VFSManager.error(comp,path,"vfs.not-supported.save",new String[] { name });
return null;
} //}}}
//{{{ _saveComplete() method
/**
* Called after a file has been saved.
* @param session The VFS session
* @param buffer The buffer
* @param path The path the buffer was saved to (can be different from
* {@link org.gjt.sp.jedit.Buffer#getPath()} if the user invoked the
* <b>Save a Copy As</b> command, for example).
* @param comp The component that will parent error dialog boxes
* @exception IOException If an I/O error occurs
* @since jEdit 4.1pre9
*/
public void _saveComplete(Object session, Buffer buffer, String path,
Component comp) throws IOException {} //}}}
//{{{ _finishTwoStageSave() method
/**
* Called after a file has been saved and we use twoStageSave (first saving to
* another file). This should re-apply permissions for example.
* @param session The VFS session
* @param buffer The buffer
* @param path The path the buffer was saved to (can be different from
* {@link org.gjt.sp.jedit.Buffer#getPath()} if the user invoked the
* <b>Save a Copy As</b> command, for example).
* @param comp The component that will parent error dialog boxes
* @exception IOException If an I/O error occurs
* @since jEdit 4.3pre4
*/
public void _finishTwoStageSave(Object session, Buffer buffer, String path,
Component comp) throws IOException
{
} //}}}
//{{{ _endVFSSession() method
/**
* Finishes the specified VFS session. This must be called
* after all I/O with this VFS is complete, to avoid leaving
* stale network connections and such.
* @param session The VFS session
* @param comp The component that will parent error dialog boxes
* @exception IOException if an I/O error occurred
* @since jEdit 2.7pre1
*/
public void _endVFSSession(Object session, Component comp)
throws IOException
{
} //}}}
//{{{ getDefaultColorFor() method
/**
* Returns color of the specified file name, by matching it against
* user-specified regular expressions.
* @since jEdit 4.0pre1
*/
public static Color getDefaultColorFor(String name)
{
synchronized(lock)
{
if(colors == null)
loadColors();
for (ColorEntry entry : colors)
{
if (entry.getRe().matcher(name).matches())
return entry.getColor();
}
return null;
}
} //}}}
//{{{ DirectoryEntryCompare class
/**
* Implementation of {@link Comparator}
* interface that compares {@link VFSFile} instances.
* @since jEdit 4.2pre1
*/
public static class DirectoryEntryCompare implements Comparator<VFSFile>
{
private final boolean sortIgnoreCase;
private final boolean sortMixFilesAndDirs;
/**
* Creates a new <code>DirectoryEntryCompare</code>.
* @param sortMixFilesAndDirs If false, directories are
* put at the top of the listing.
* @param sortIgnoreCase If false, upper case comes before
* lower case.
*/
public DirectoryEntryCompare(boolean sortMixFilesAndDirs,
boolean sortIgnoreCase)
{
this.sortMixFilesAndDirs = sortMixFilesAndDirs;
this.sortIgnoreCase = sortIgnoreCase;
}
@Override
public int compare(VFSFile file1, VFSFile file2)
{
if(!sortMixFilesAndDirs)
{
if(file1.getType() != file2.getType())
return file2.getType() - file1.getType();
}
return StandardUtilities.compareStrings(file1.getName(),
file2.getName(),sortIgnoreCase);
}
} //}}}
//{{{ Private members
private final String name;
private final int caps;
private final String[] extAttrs;
private static List<ColorEntry> colors;
private static final Object lock = new Object();
//{{{ Class initializer
static
{
EditBus.addToBus(new EBComponent()
{
@Override
public void handleMessage(EBMessage msg)
{
if(msg instanceof PropertiesChanged)
{
synchronized(lock)
{
colors = null;
}
}
}
});
} //}}}
//{{{ recursive listFiles() method
private void listFiles(Object session, Collection<String> stack,
List<String> files, String directory, VFSFileFilter filter, boolean recursive,
Component comp, boolean skipBinary, boolean skipHidden) throws IOException
{
if (recursive && !MiscUtilities.isURL(directory))
{
String resolvedPath = MiscUtilities.resolveSymlinks(directory);
/*
* If looking at a symlink, do not traverse the
* resolved path more than once.
*/
if (!directory.equals(resolvedPath))
{
if (stack.contains(resolvedPath))
{
Log.log(Log.ERROR,this,
"Recursion in listFiles(): "
+ directory);
return;
}
stack.add(resolvedPath);
}
}
VFSFile[] _files = _listFiles(session,directory,
comp);
if(_files == null || _files.length == 0)
return;
for(int i = 0; i < _files.length; i++)
{
if (Thread.currentThread().isInterrupted())
break;
VFSFile file = _files[i];
if (skipHidden && (file.isHidden() || MiscUtilities.isBackup(file.getName())))
continue;
if(!filter.accept(file))
continue;
if(file.getType() == VFSFile.DIRECTORY
|| file.getType() == VFSFile.FILESYSTEM)
{
if(recursive)
{
String canonPath = _canonPath(session,
file.getPath(),comp);
listFiles(session,stack,files,
canonPath,filter,recursive,
comp, skipBinary, skipHidden);
}
}
else // It's a regular file
{
if (skipBinary)
{
try
{
if (file.isBinary(session))
{
Log.log(Log.NOTICE,this
,file.getPath() + ": skipped as a binary file");
continue;
}
}
catch(IOException e)
{
Log.log(Log.ERROR,this,e);
// may be not binary...
}
}
files.add(file.getPath());
}
}
} //}}}
//{{{ loadColors() method
private static void loadColors()
{
synchronized(lock)
{
colors = new ArrayList<>();
if(!jEdit.getBooleanProperty("vfs.browser.colorize"))
return;
String glob;
int i = 0;
while((glob = jEdit.getProperty("vfs.browser.colors." + i + ".glob")) != null)
{
try
{
colors.add(new ColorEntry(
Pattern.compile(StandardUtilities.globToRE(glob)),
jEdit.getColorProperty(
"vfs.browser.colors." + i + ".color",
Color.black)));
}
catch(PatternSyntaxException e)
{
Log.log(Log.ERROR,VFS.class,"Invalid regular expression: " + glob);
Log.log(Log.ERROR,VFS.class,e);
}
i++;
}
}
} //}}}
//{{{ ColorEntry class
private static class ColorEntry
{
private final Pattern re;
private final Color color;
ColorEntry(Pattern re, Color color)
{
this.re = re;
this.color = color;
}
public Pattern getRe()
{
return re;
}
public Color getColor()
{
return color;
}
} //}}}
//{{{ SessionGetter class
private class SessionGetter implements Runnable
{
SessionGetter(String path, Component comp)
{
this.path = path;
this.comp = comp;
}
private Object session;
private final String path;
private final Component comp;
@Override
public void run()
{
session = createVFSSession(path, comp);
}
public Object get() { return session; }
} //}}}
//}}}
}
¤ Dauer der Verarbeitung: 0.86 Sekunden
(vorverarbeitet)
¤
|
Haftungshinweis
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.
|