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    }