001 /*
002 * This file is part of muCommander, http://www.mucommander.com
003 * Copyright (C) 2002-2008 Maxence Bernard
004 *
005 * muCommander is free software; you can redistribute it and/or modify
006 * it under the terms of the GNU General Public License as published by
007 * the Free Software Foundation; either version 3 of the License, or
008 * (at your option) any later version.
009 *
010 * muCommander is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License
016 * along with this program. If not, see <http://www.gnu.org/licenses/>.
017 */
018
019 package com.mucommander.bookmark.file;
020
021 import com.mucommander.bookmark.Bookmark;
022 import com.mucommander.bookmark.BookmarkBuilder;
023 import com.mucommander.bookmark.BookmarkManager;
024 import com.mucommander.file.*;
025 import com.mucommander.io.FileTransferException;
026 import com.mucommander.io.RandomAccessInputStream;
027 import com.mucommander.io.RandomAccessOutputStream;
028 import com.mucommander.process.AbstractProcess;
029
030 import java.io.*;
031
032 /**
033 * Represents a file in the <code>bookmark://</code> file system.
034 * @author Nicolas Rinaudo
035 */
036 public class BookmarkFile extends AbstractFile {
037 // - Instance fields -------------------------------------------------------
038 // -------------------------------------------------------------------------
039 /** Bookmark wrapped by this abstract file. */
040 private Bookmark bookmark;
041 /** Underlying abstract file. */
042 private AbstractFile file;
043
044 /** Permissions for all bookmark files: rw- (600 octal). Only the 'user' permissions bits are supported. */
045 final static FilePermissions PERMISSIONS = new SimpleFilePermissions(384, 448);
046
047
048 // - Initialisation --------------------------------------------------------
049 // -------------------------------------------------------------------------
050 /**
051 * Creates a new bookmark file wrapping the specified bookmark.
052 * @param bookmark bookmark to wrap.
053 * @throws IOException if the specified bookmark's URL cannot be resolved.
054 */
055 public BookmarkFile(Bookmark bookmark) throws IOException {
056 super(FileURL.getFileURL(FileProtocols.BOOKMARKS + "://" + java.net.URLEncoder.encode(bookmark.getName(), "UTF-8")));
057 this.bookmark = bookmark;
058 }
059
060
061
062 // - Helper methods --------------------------------------------------------
063 // -------------------------------------------------------------------------
064 /**
065 * Returns the <code>AbstractFile</code> this instance wraps.
066 * <p>
067 * Some methods need to have access to the underlying file. This, however, requires
068 * resolving the path which can be time consuming. Using this method ensures that the
069 * path is only resolved if necessary, and at most once.
070 * </p>
071 * @return the <code>AbstractFile</code> this instance wraps.
072 */
073 private synchronized AbstractFile getUnderlyingFile() {
074 // Resolves the file if necessary.
075 if(file == null)
076 file = FileFactory.getFile(bookmark.getLocation());
077
078 return file;
079 }
080
081 /**
082 * Returns the underlying bookmark.
083 * @return the underlying bookmark.
084 */
085 public Bookmark getBookmark() {return bookmark;}
086
087
088
089 // - AbstractFile methods --------------------------------------------------
090 // -------------------------------------------------------------------------
091 /**
092 * Returns the underlying bookmark's name.
093 * @return the underlying bookmark's name.
094 */
095 public String getName() {return bookmark.getName();}
096
097 /**
098 * Returns the wrapped file's descendants.
099 * @return the wrapped file's descendants.
100 * @throws IOException if an I/O error occurs.
101 */
102 public AbstractFile[] ls() throws IOException {return getUnderlyingFile().ls();}
103
104 /**
105 * Returns the wrapped file's parent.
106 * @return the wrapped file's parent.
107 * @throws IOException if an IO error occurs.
108 * @see #setParent(AbstractFile)
109 */
110 public AbstractFile getParent() throws IOException {
111 return new BookmarkRoot();
112 }
113
114 /**
115 * Returns <code>true</code> if the wrapped file knows how to create processes.
116 * @return <code>true</code> if the wrapped file knows how to create processes.
117 */
118 public boolean canRunProcess() {return getUnderlyingFile().canRunProcess();}
119
120 /**
121 * Runs the specified command on the wrapped file.
122 * @param tokens command to run.
123 * @return a process running the specified command.
124 * @throws IOException if an IO error occurs.
125 */
126 public AbstractProcess runProcess(String[] tokens) throws IOException {return getUnderlyingFile().runProcess(tokens);}
127
128 /**
129 * Returns the result of the wrapped file's <code>getFreeSpace()</code> methods.
130 * @return the result of the wrapped file's <code>getFreeSpace()</code> methods.
131 */
132 public long getFreeSpace() {return getUnderlyingFile().getFreeSpace();}
133
134 /**
135 * Returns the result of the wrapped file's <code>getTotalSpace()</code> methods.
136 * @return the result of the wrapped file's <code>getTotalSpace()</code> methods.
137 */
138 public long getTotalSpace() {return getUnderlyingFile().getTotalSpace();}
139
140 /**
141 * Returns <code>false</code>.
142 * @return <code>false</code>.
143 */
144 public boolean isDirectory() {return false;}
145
146 /**
147 * Returns <code>true</code>.
148 * @return <code>true</code>.
149 */
150 public boolean isBrowsable() {return true;}
151
152 /**
153 * Sets the wrapped file's parent.
154 * @param parent object to use as the wrapped file's parent.
155 * @see #getParent()
156 */
157 public void setParent(AbstractFile parent) {
158 getUnderlyingFile().setParent(parent);}
159
160 /**
161 * Returns <code>true</code> if the specified bookmark exists.
162 * <p>
163 * A bookmark is said to exist if and only if it is known to the {@link com.mucommander.bookmark.BookmarkManager}.
164 * </p>
165 * @return <code>true</code> if the specified bookmark exists, <code>false</code> otherwise.
166 */
167 public boolean exists() {return BookmarkManager.getBookmark(bookmark.getName()) != null;}
168
169 public void mkfile() {BookmarkManager.addBookmark(bookmark);}
170
171 public boolean equals(Object o) {
172 // Makes sure we're working with an abstract file.
173 if(!(o instanceof AbstractFile))
174 return false;
175
176 // Retrieves the actual file instance.
177 // We might have received a Proxied or Cached file, so we need to make sure
178 // we 'unwrap' that before comparing.
179 AbstractFile file = ((AbstractFile)o).getAncestor();
180
181 // We only know how to compare one bookmark file to the other.
182 if(file instanceof BookmarkFile)
183 return bookmark.equals(((BookmarkFile)file).getBookmark());
184 return false;
185 }
186
187 public String getCanonicalPath() {return bookmark.getLocation();}
188
189
190
191 // - Bookmark renaming -----------------------------------------------------
192 // -------------------------------------------------------------------------
193 /**
194 * Returns {@link AbstractFile#MUST_HINT}.
195 * <p>
196 * If the specified file is a <code>BookmarkFile</code>, then we must use the custom
197 * {@link #moveTo(AbstractFile) moveTo} method. Otherwise, the point is moot as any
198 * other move operation will fail.
199 * </p>
200 * @param destination where the file will be moved to.
201 * @return {@link AbstractFile#MUST_HINT}.
202 */
203 public int getMoveToHint(AbstractFile destination) {
204 if(destination.getAncestor() instanceof BookmarkFile)
205 return MUST_HINT;
206 return MUST_NOT_HINT;
207 }
208
209 /**
210 * Tries to move the bookmark to the specified destination.
211 * <p>
212 * If the specified destination is an instance of <code>BookmarkFile</code>,
213 * this will rename the bookmark. Otherwise, this method will fail.
214 * </p>
215 * @param destination where to move the bookmark to.
216 * @return <code>true</code>.
217 * @throws FileTransferException if the specified destination is not an instance of <code>BookmarkFile</code>.
218 */
219 public boolean moveTo(AbstractFile destination) throws FileTransferException {
220 Bookmark oldBookmark;
221 Bookmark newBookmark;
222
223 destination = destination.getAncestor();
224
225 // Makes sure we're working with a bookmark.
226 if(!(destination instanceof BookmarkFile))
227 throw new FileTransferException(FileTransferException.OPENING_DESTINATION);
228
229 // Creates the new bookmark and checks for conflicts.
230 newBookmark = new Bookmark(destination.getName(), bookmark.getLocation());
231 if((oldBookmark = BookmarkManager.getBookmark(newBookmark.getName())) != null)
232 BookmarkManager.removeBookmark(oldBookmark);
233
234 // Adds the new bookmark and deletes its 'old' version.
235 BookmarkManager.addBookmark(newBookmark);
236 BookmarkManager.removeBookmark(bookmark);
237
238 return true;
239 }
240
241 /**
242 * Deletes the bookmark.
243 * <p>
244 * Deleting a bookmark means unregistering it from the {@link com.mucommander.bookmark.BookmarkManager}.
245 * </p>
246 */
247 public void delete() {BookmarkManager.removeBookmark(bookmark);}
248
249
250
251 // - Bookmark duplication --------------------------------------------------
252 // -------------------------------------------------------------------------
253 public int getCopyToHint(AbstractFile destination) {
254 destination = destination.getAncestor();
255 if(destination instanceof BookmarkFile)
256 return MUST_HINT;
257 return MUST_NOT_HINT;
258 }
259
260 /**
261 * Tries to copy the bookmark to the specified destination.
262 * <p>
263 * If the specified destination is an instance of <code>BookmarkFile</code>,
264 * this will duplicate the bookmark. Otherwise, this method will fail.
265 * </p>
266 * @param destination where to copy the bookmark to.
267 * @return <code>true</code>.
268 * @throws FileTransferException if the specified destination is not an instance of <code>BookmarkFile</code>.
269 */
270 public boolean copyTo(AbstractFile destination) throws FileTransferException {
271 // Makes sure we're working with a bookmark.
272 destination = destination.getAncestor();
273 if(!(destination instanceof BookmarkFile))
274 throw new FileTransferException(FileTransferException.OPENING_DESTINATION);
275
276 // Copies this bookmark to the specified destination.
277 BookmarkManager.addBookmark(new Bookmark(destination.getName(), bookmark.getLocation()));
278
279 return true;
280 }
281
282
283
284 // - Permissions -----------------------------------------------------------
285 // -------------------------------------------------------------------------
286 /**
287 * Returns the same permissions for all boookmark files: rw- (600 octal).
288 * Only the 'user' permissions bits are supported.
289
290 * @return this file's permissions.
291 * @see #changePermission(int,int,boolean)
292 */
293 public FilePermissions getPermissions() {return PERMISSIONS;}
294
295 /**
296 * Returns <code>false</code>.
297 * <p>
298 * Bookmarks always have all permissions, this is not changeable. Calls
299 * to this method will always be ignored.
300 * </p>
301 * @param access ignored.
302 * @param permission ignored.
303 * @param enabled ignored.
304 * @return <code>false</code>.
305 * @see #getPermissions()
306 */
307 public boolean changePermission(int access, int permission, boolean enabled) {return false;}
308
309
310 // - Import / export -------------------------------------------------------
311 // -------------------------------------------------------------------------
312 public InputStream getInputStream() throws IOException {
313 BookmarkBuilder builder;
314 ByteArrayOutputStream stream;
315
316 builder = BookmarkManager.getBookmarkWriter(stream = new ByteArrayOutputStream());
317 try {
318 builder.startBookmarks();
319 builder.addBookmark(bookmark.getName(), bookmark.getLocation());
320 builder.endBookmarks();
321 }
322 // If an exception occured, we have to look for its root cause.
323 catch(Throwable e) {
324 Throwable e2;
325
326 // Looks for the cause.
327 while((e2 = e.getCause()) != null)
328 e = e2;
329
330 // If the cause is an IOException, thow it.
331 if(e instanceof IOException)
332 throw (IOException)e;
333
334 // Otherwise, throw the exception as an IOException with a the underlying cause's message.
335 throw new IOException(e.getMessage());
336 }
337
338 return new ByteArrayInputStream(stream.toByteArray());
339 }
340
341 public OutputStream getOutputStream(boolean append) throws IOException {return new BookmarkOutputStream();}
342
343
344
345 // - Unused methods --------------------------------------------------------
346 // -------------------------------------------------------------------------
347 // The following methods are not used by BookmarkFile. They will throw an exception or
348 // return an 'operation non supported' / default value.
349
350 public void mkdir() throws IOException {throw new IOException();}
351 public long getDate() {return 0;}
352 public boolean canChangeDate() {return false;}
353 public PermissionBits getChangeablePermissions() {return PermissionBits.EMPTY_PERMISSION_BITS;}
354 public boolean changeDate(long lastModified) {return false;}
355 public long getSize() {return -1;}
356 public boolean hasRandomAccessInputStream() {return false;}
357 public RandomAccessInputStream getRandomAccessInputStream() throws IOException {throw new IOException();}
358 public boolean hasRandomAccessOutputStream() {return false;}
359 public RandomAccessOutputStream getRandomAccessOutputStream() throws IOException {throw new IOException();}
360 public Object getUnderlyingFileObject() {return null;}
361 public boolean isSymlink() {return false;}
362 public String getOwner() {return null;}
363 public boolean canGetOwner() {return false;}
364 public String getGroup() {return null;}
365 public boolean canGetGroup() {return false;}
366 }