001: /*
002: * FileVFS.java - Local filesystem VFS
003: * :tabSize=8:indentSize=8:noTabs=false:
004: * :folding=explicit:collapseFolds=1:
005: *
006: * Copyright (C) 1998, 2005 Slava Pestov
007: * Portions copyright (C) 1998, 1999, 2000 Peter Graves
008: * Portions copyright (C) 2007 Matthieu Casanova
009: *
010: * This program is free software; you can redistribute it and/or
011: * modify it under the terms of the GNU General Public License
012: * as published by the Free Software Foundation; either version 2
013: * of the License, or any later version.
014: *
015: * This program is distributed in the hope that it will be useful,
016: * but WITHOUT ANY WARRANTY; without even the implied warranty of
017: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
018: * GNU General Public License for more details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with this program; if not, write to the Free Software
022: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
023: */
024:
025: package org.gjt.sp.jedit.io;
026:
027: //{{{ Imports
028: import javax.swing.filechooser.FileSystemView;
029: import javax.swing.*;
030: import java.awt.Component;
031: import java.io.*;
032: import java.text.*;
033: import java.util.Date;
034: import org.gjt.sp.jedit.*;
035: import org.gjt.sp.util.Log;
036:
037: //}}}
038:
039: /**
040: * Local filesystem VFS.
041: * @author Slava Pestov
042: * @version $Id: FileVFS.java 10789 2007-10-03 01:18:48Z Vampire0 $
043: */
044: public class FileVFS extends VFS {
045: public static final String PERMISSIONS_PROPERTY = "FileVFS__perms";
046:
047: //{{{ FileVFS constructor
048: public FileVFS() {
049: super (
050: "file",
051: READ_CAP
052: | WRITE_CAP
053: | BROWSE_CAP
054: | DELETE_CAP
055: | RENAME_CAP
056: | MKDIR_CAP
057: | LOW_LATENCY_CAP
058: | ((OperatingSystem.isCaseInsensitiveFS()) ? CASE_INSENSITIVE_CAP
059: : 0), new String[] { EA_TYPE, EA_SIZE,
060: EA_STATUS, EA_MODIFIED });
061: } //}}}
062:
063: //{{{ getParentOfPath() method
064: public String getParentOfPath(String path) {
065: if (OperatingSystem.isDOSDerived()) {
066: if (path.length() == 2 && path.charAt(1) == ':')
067: return FileRootsVFS.PROTOCOL + ':';
068: else if (path.length() == 3 && path.endsWith(":\\"))
069: return FileRootsVFS.PROTOCOL + ':';
070: else if (path.startsWith("\\\\")
071: && path.indexOf('\\', 2) == -1)
072: return path;
073: }
074:
075: return super .getParentOfPath(path);
076: } //}}}
077:
078: //{{{ constructPath() method
079: public String constructPath(String parent, String path) {
080: if (parent.endsWith(File.separator) || parent.endsWith("/"))
081: return parent + path;
082: else
083: return parent + File.separator + path;
084: } //}}}
085:
086: //{{{ getFileSeparator() method
087: public char getFileSeparator() {
088: return File.separatorChar;
089: } //}}}
090:
091: //{{{ getTwoStageSaveName() method
092: /**
093: * Returns a temporary file name based on the given path.
094: *
095: * <p>If the directory where the file would be created cannot be
096: * written (i.e., no new files can be created in that directory),
097: * this method returns <code>null</code>.</p>
098: *
099: * @param path The path name
100: */
101: public String getTwoStageSaveName(String path) {
102: File parent = new File(getParentOfPath(path));
103: // the ignorance of the canWrite() method for windows
104: // is, because the read-only flag on windows has
105: // not the effect of preventing the creation of new files.
106: // The only way to make a directory read-only in this means
107: // the ACL of the directory has to be set to read-only,
108: // which is not checkable by java.
109: // The " || OperatingSystem.isWindows()" can be removed
110: // if the canWrite() method gives back the right value.
111: return (parent.canWrite() || OperatingSystem.isWindows()) ? super
112: .getTwoStageSaveName(path)
113: : null;
114: } //}}}
115:
116: //{{{ save() method
117: public boolean save(View view, Buffer buffer, String path) {
118: if (OperatingSystem.isUnix()) {
119: int permissions = getPermissions(buffer.getPath());
120: Log.log(Log.DEBUG, this , buffer.getPath()
121: + " has permissions 0"
122: + Integer.toString(permissions, 8));
123: buffer
124: .setIntegerProperty(PERMISSIONS_PROPERTY,
125: permissions);
126: }
127:
128: return super .save(view, buffer, path);
129: } //}}}
130:
131: //{{{ insert() method
132: public boolean insert(View view, Buffer buffer, String path) {
133: File file = new File(path);
134:
135: //{{{ Check if file is valid
136: if (!file.exists())
137: return false;
138:
139: if (file.isDirectory()) {
140: VFSManager.error(view, file.getPath(),
141: "ioerror.open-directory", null);
142: return false;
143: }
144:
145: if (!file.canRead()) {
146: VFSManager.error(view, file.getPath(), "ioerror.no-read",
147: null);
148: return false;
149: } //}}}
150:
151: return super .insert(view, buffer, path);
152: } //}}}
153:
154: //{{{ recursiveDelete() method
155: /**
156: * #
157: * @param path the directory path to recursive delete
158: * @return true if successful, else false
159: */
160: public static boolean recursiveDelete(File path) {
161: if (path.exists()) {
162: File[] files = path.listFiles();
163: for (int i = 0; i < files.length; i++) {
164: if (files[i].isDirectory()) {
165: recursiveDelete(files[i]);
166: } else {
167: files[i].delete();
168: }
169: }
170: }
171: return path.delete();
172: } //}}}
173:
174: //{{{ _canonPath() method
175: /**
176: * Returns the canonical form if the specified path name. For example,
177: * <code>~</code> might be expanded to the user's home directory.
178: * @param session The session
179: * @param path The path
180: * @param comp The component that will parent error dialog boxes
181: * @exception IOException if an I/O error occurred
182: * @since jEdit 4.0pre2
183: */
184: public String _canonPath(Object session, String path, Component comp)
185: throws IOException {
186: return MiscUtilities.canonPath(path);
187: } //}}}
188:
189: //{{{ LocalFile class
190: public static class LocalFile extends VFSFile {
191: private File file;
192:
193: // use system default short format
194: public static DateFormat DATE_FORMAT = DateFormat.getInstance();
195:
196: /**
197: * @deprecated Call getModified() instead.
198: */
199: public long modified;
200:
201: //{{{ LocalFile() class
202: public LocalFile(File file) {
203: this .file = file;
204:
205: /* These attributes are fetched relatively
206: quickly. The rest are lazily filled in. */
207: setName(file.getName());
208: String path = file.getPath();
209: setPath(path);
210: setDeletePath(path);
211: setHidden(file.isHidden());
212: setType(file.isDirectory() ? VFSFile.DIRECTORY
213: : VFSFile.FILE);
214: } //}}}
215:
216: //{{{ getExtendedAttribute() method
217: public String getExtendedAttribute(String name) {
218: fetchAttrs();
219: if (name.equals(EA_MODIFIED)) {
220: return DATE_FORMAT.format(new Date(modified));
221: } else {
222: return super .getExtendedAttribute(name);
223: }
224: } //}}}
225:
226: //{{{ fetchAttrs() method
227: /** Fetch the attributes of the local file. */
228: protected void fetchAttrs() {
229: if (fetchedAttrs())
230: return;
231:
232: super .fetchAttrs();
233:
234: setSymlinkPath(MiscUtilities
235: .resolveSymlinks(file.getPath()));
236: setReadable(file.canRead());
237: setWriteable(file.canWrite());
238: setLength(file.length());
239: setModified(file.lastModified());
240: } //}}}
241:
242: //{{{ getIcon() method
243: /**
244: * Returns the file system icon for the file.
245: *
246: * @param expanded not used here
247: * @param openBuffer not used here
248: * @return the file system icon
249: * @since 4.3pre9
250: */
251: public Icon getIcon(boolean expanded, boolean openBuffer) {
252: if (icon == null) {
253: if (fsView == null)
254: fsView = FileSystemView.getFileSystemView();
255:
256: icon = fsView.getSystemIcon(file);
257: }
258: return icon;
259: } //}}}
260:
261: //{{{ getSymlinkPath() method
262: public String getSymlinkPath() {
263: fetchAttrs();
264: return super .getSymlinkPath();
265: } //}}}
266:
267: //{{{ getLength() method
268: public long getLength() {
269: fetchAttrs();
270: return super .getLength();
271: } //}}}
272:
273: //{{{ isReadable() method
274: public boolean isReadable() {
275: fetchAttrs();
276: return super .isReadable();
277: } //}}}
278:
279: //{{{ isWriteable() method
280: public boolean isWriteable() {
281: fetchAttrs();
282: return super .isWriteable();
283: } //}}}
284:
285: //{{{ getModified() method
286: public long getModified() {
287: fetchAttrs();
288: return modified;
289: } //}}}
290:
291: //{{{ setModified() method
292: public void setModified(long modified) {
293: this .modified = modified;
294: } //}}}
295:
296: private transient FileSystemView fsView;
297: private transient Icon icon;
298: } //}}}
299:
300: //{{{ _listFiles() method
301: public VFSFile[] _listFiles(Object session, String path,
302: Component comp) {
303: //{{{ Windows work around
304: /* On Windows, paths of the form X: list the last *working
305: * directory* on that drive. To list the root of the drive,
306: * you must use X:\.
307: *
308: * However, the VFS browser and friends strip off trailing
309: * path separators, for various reasons. So to work around
310: * that, we add a '\' to drive letter paths on Windows.
311: */
312: if (OperatingSystem.isWindows()) {
313: if (path.length() == 2 && path.charAt(1) == ':')
314: path = path.concat(File.separator);
315: } //}}}
316:
317: File directory = new File(path);
318: File[] list = null;
319: if (directory.exists())
320: list = fsView.getFiles(directory, false);
321:
322: if (list == null) {
323: VFSManager.error(comp, path,
324: "ioerror.directory-error-nomsg", null);
325: return null;
326: }
327:
328: VFSFile[] list2 = new VFSFile[list.length];
329: for (int i = 0; i < list.length; i++)
330: list2[i] = new LocalFile(list[i]);
331:
332: return list2;
333: } //}}}
334:
335: //{{{ _getFile() method
336: public VFSFile _getFile(Object session, String path, Component comp) {
337: if (path.equals("/") && OperatingSystem.isUnix()) {
338: return new VFS.DirectoryEntry(path, path, path,
339: VFSFile.DIRECTORY, 0L, false);
340: }
341:
342: File file = new File(path);
343: if (!file.exists())
344: return null;
345:
346: return new LocalFile(file);
347: } //}}}
348:
349: //{{{ _delete() method
350: public boolean _delete(Object session, String path, Component comp) {
351: File file = new File(path);
352: // do some platforms throw exceptions if the file does not exist
353: // when we ask for the canonical path?
354: String canonPath;
355: try {
356: canonPath = file.getCanonicalPath();
357: } catch (IOException io) {
358: canonPath = path;
359: }
360: // if directory, do recursive delete
361: boolean retVal;
362: if (!file.isDirectory()) {
363: retVal = file.delete();
364: } else {
365: retVal = recursiveDelete(file);
366: }
367: if (retVal)
368: VFSManager.sendVFSUpdate(this , canonPath, true);
369: return retVal;
370: } //}}}
371:
372: //{{{ _rename() method
373: public boolean _rename(Object session, String from, String to,
374: Component comp) {
375: File _to = new File(to);
376:
377: String toCanonPath;
378: try {
379: toCanonPath = _to.getCanonicalPath();
380: } catch (IOException io) {
381: toCanonPath = to;
382: }
383:
384: // this is needed because on OS X renaming to a non-existent
385: // directory causes problems
386: File parent = new File(_to.getParent());
387: if (parent.exists()) {
388: if (!parent.isDirectory())
389: return false;
390: } else {
391: parent.mkdirs();
392: if (!parent.exists())
393: return false;
394: }
395:
396: File _from = new File(from);
397:
398: String fromCanonPath;
399: try {
400: fromCanonPath = _from.getCanonicalPath();
401: } catch (IOException io) {
402: fromCanonPath = from;
403: }
404:
405: // Case-insensitive fs workaround
406: if (!fromCanonPath.equalsIgnoreCase(toCanonPath))
407: _to.delete();
408:
409: boolean retVal = _from.renameTo(_to);
410: VFSManager.sendVFSUpdate(this , fromCanonPath, true);
411: VFSManager.sendVFSUpdate(this , toCanonPath, true);
412: return retVal;
413: } //}}}
414:
415: //{{{ _mkdir() method
416: public boolean _mkdir(Object session, String directory,
417: Component comp) {
418: String parent = getParentOfPath(directory);
419: if (!new File(parent).exists()) {
420: if (!_mkdir(session, parent, comp))
421: return false;
422: }
423:
424: File file = new File(directory);
425:
426: boolean retVal = file.mkdir();
427: String canonPath;
428: try {
429: canonPath = file.getCanonicalPath();
430: } catch (IOException io) {
431: canonPath = directory;
432: }
433: VFSManager.sendVFSUpdate(this , canonPath, true);
434: return retVal;
435: } //}}}
436:
437: //{{{ _backup() method
438: public void _backup(Object session, String path, Component comp)
439: throws IOException {
440: // Fetch properties
441: int backups = jEdit.getIntegerProperty("backups", 1);
442:
443: if (backups == 0)
444: return;
445:
446: String backupPrefix = jEdit.getProperty("backup.prefix");
447: String backupSuffix = jEdit.getProperty("backup.suffix");
448:
449: String backupDirectory = jEdit.getProperty("backup.directory");
450:
451: int backupTimeDistance = jEdit.getIntegerProperty(
452: "backup.minTime", 0);
453: File file = new File(path);
454:
455: if (!file.exists())
456: return;
457:
458: // Check for backup.directory, and create that
459: // directory if it doesn't exist
460: if (backupDirectory == null || backupDirectory.length() == 0)
461: backupDirectory = file.getParent();
462: else {
463: backupDirectory = MiscUtilities.constructPath(System
464: .getProperty("user.home"), backupDirectory);
465:
466: // Perhaps here we would want to guard with
467: // a property for parallel backups or not.
468: backupDirectory = MiscUtilities.concatPath(backupDirectory,
469: file.getParent());
470:
471: File dir = new File(backupDirectory);
472:
473: if (!dir.exists())
474: dir.mkdirs();
475: }
476:
477: MiscUtilities.saveBackup(file, backups, backupPrefix,
478: backupSuffix, backupDirectory, backupTimeDistance);
479: } //}}}
480:
481: //{{{ _createInputStream() method
482: public InputStream _createInputStream(Object session, String path,
483: boolean ignoreErrors, Component comp) throws IOException {
484: try {
485: return new FileInputStream(path);
486: } catch (IOException io) {
487: if (ignoreErrors)
488: return null;
489: else
490: throw io;
491: }
492: } //}}}
493:
494: //{{{ _createOutputStream() method
495: public OutputStream _createOutputStream(Object session,
496: String path, Component comp) throws IOException {
497: return new FileOutputStream(path);
498: } //}}}
499:
500: //{{{ _saveComplete() method
501: public void _saveComplete(Object session, Buffer buffer,
502: String path, Component comp) {
503: int permissions = buffer.getIntegerProperty(
504: PERMISSIONS_PROPERTY, 0);
505: setPermissions(path, permissions);
506: } //}}}
507:
508: //{{{ Permission preservation code
509:
510: /** Code borrowed from j text editor (http://www.armedbear.org) */
511: /** I made some changes to make it support suid, sgid and sticky files */
512:
513: //{{{ getPermissions() method
514: /**
515: * Returns numeric permissions of a file. On non-Unix systems, always
516: * returns zero.
517: * @since jEdit 3.2pre9
518: */
519: public static int getPermissions(String path) {
520: int permissions = 0;
521:
522: if (jEdit.getBooleanProperty("chmodDisabled"))
523: return permissions;
524:
525: if (OperatingSystem.isUnix()) {
526: String[] cmdarray = { "ls", "-ld", path };
527:
528: try {
529: Process process = Runtime.getRuntime().exec(cmdarray);
530:
531: BufferedReader reader = new BufferedReader(
532: new InputStreamReader(process.getInputStream()));
533:
534: String output = reader.readLine();
535:
536: if (output != null) {
537: String s = output.substring(1, 10);
538:
539: permissions = MiscUtilities.parsePermissions(s);
540: }
541: }
542:
543: // Feb 4 2000 5:30 PM
544: // Catch Throwable here rather than Exception.
545: // Kaffe's implementation of Runtime.exec throws java.lang.InternalError.
546: catch (Throwable t) {
547: }
548: }
549:
550: return permissions;
551: } //}}}
552:
553: //{{{ setPermissions() method
554: /**
555: * Sets numeric permissions of a file. On non-Unix platforms,
556: * does nothing.
557: * @since jEdit 3.2pre9
558: */
559: public static void setPermissions(String path, int permissions) {
560: if (jEdit.getBooleanProperty("chmodDisabled"))
561: return;
562:
563: if (permissions != 0) {
564: if (OperatingSystem.isUnix()) {
565: String[] cmdarray = { "chmod",
566: Integer.toString(permissions, 8), path };
567:
568: try {
569: Process process = Runtime.getRuntime().exec(
570: cmdarray);
571: process.getInputStream().close();
572: process.getOutputStream().close();
573: process.getErrorStream().close();
574: // Jun 9 2004 12:40 PM
575: // waitFor() hangs on some Java
576: // implementations.
577: /* int exitCode = process.waitFor();
578: if(exitCode != 0)
579: Log.log(Log.NOTICE,FileVFS.class,"chmod exited with code " + exitCode); */
580: }
581:
582: // Feb 4 2000 5:30 PM
583: // Catch Throwable here rather than Exception.
584: // Kaffe's implementation of Runtime.exec throws java.lang.InternalError.
585: catch (Throwable t) {
586: }
587: }
588: }
589: } //}}}
590:
591: //}}}
592:
593: //{{{ Private members
594: private static FileSystemView fsView = FileSystemView
595: .getFileSystemView();
596: //}}}
597: }
|