/jEdit/branches/notifications/org/gjt/sp/jedit/io/FileVFS.java

# · Java · 686 lines · 448 code · 85 blank · 153 comment · 64 complexity · c7bc3c4164517058c1aee087a5c87f44 MD5 · raw file

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