0001: /*
0002: * VFS.java - Virtual filesystem implementation
0003: * :tabSize=8:indentSize=8:noTabs=false:
0004: * :folding=explicit:collapseFolds=1:
0005: *
0006: * Copyright (C) 2000, 2003 Slava Pestov
0007: *
0008: * This program is free software; you can redistribute it and/or
0009: * modify it under the terms of the GNU General Public License
0010: * as published by the Free Software Foundation; either version 2
0011: * of the License, or any later version.
0012: *
0013: * This program is distributed in the hope that it will be useful,
0014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0016: * GNU General Public License for more details.
0017: *
0018: * You should have received a copy of the GNU General Public License
0019: * along with this program; if not, write to the Free Software
0020: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
0021: */
0022:
0023: package org.gjt.sp.jedit.io;
0024:
0025: //{{{ Imports
0026: import java.awt.Color;
0027: import java.awt.Component;
0028: import java.io.*;
0029: import java.util.*;
0030:
0031: import java.util.regex.Pattern;
0032: import java.util.regex.PatternSyntaxException;
0033:
0034: import org.gjt.sp.jedit.msg.PropertiesChanged;
0035: import org.gjt.sp.jedit.*;
0036: import org.gjt.sp.jedit.bufferio.BufferLoadRequest;
0037: import org.gjt.sp.jedit.bufferio.BufferSaveRequest;
0038: import org.gjt.sp.jedit.bufferio.BufferInsertRequest;
0039: import org.gjt.sp.jedit.bufferio.BufferIORequest;
0040: import org.gjt.sp.util.Log;
0041: import org.gjt.sp.util.ProgressObserver;
0042: import org.gjt.sp.util.IOUtilities;
0043: import org.gjt.sp.util.StandardUtilities; //}}}
0044: import org.gjt.sp.util.WorkThread;
0045:
0046: /**
0047: * A virtual filesystem implementation.<p>
0048: *
0049: * Plugins can provide virtual file systems by defining entries in their
0050: * <code>services.xml</code> files like so:
0051: *
0052: * <pre><SERVICE CLASS="org.gjt.sp.jedit.io.VFS" NAME="<i>name</i>">
0053: * new <i>MyVFS</i>();
0054: *</SERVICE></pre>
0055: *
0056: * URLs of the form <code><i>name</i>:<i>path</i></code> will then be handled
0057: * by the VFS named <code><i>name</i></code>.<p>
0058: *
0059: * See {@link org.gjt.sp.jedit.ServiceManager} for details.<p>
0060: *
0061: * <h3>Session objects:</h3>
0062: *
0063: * A session is used to persist things like login information, any network
0064: * sockets, etc. File system implementations that do not need this kind of
0065: * persistence return a dummy object as a session.<p>
0066: *
0067: * Methods whose names are prefixed with "_" expect to be given a
0068: * previously-obtained session object. A session must be obtained from the AWT
0069: * thread in one of two ways:
0070: *
0071: * <ul>
0072: * <li>{@link #createVFSSession(String,Component)}</li>
0073: * <li>{@link #showBrowseDialog(Object[],Component)}</li>
0074: * </ul>
0075: *
0076: * When done, the session must be disposed of using
0077: * {@link #_endVFSSession(Object,Component)}.<p>
0078: *
0079: * <h3>Thread safety:</h3>
0080: *
0081: * The following methods cannot be called from an I/O thread:
0082: *
0083: * <ul>
0084: * <li>{@link #createVFSSession(String,Component)}</li>
0085: * <li>{@link #insert(View,Buffer,String)}</li>
0086: * <li>{@link #load(View,Buffer,String)}</li>
0087: * <li>{@link #save(View,Buffer,String)}</li>
0088: * <li>{@link #showBrowseDialog(Object[],Component)}</li>
0089: * </ul>
0090: *
0091: * All remaining methods are required to be thread-safe in subclasses.
0092: *
0093: * <h3>Implementing a VFS</h3>
0094: *
0095: * You can override as many or as few methods as you want. Make sure
0096: * {@link #getCapabilities()} returns a value reflecting the functionality
0097: * implemented by your VFS.
0098: *
0099: * @see VFSManager#getVFSForPath(String)
0100: * @see VFSManager#getVFSForProtocol(String)
0101: *
0102: * @author Slava Pestov
0103: * @author $Id: VFS.java 10773 2007-09-30 19:21:03Z kpouer $
0104: */
0105: public abstract class VFS {
0106: //{{{ Capabilities
0107:
0108: /**
0109: * Read capability.
0110: * @since jEdit 2.6pre2
0111: */
0112: public static final int READ_CAP = 1 << 0;
0113:
0114: /**
0115: * Write capability.
0116: * @since jEdit 2.6pre2
0117: */
0118: public static final int WRITE_CAP = 1 << 1;
0119:
0120: /**
0121: * Browse capability
0122: * @since jEdit 4.3pre11
0123: *
0124: * This was the official API for adding items to a file
0125: * system browser's <b>Plugins</b> menu in jEdit 4.1 and earlier. In
0126: * jEdit 4.2, there is a different way of doing this, you must provide
0127: * a <code>browser.actions.xml</code> file in your plugin JAR, and
0128: * define <code>plugin.<i>class</i>.browser-menu-item</code>
0129: * or <code>plugin.<i>class</i>.browser-menu</code> properties.
0130: * See {@link org.gjt.sp.jedit.EditPlugin} for details.
0131: */
0132: public static final int BROWSE_CAP = 1 << 2;
0133:
0134: /**
0135: * Delete file capability.
0136: * @since jEdit 2.6pre2
0137: */
0138: public static final int DELETE_CAP = 1 << 3;
0139:
0140: /**
0141: * Rename file capability.
0142: * @since jEdit 2.6pre2
0143: */
0144: public static final int RENAME_CAP = 1 << 4;
0145:
0146: /**
0147: * Make directory capability.
0148: * @since jEdit 2.6pre2
0149: */
0150: public static final int MKDIR_CAP = 1 << 5;
0151:
0152: /**
0153: * Low latency capability. If this is not set, then a confirm dialog
0154: * will be shown before doing a directory search in this VFS.
0155: * @since jEdit 4.1pre1
0156: */
0157: public static final int LOW_LATENCY_CAP = 1 << 6;
0158:
0159: /**
0160: * Case insensitive file system capability.
0161: * @since jEdit 4.1pre1
0162: */
0163: public static final int CASE_INSENSITIVE_CAP = 1 << 7;
0164:
0165: //}}}
0166:
0167: //{{{ Extended attributes
0168: /**
0169: * File type.
0170: * @since jEdit 4.2pre1
0171: */
0172: public static final String EA_TYPE = "type";
0173:
0174: /**
0175: * File status (read only, read write, etc).
0176: * @since jEdit 4.2pre1
0177: */
0178: public static final String EA_STATUS = "status";
0179:
0180: /**
0181: * File size.
0182: * @since jEdit 4.2pre1
0183: */
0184: public static final String EA_SIZE = "size";
0185:
0186: /**
0187: * File last modified date.
0188: * @since jEdit 4.2pre1
0189: */
0190: public static final String EA_MODIFIED = "modified";
0191: //}}}
0192:
0193: public static int IOBUFSIZE = 32678;
0194:
0195: //{{{ VFS constructor
0196: /**
0197: * @deprecated Use the form where the constructor takes a capability
0198: * list.
0199: */
0200: public VFS(String name) {
0201: this (name, 0);
0202: } //}}}
0203:
0204: //{{{ VFS constructor
0205: /**
0206: * Creates a new virtual filesystem.
0207: * @param name The name
0208: * @param caps The capabilities
0209: */
0210: public VFS(String name, int caps) {
0211: this .name = name;
0212: this .caps = caps;
0213: // reasonable defaults (?)
0214: this .extAttrs = new String[] { EA_SIZE, EA_TYPE };
0215: } //}}}
0216:
0217: //{{{ VFS constructor
0218: /**
0219: * Creates a new virtual filesystem.
0220: * @param name The name
0221: * @param caps The capabilities
0222: * @param extAttrs The extended attributes
0223: * @since jEdit 4.2pre1
0224: */
0225: public VFS(String name, int caps, String[] extAttrs) {
0226: this .name = name;
0227: this .caps = caps;
0228: this .extAttrs = extAttrs;
0229: } //}}}
0230:
0231: //{{{ getName() method
0232: /**
0233: * Returns this VFS's name. The name is used to obtain the
0234: * label stored in the <code>vfs.<i>name</i>.label</code>
0235: * property.
0236: */
0237: public String getName() {
0238: return name;
0239: } //}}}
0240:
0241: //{{{ getCapabilities() method
0242: /**
0243: * Returns the capabilities of this VFS.
0244: * @since jEdit 2.6pre2
0245: */
0246: public int getCapabilities() {
0247: return caps;
0248: } //}}}
0249:
0250: //{{{ isMarkersFileSupported() method
0251: /**
0252: * Returns if an additional markers file can be saved by this VFS.
0253: * Default is {@code true}.
0254: *
0255: * @since jEdit 4.3pre10
0256: */
0257: public boolean isMarkersFileSupported() {
0258: return true;
0259: } //}}}
0260:
0261: //{{{ getExtendedAttributes() method
0262: /**
0263: * Returns the extended attributes supported by this VFS.
0264: * @since jEdit 4.2pre1
0265: */
0266: public String[] getExtendedAttributes() {
0267: return extAttrs;
0268: } //}}}
0269:
0270: //{{{ showBrowseDialog() method
0271: /**
0272: * Displays a dialog box that should set up a session and return
0273: * the initial URL to browse.
0274: * @param session Where the VFS session will be stored
0275: * @param comp The component that will parent error dialog boxes
0276: * @return The URL
0277: * @since jEdit 2.7pre1
0278: * @deprecated This function is not used in the jEdit core anymore,
0279: * so it doesn't have to be provided anymore. If you want
0280: * to use it for another purpose like in the FTP plugin,
0281: * feel free to do so.
0282: */
0283: @Deprecated
0284: public String showBrowseDialog(Object[] session, Component comp) {
0285: return null;
0286: } //}}}
0287:
0288: //{{{ getFileName() method
0289: /**
0290: * Returns the file name component of the specified path.
0291: * @param path The path
0292: * @since jEdit 3.1pre4
0293: */
0294: public String getFileName(String path) {
0295: if (path.equals("/"))
0296: return path;
0297:
0298: while (path.endsWith("/") || path.endsWith(File.separator))
0299: path = path.substring(0, path.length() - 1);
0300:
0301: int index = Math.max(path.lastIndexOf('/'), path
0302: .lastIndexOf(File.separatorChar));
0303: if (index == -1)
0304: index = path.indexOf(':');
0305:
0306: // don't want getFileName("roots:") to return ""
0307: if (index == -1 || index == path.length() - 1)
0308: return path;
0309:
0310: return path.substring(index + 1);
0311: } //}}}
0312:
0313: //{{{ getParentOfPath() method
0314: /**
0315: * Returns the parent of the specified path. This must be
0316: * overridden to return a non-null value for browsing of this
0317: * filesystem to work.
0318: * @param path The path
0319: * @since jEdit 2.6pre5
0320: */
0321: public String getParentOfPath(String path) {
0322: // ignore last character of path to properly handle
0323: // paths like /foo/bar/
0324: int lastIndex = path.length() - 1;
0325: while (lastIndex > 0
0326: && (path.charAt(lastIndex) == File.separatorChar || path
0327: .charAt(lastIndex) == '/')) {
0328: lastIndex--;
0329: }
0330:
0331: int count = Math.max(0, lastIndex);
0332: int index = path.lastIndexOf(File.separatorChar, count);
0333: if (index == -1)
0334: index = path.lastIndexOf('/', count);
0335: if (index == -1) {
0336: // this ensures that getFileParent("protocol:"), for
0337: // example, is "protocol:" and not "".
0338: index = path.lastIndexOf(':');
0339: }
0340:
0341: return path.substring(0, index + 1);
0342: } //}}}
0343:
0344: //{{{ constructPath() method
0345: /**
0346: * Constructs a path from the specified directory and
0347: * file name component. This must be overridden to return a
0348: * non-null value, otherwise browsing this filesystem will
0349: * not work.<p>
0350: *
0351: * Unless you are writing a VFS, this method should not be called
0352: * directly. To ensure correct behavior, you <b>must</b> call
0353: * {@link org.gjt.sp.jedit.MiscUtilities#constructPath(String,String)}
0354: * instead.
0355: *
0356: * @param parent The parent directory
0357: * @param path The path
0358: * @since jEdit 2.6pre2
0359: */
0360: public String constructPath(String parent, String path) {
0361: return parent + path;
0362: } //}}}
0363:
0364: //{{{ getFileSeparator() method
0365: /**
0366: * Returns the file separator used by this VFS.
0367: * @since jEdit 2.6pre9
0368: */
0369: public char getFileSeparator() {
0370: return '/';
0371: } //}}}
0372:
0373: //{{{ getTwoStageSaveName() method
0374: /**
0375: * Returns a temporary file name based on the given path.
0376: *
0377: * By default jEdit first saves a file to <code>#<i>name</i>#save#</code>
0378: * and then renames it to the original file. However some virtual file
0379: * systems might not support the <code>#</code> character in filenames,
0380: * so this method permits the VFS to override this behavior.
0381: *
0382: * If this method returns <code>null</code>, two stage save will not
0383: * be used for that particular file (introduced in jEdit 4.3pre1).
0384: *
0385: * @param path The path name
0386: * @since jEdit 4.1pre7
0387: */
0388: public String getTwoStageSaveName(String path) {
0389: return MiscUtilities.constructPath(getParentOfPath(path), '#'
0390: + getFileName(path) + "#save#");
0391: } //}}}
0392:
0393: //{{{ reloadDirectory() method
0394: /**
0395: * Called before a directory is reloaded by the file system browser.
0396: * Can be used to flush a cache, etc.
0397: * @since jEdit 4.0pre3
0398: */
0399: public void reloadDirectory(String path) {
0400: } //}}}
0401:
0402: //{{{ createVFSSession() method
0403: /**
0404: * Creates a VFS session. This method is called from the AWT thread,
0405: * so it should not do any I/O. It could, however, prompt for
0406: * a login name and password, for example.
0407: * @param path The path in question
0408: * @param comp The component that will parent any dialog boxes shown
0409: * @return The session. The session can be null if there were errors
0410: * @since jEdit 2.6pre3
0411: */
0412: public Object createVFSSession(String path, Component comp) {
0413: return new Object();
0414: } //}}}
0415:
0416: //{{{ load() method
0417: /**
0418: * Loads the specified buffer. The default implementation posts
0419: * an I/O request to the I/O thread.
0420: * @param view The view
0421: * @param buffer The buffer
0422: * @param path The path
0423: */
0424: public boolean load(View view, Buffer buffer, String path) {
0425: if ((getCapabilities() & READ_CAP) == 0) {
0426: VFSManager.error(view, path, "vfs.not-supported.load",
0427: new String[] { name });
0428: return false;
0429: }
0430:
0431: Object session = createVFSSession(path, view);
0432: if (session == null)
0433: return false;
0434:
0435: if ((getCapabilities() & WRITE_CAP) == 0)
0436: buffer.setReadOnly(true);
0437:
0438: BufferIORequest request = new BufferLoadRequest(view, buffer,
0439: session, this , path);
0440: if (buffer.isTemporary())
0441: // this makes HyperSearch much faster
0442: request.run();
0443: else
0444: VFSManager.runInWorkThread(request);
0445:
0446: return true;
0447: } //}}}
0448:
0449: //{{{ save() method
0450: /**
0451: * Saves the specifies buffer. The default implementation posts
0452: * an I/O request to the I/O thread.
0453: * @param view The view
0454: * @param buffer The buffer
0455: * @param path The path
0456: */
0457: public boolean save(View view, Buffer buffer, String path) {
0458: if ((getCapabilities() & WRITE_CAP) == 0) {
0459: VFSManager.error(view, path, "vfs.not-supported.save",
0460: new String[] { name });
0461: return false;
0462: }
0463:
0464: Object session = createVFSSession(path, view);
0465: if (session == null)
0466: return false;
0467:
0468: /* When doing a 'save as', the path to save to (path)
0469: * will not be the same as the buffer's previous path
0470: * (buffer.getPath()). In that case, we want to create
0471: * a backup of the new path, even if the old path was
0472: * backed up as well (BACKED_UP property set) */
0473: if (!path.equals(buffer.getPath()))
0474: buffer.unsetProperty(Buffer.BACKED_UP);
0475:
0476: VFSManager.runInWorkThread(new BufferSaveRequest(view, buffer,
0477: session, this , path));
0478: return true;
0479: } //}}}
0480:
0481: //{{{ copy() method
0482: /**
0483: * Copy a file to another using VFS.
0484: *
0485: * @param progress the progress observer. It could be null if you don't want to monitor progress. If not null
0486: * you should probably launch this command in a WorkThread
0487: * @param sourceVFS the source VFS
0488: * @param sourceSession the VFS session
0489: * @param sourcePath the source path
0490: * @param targetVFS the target VFS
0491: * @param targetSession the target session
0492: * @param targetPath the target path
0493: * @param comp comp The component that will parent error dialog boxes
0494: * @return true if the copy was successful
0495: * @throws IOException IOException If an I/O error occurs
0496: * @since jEdit 4.3pre3
0497: */
0498: public static boolean copy(ProgressObserver progress,
0499: VFS sourceVFS, Object sourceSession, String sourcePath,
0500: VFS targetVFS, Object targetSession, String targetPath,
0501: Component comp, boolean canStop) throws IOException {
0502: if (progress != null)
0503: progress.setStatus("Initializing");
0504:
0505: InputStream in = null;
0506: OutputStream out = null;
0507: try {
0508: if (progress != null) {
0509: VFSFile sourceVFSFile = sourceVFS._getFile(
0510: sourceSession, sourcePath, comp);
0511: if (sourceVFSFile == null)
0512: throw new FileNotFoundException(sourcePath);
0513:
0514: progress.setMaximum(sourceVFSFile.getLength());
0515: }
0516: in = new BufferedInputStream(sourceVFS._createInputStream(
0517: sourceSession, sourcePath, false, comp));
0518: out = new BufferedOutputStream(targetVFS
0519: ._createOutputStream(targetSession, targetPath,
0520: comp));
0521: boolean copyResult = IOUtilities.copyStream(IOBUFSIZE,
0522: progress, in, out, canStop);
0523: VFSManager.sendVFSUpdate(targetVFS, targetPath, true);
0524: return copyResult;
0525: } finally {
0526: IOUtilities.closeQuietly(in);
0527: IOUtilities.closeQuietly(out);
0528: }
0529: } //}}}
0530:
0531: //{{{ copy() method
0532: /**
0533: * Copy a file to another using VFS.
0534: *
0535: * @param progress the progress observer. It could be null if you don't want to monitor progress. If not null
0536: * you should probably launch this command in a WorkThread
0537: * @param sourcePath the source path
0538: * @param targetPath the target path
0539: * @param comp comp The component that will parent error dialog boxes
0540: * @param canStop if true the copy can be stopped
0541: * @return true if the copy was successful
0542: * @throws IOException IOException If an I/O error occurs
0543: * @since jEdit 4.3pre3
0544: */
0545: public static boolean copy(ProgressObserver progress,
0546: String sourcePath, String targetPath, Component comp,
0547: boolean canStop) throws IOException {
0548: VFS sourceVFS = VFSManager.getVFSForPath(sourcePath);
0549: Object sourceSession = sourceVFS.createVFSSession(sourcePath,
0550: comp);
0551: if (sourceSession == null) {
0552: Log.log(Log.WARNING, VFS.class,
0553: "Unable to get a valid session from " + sourceVFS
0554: + " for path " + sourcePath);
0555: return false;
0556: }
0557: VFS targetVFS = VFSManager.getVFSForPath(targetPath);
0558: Object targetSession = targetVFS.createVFSSession(targetPath,
0559: comp);
0560: if (targetSession == null) {
0561: Log.log(Log.WARNING, VFS.class,
0562: "Unable to get a valid session from " + targetVFS
0563: + " for path " + targetPath);
0564: return false;
0565: }
0566: return copy(progress, sourceVFS, sourceSession, sourcePath,
0567: targetVFS, targetSession, targetPath, comp, canStop);
0568: } //}}}
0569:
0570: //{{{ insert() method
0571: /**
0572: * Inserts a file into the specified buffer. The default implementation
0573: * posts an I/O request to the I/O thread.
0574: * @param view The view
0575: * @param buffer The buffer
0576: * @param path The path
0577: */
0578: public boolean insert(View view, Buffer buffer, String path) {
0579: if ((getCapabilities() & READ_CAP) == 0) {
0580: VFSManager.error(view, path, "vfs.not-supported.load",
0581: new String[] { name });
0582: return false;
0583: }
0584:
0585: Object session = createVFSSession(path, view);
0586: if (session == null)
0587: return false;
0588:
0589: VFSManager.runInWorkThread(new BufferInsertRequest(view,
0590: buffer, session, this , path));
0591: return true;
0592: } //}}}
0593:
0594: // A method name that starts with _ requires a session object
0595:
0596: //{{{ _canonPath() method
0597: /**
0598: * Returns the canonical form of the specified path name. For example,
0599: * <code>~</code> might be expanded to the user's home directory.
0600: * @param session The session
0601: * @param path The path
0602: * @param comp The component that will parent error dialog boxes
0603: * @exception IOException if an I/O error occurred
0604: * @since jEdit 4.0pre2
0605: */
0606: public String _canonPath(Object session, String path, Component comp)
0607: throws IOException {
0608: return path;
0609: } //}}}
0610:
0611: //{{{ _listDirectory() method
0612: /**
0613: * A convinience method that matches file names against globs, and can
0614: * optionally list the directory recursively.
0615: * @param session The session
0616: * @param directory The directory. Note that this must be a full
0617: * URL, including the host name, path name, and so on. The
0618: * username and password (if needed by the VFS) is obtained from the
0619: * session instance.
0620: * @param glob Only file names matching this glob will be returned
0621: * @param recursive If true, subdirectories will also be listed.
0622: * @param comp The component that will parent error dialog boxes
0623: * @exception IOException if an I/O error occurred
0624: * @since jEdit 4.1pre1
0625: */
0626: public String[] _listDirectory(Object session, String directory,
0627: String glob, boolean recursive, Component comp)
0628: throws IOException {
0629: String[] retval = null;
0630: retval = _listDirectory(session, directory, glob, recursive,
0631: comp, true, false);
0632: return retval;
0633: } //}}}
0634:
0635: //{{{ _listDirectory() method
0636: /**
0637: * A convinience method that matches file names against globs, and can
0638: * optionally list the directory recursively.
0639: * @param session The session
0640: * @param directory The directory. Note that this must be a full
0641: * URL, including the host name, path name, and so on. The
0642: * username and password (if needed by the VFS) is obtained from the
0643: * session instance.
0644: * @param glob Only file names matching this glob will be returned
0645: * @param recursive If true, subdirectories will also be listed.
0646: * @param comp The component that will parent error dialog boxes
0647: * @exception IOException if an I/O error occurred
0648: * @param skipBinary ignore binary files (do not return them).
0649: * This will slow down the process since it will open the files
0650: * @param skipHidden skips hidden files, directories, and
0651: * backup files. Ignores any file beginning with . or #, or ending with ~
0652: * or .bak
0653: *
0654: *
0655: * @since jEdit 4.3pre5
0656: */
0657: public String[] _listDirectory(Object session, String directory,
0658: String glob, boolean recursive, Component comp,
0659: boolean skipBinary, boolean skipHidden) throws IOException {
0660: VFSFileFilter filter = new GlobVFSFileFilter(glob);
0661: return _listDirectory(session, directory, filter, recursive,
0662: comp, skipBinary, skipHidden);
0663: } //}}}
0664:
0665: //{{{ _listDirectory() method
0666: /**
0667: * A convinience method that filters the directory listing
0668: * according to a filter, and can optionally list the directory
0669: * recursively.
0670: * @param session The session
0671: * @param directory The directory. Note that this must be a full
0672: * URL, including the host name, path name, and so on. The
0673: * username and password (if needed by the VFS) is obtained from the
0674: * session instance.
0675: * @param filter The {@link VFSFileFilter} to use for filtering.
0676: * @param recursive If true, subdirectories will also be listed.
0677: * @param comp The component that will parent error dialog boxes
0678: * @exception IOException if an I/O error occurred
0679: * @param skipBinary ignore binary files (do not return them).
0680: * This will slow down the process since it will open the files
0681: * @param skipHidden skips hidden files, directories, and
0682: * backup files. Ignores any file beginning with . or #, or ending with ~
0683: * or .bak
0684: *
0685: * @since jEdit 4.3pre7
0686: */
0687: public String[] _listDirectory(Object session, String directory,
0688: VFSFileFilter filter, boolean recursive, Component comp,
0689: boolean skipBinary, boolean skipHidden) throws IOException {
0690: Log.log(Log.DEBUG, this , "Listing " + directory);
0691: List<String> files = new ArrayList<String>(100);
0692:
0693: listFiles(session, new HashSet<String>(), files, directory,
0694: filter, recursive, comp, skipBinary, skipHidden);
0695:
0696: String[] retVal = files.toArray(new String[files.size()]);
0697:
0698: Arrays.sort(retVal, new MiscUtilities.StringICaseCompare());
0699:
0700: return retVal;
0701: } //}}}
0702:
0703: //{{{ _listFiles() method
0704: /**
0705: * Lists the specified directory.
0706: * @param session The session
0707: * @param directory The directory. Note that this must be a full
0708: * URL, including the host name, path name, and so on. The
0709: * username and password (if needed by the VFS) is obtained from the
0710: * session instance.
0711: * @param comp The component that will parent error dialog boxes
0712: * @exception IOException if an I/O error occurred
0713: * @since jEdit 4.3pre2
0714: */
0715: public VFSFile[] _listFiles(Object session, String directory,
0716: Component comp) throws IOException {
0717: return _listDirectory(session, directory, comp);
0718: } //}}}
0719:
0720: //{{{ _listDirectory() method
0721: /**
0722: * @deprecated Use <code>_listFiles()</code> instead.
0723: */
0724: public DirectoryEntry[] _listDirectory(Object session,
0725: String directory, Component comp) throws IOException {
0726: VFSManager.error(comp, directory, "vfs.not-supported.list",
0727: new String[] { name });
0728: return null;
0729: } //}}}
0730:
0731: //{{{ _getFile() method
0732: /**
0733: * Returns the specified directory entry.
0734: * @param session The session get it with {@link VFS#createVFSSession(String, Component)}
0735: * @param path The path
0736: * @param comp The component that will parent error dialog boxes
0737: * @exception IOException if an I/O error occurred
0738: * @return The specified directory entry, or null if it doesn't exist.
0739: * @since jEdit 4.3pre2
0740: */
0741: public VFSFile _getFile(Object session, String path, Component comp)
0742: throws IOException {
0743: return _getDirectoryEntry(session, path, comp);
0744: } //}}}
0745:
0746: //{{{ _getDirectoryEntry() method
0747: /**
0748: * Returns the specified directory entry.
0749: * @param session The session get it with {@link VFS#createVFSSession(String, Component)}
0750: * @param path The path
0751: * @param comp The component that will parent error dialog boxes
0752: * @exception IOException if an I/O error occurred
0753: * @return The specified directory entry, or null if it doesn't exist.
0754: * @since jEdit 2.7pre1
0755: * @deprecated Use <code>_getFile()</code> instead.
0756: */
0757: public DirectoryEntry _getDirectoryEntry(Object session,
0758: String path, Component comp) throws IOException {
0759: return null;
0760: } //}}}
0761:
0762: //{{{ DirectoryEntry class
0763: /**
0764: * @deprecated Use <code>VFSFile</code> instead.
0765: */
0766: public static class DirectoryEntry extends VFSFile {
0767: //{{{ DirectoryEntry constructor
0768: /**
0769: * @since jEdit 4.2pre2
0770: */
0771: public DirectoryEntry() {
0772: } //}}}
0773:
0774: //{{{ DirectoryEntry constructor
0775: public DirectoryEntry(String name, String path,
0776: String deletePath, int type, long length, boolean hidden) {
0777: this .name = name;
0778: this .path = path;
0779: this .deletePath = deletePath;
0780: this .symlinkPath = path;
0781: this .type = type;
0782: this .length = length;
0783: this .hidden = hidden;
0784: if (path != null) {
0785: // maintain backwards compatibility
0786: VFS vfs = VFSManager.getVFSForPath(path);
0787: canRead = ((vfs.getCapabilities() & READ_CAP) != 0);
0788: canWrite = ((vfs.getCapabilities() & WRITE_CAP) != 0);
0789: }
0790: } //}}}
0791: } //}}}
0792:
0793: //{{{ _delete() method
0794: /**
0795: * Deletes the specified URL.
0796: * @param session The VFS session
0797: * @param path The path
0798: * @param comp The component that will parent error dialog boxes
0799: * @exception IOException if an I/O error occurs
0800: * @since jEdit 2.7pre1
0801: */
0802: public boolean _delete(Object session, String path, Component comp)
0803: throws IOException {
0804: return false;
0805: } //}}}
0806:
0807: //{{{ _rename() method
0808: /**
0809: * Renames the specified URL. Some filesystems might support moving
0810: * URLs between directories, however others may not. Do not rely on
0811: * this behavior.
0812: * @param session The VFS session
0813: * @param from The old path
0814: * @param to The new path
0815: * @param comp The component that will parent error dialog boxes
0816: * @exception IOException if an I/O error occurs
0817: * @since jEdit 2.7pre1
0818: */
0819: public boolean _rename(Object session, String from, String to,
0820: Component comp) throws IOException {
0821: return false;
0822: } //}}}
0823:
0824: //{{{ _mkdir() method
0825: /**
0826: * Creates a new directory with the specified URL.
0827: * @param session The VFS session
0828: * @param directory The directory
0829: * @param comp The component that will parent error dialog boxes
0830: * @exception IOException if an I/O error occurs
0831: * @since jEdit 2.7pre1
0832: */
0833: public boolean _mkdir(Object session, String directory,
0834: Component comp) throws IOException {
0835: return false;
0836: } //}}}
0837:
0838: //{{{ _backup() method
0839: /**
0840: * Backs up the specified file. This should only be overriden by
0841: * the local filesystem VFS.
0842: * @param session The VFS session
0843: * @param path The path
0844: * @param comp The component that will parent error dialog boxes
0845: * @exception IOException if an I/O error occurs
0846: * @since jEdit 3.2pre2
0847: */
0848: public void _backup(Object session, String path, Component comp)
0849: throws IOException {
0850: } //}}}
0851:
0852: //{{{ _createInputStream() method
0853: /**
0854: * Creates an input stream. This method is called from the I/O
0855: * thread.
0856: * @param session the VFS session
0857: * @param path The path
0858: * @param ignoreErrors If true, file not found errors should be
0859: * ignored
0860: * @param comp The component that will parent error dialog boxes
0861: * @return an inputstream or <code>null</code> if there was a problem
0862: * @exception IOException If an I/O error occurs
0863: * @since jEdit 2.7pre1
0864: */
0865: public InputStream _createInputStream(Object session, String path,
0866: boolean ignoreErrors, Component comp) throws IOException {
0867: VFSManager.error(comp, path, "vfs.not-supported.load",
0868: new String[] { name });
0869: return null;
0870: } //}}}
0871:
0872: //{{{ _createOutputStream() method
0873: /**
0874: * Creates an output stream. This method is called from the I/O
0875: * thread.
0876: * @param session the VFS session
0877: * @param path The path
0878: * @param comp The component that will parent error dialog boxes
0879: * @exception IOException If an I/O error occurs
0880: * @since jEdit 2.7pre1
0881: */
0882: public OutputStream _createOutputStream(Object session,
0883: String path, Component comp) throws IOException {
0884: VFSManager.error(comp, path, "vfs.not-supported.save",
0885: new String[] { name });
0886: return null;
0887: } //}}}
0888:
0889: //{{{ _saveComplete() method
0890: /**
0891: * Called after a file has been saved.
0892: * @param session The VFS session
0893: * @param buffer The buffer
0894: * @param path The path the buffer was saved to (can be different from
0895: * {@link org.gjt.sp.jedit.Buffer#getPath()} if the user invoked the
0896: * <b>Save a Copy As</b> command, for example).
0897: * @param comp The component that will parent error dialog boxes
0898: * @exception IOException If an I/O error occurs
0899: * @since jEdit 4.1pre9
0900: */
0901: public void _saveComplete(Object session, Buffer buffer,
0902: String path, Component comp) throws IOException {
0903: } //}}}
0904:
0905: //{{{ _finishTwoStageSave() method
0906: /**
0907: * Called after a file has been saved and we use twoStageSave (first saving to
0908: * another file). This should re-apply permissions for example.
0909:
0910: * @param session The VFS session
0911: * @param buffer The buffer
0912: * @param path The path the buffer was saved to (can be different from
0913: * {@link org.gjt.sp.jedit.Buffer#getPath()} if the user invoked the
0914: * <b>Save a Copy As</b> command, for example).
0915: * @param comp The component that will parent error dialog boxes
0916: * @exception IOException If an I/O error occurs
0917: * @since jEdit 4.3pre4
0918: */
0919: public void _finishTwoStageSave(Object session, Buffer buffer,
0920: String path, Component comp) throws IOException {
0921: } //}}}
0922:
0923: //{{{ _endVFSSession() method
0924: /**
0925: * Finishes the specified VFS session. This must be called
0926: * after all I/O with this VFS is complete, to avoid leaving
0927: * stale network connections and such.
0928: * @param session The VFS session
0929: * @param comp The component that will parent error dialog boxes
0930: * @exception IOException if an I/O error occurred
0931: * @since jEdit 2.7pre1
0932: */
0933: public void _endVFSSession(Object session, Component comp)
0934: throws IOException {
0935: } //}}}
0936:
0937: //{{{ getDefaultColorFor() method
0938: /**
0939: * Returns color of the specified file name, by matching it against
0940: * user-specified regular expressions.
0941: * @since jEdit 4.0pre1
0942: */
0943: public static Color getDefaultColorFor(String name) {
0944: synchronized (lock) {
0945: if (colors == null)
0946: loadColors();
0947:
0948: for (int i = 0; i < colors.size(); i++) {
0949: ColorEntry entry = colors.get(i);
0950: if (entry.re.matcher(name).matches())
0951: return entry.color;
0952: }
0953:
0954: return null;
0955: }
0956: } //}}}
0957:
0958: //{{{ DirectoryEntryCompare class
0959: /**
0960: * Implementation of {@link Comparator}
0961: * interface that compares {@link VFS.DirectoryEntry} instances.
0962: * @since jEdit 4.2pre1
0963: */
0964: public static class DirectoryEntryCompare implements Comparator {
0965: private boolean sortIgnoreCase, sortMixFilesAndDirs;
0966:
0967: /**
0968: * Creates a new <code>DirectoryEntryCompare</code>.
0969: * @param sortMixFilesAndDirs If false, directories are
0970: * put at the top of the listing.
0971: * @param sortIgnoreCase If false, upper case comes before
0972: * lower case.
0973: */
0974: public DirectoryEntryCompare(boolean sortMixFilesAndDirs,
0975: boolean sortIgnoreCase) {
0976: this .sortMixFilesAndDirs = sortMixFilesAndDirs;
0977: this .sortIgnoreCase = sortIgnoreCase;
0978: }
0979:
0980: public int compare(Object obj1, Object obj2) {
0981: VFSFile file1 = (VFSFile) obj1;
0982: VFSFile file2 = (VFSFile) obj2;
0983:
0984: if (!sortMixFilesAndDirs) {
0985: if (file1.getType() != file2.getType())
0986: return file2.getType() - file1.getType();
0987: }
0988:
0989: return StandardUtilities.compareStrings(file1.getName(),
0990: file2.getName(), sortIgnoreCase);
0991: }
0992: } //}}}
0993:
0994: //{{{ Private members
0995: private String name;
0996: private int caps;
0997: private String[] extAttrs;
0998: private static List<ColorEntry> colors;
0999: private static final Object lock = new Object();
1000:
1001: //{{{ Class initializer
1002: static {
1003: EditBus.addToBus(new EBComponent() {
1004: public void handleMessage(EBMessage msg) {
1005: if (msg instanceof PropertiesChanged) {
1006: synchronized (lock) {
1007: colors = null;
1008: }
1009: }
1010: }
1011: });
1012: } //}}}
1013:
1014: private long val;
1015:
1016: //{{{ recursive listFiles() method
1017: private void listFiles(Object session, Collection<String> stack,
1018: List<String> files, String directory, VFSFileFilter filter,
1019: boolean recursive, Component comp, boolean skipBinary,
1020: boolean skipHidden) throws IOException {
1021: if (stack.contains(directory)) {
1022: Log.log(Log.ERROR, this , "Recursion in _listDirectory(): "
1023: + directory);
1024: return;
1025: }
1026:
1027: stack.add(directory);
1028:
1029: Thread ct = Thread.currentThread();
1030: WorkThread wt = null;
1031: if (ct instanceof WorkThread) {
1032: wt = (WorkThread) ct;
1033: }
1034:
1035: VFSFile[] _files = _listFiles(session, directory, comp);
1036: if (_files == null || _files.length == 0)
1037: return;
1038:
1039: for (int i = 0; i < _files.length; i++) {
1040: if (wt != null && wt.isAborted())
1041: break;
1042: VFSFile file = _files[i];
1043: if (skipHidden
1044: && (file.isHidden() || MiscUtilities.isBackup(file
1045: .getName())))
1046: continue;
1047: if (!filter.accept(file))
1048: continue;
1049: if (file.getType() == VFSFile.DIRECTORY
1050: || file.getType() == VFSFile.FILESYSTEM) {
1051: if (recursive) {
1052: // resolve symlinks to avoid loops
1053: String canonPath = _canonPath(session, file
1054: .getPath(), comp);
1055: if (!MiscUtilities.isURL(canonPath))
1056: canonPath = MiscUtilities
1057: .resolveSymlinks(canonPath);
1058:
1059: listFiles(session, stack, files, canonPath, filter,
1060: recursive, comp, skipBinary, skipHidden);
1061: }
1062:
1063: } else // It's a regular file
1064: {
1065: if (skipBinary) {
1066: try {
1067: if (file.isBinary(session)) {
1068: Log.log(Log.NOTICE, this , file.getPath()
1069: + ": skipped as a binary file");
1070: continue;
1071: }
1072: } catch (IOException e) {
1073: Log.log(Log.ERROR, this , e);
1074: // may be not binary...
1075: }
1076: }
1077:
1078: Log.log(Log.DEBUG, this , file.getPath());
1079: files.add(file.getPath());
1080: }
1081: }
1082: } //}}}
1083:
1084: //{{{ loadColors() method
1085: private static void loadColors() {
1086: synchronized (lock) {
1087: colors = new ArrayList<ColorEntry>();
1088:
1089: if (!jEdit.getBooleanProperty("vfs.browser.colorize"))
1090: return;
1091:
1092: String glob;
1093: int i = 0;
1094: while ((glob = jEdit.getProperty("vfs.browser.colors." + i
1095: + ".glob")) != null) {
1096: try {
1097: colors.add(new ColorEntry(Pattern
1098: .compile(StandardUtilities.globToRE(glob)),
1099: jEdit.getColorProperty(
1100: "vfs.browser.colors." + i
1101: + ".color", Color.black)));
1102: } catch (PatternSyntaxException e) {
1103: Log.log(Log.ERROR, VFS.class,
1104: "Invalid regular expression: " + glob);
1105: Log.log(Log.ERROR, VFS.class, e);
1106: }
1107:
1108: i++;
1109: }
1110: }
1111: } //}}}
1112:
1113: //{{{ ColorEntry class
1114: static class ColorEntry {
1115: Pattern re;
1116: Color color;
1117:
1118: ColorEntry(Pattern re, Color color) {
1119: this .re = re;
1120: this .color = color;
1121: }
1122: } //}}}
1123:
1124: //}}}
1125: }
|