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    
020    package com.mucommander.ui.main.tree;
021    
022    import java.lang.reflect.InvocationTargetException;
023    import java.util.Arrays;
024    
025    import javax.swing.Icon;
026    import javax.swing.ImageIcon;
027    import javax.swing.SwingUtilities;
028    
029    import com.mucommander.file.AbstractFile;
030    import com.mucommander.file.impl.ProxyFile;
031    import com.mucommander.ui.icon.CustomFileIconProvider;
032    import com.mucommander.ui.icon.FileIcons;
033    import com.mucommander.ui.icon.IconManager;
034    
035    /**
036     * A class that holds cached children of a directory.
037     * 
038     * @author Mariusz Jakubowski
039     * 
040     */
041    public class CachedDirectory extends ProxyFile {
042        
043        private static final ImageIcon NOT_ACCESSIBLE_ICON = IconManager.getIcon(IconManager.FILE_ICON_SET, CustomFileIconProvider.NOT_ACCESSIBLE_FILE);
044    
045        /** an array of cached children */
046        private AbstractFile[] cachedChildren = null;
047        
048        /** a flag indicating that a thread is running, caching children */
049        private boolean readingChildren = false;
050        
051        /** a timestamp of last modification time of this directory */
052        private long lsTimeStamp = -1;
053        
054        /** a cache in which this object is stored */
055        private DirectoryCache cache;
056    
057        /** a cached icon */
058        private Icon cachedIcon;
059        
060    
061        /**
062         * Creates a new instance.
063         * 
064         * @param directory a directory to cache
065         */
066        public CachedDirectory(AbstractFile directory, DirectoryCache cache) {
067            super(directory);
068            this.cache = cache;
069        }
070    
071        /**
072         * Checks if this directory is already cached. If it isn't cached then a new
073         * cache thread is started.
074         * @return true if directory is cached, false otherwise
075         */
076        public synchronized boolean isCached() {
077            // check if caching thread is running
078            if (isReadingChildren()) {
079                return false;
080            }
081            // check if directory contents changed
082            if (lsTimeStamp != file.getDate()) {
083                setReadingChildren(true);
084                // read children in caching thread
085                TreeIOThreadManager.getInstance().addTask(new Runnable() {
086                    public void run() {
087                        lsAsync();
088                    }
089                });
090                return false;
091            }
092            return true;
093        }
094    
095        /**
096         * Gets children of current directory. Files are filtered and then sorted. This
097         * method is executed in caching thread.
098         */
099        private void lsAsync() {
100            if (getCachedIcon() == null || getCachedIcon() == NOT_ACCESSIBLE_ICON) {
101                setCachedIcon(FileIcons.getFileIcon(getProxiedFile()));
102            }
103    
104            AbstractFile[] children = null;
105            try {
106                children = file.ls(cache.getFilter());
107            } catch (Exception e) {
108                e.printStackTrace();
109                children = new AbstractFile[0];
110                setCachedIcon(NOT_ACCESSIBLE_ICON);
111            }
112    
113            Arrays.sort(children, cache.getSort());
114            Icon icons[] = new Icon[children.length];
115            for (int i = 0; i < children.length; i++) {
116                icons[i] = FileIcons.getFileIcon(children[i]);
117            }
118            synchronized (cache) {
119                for (int i = 0; i < children.length; i++) {
120                    CachedDirectory cachedChild = cache.getOrAdd(children[i]);
121                    cachedChild.setCachedIcon(icons[i]);
122                }
123            }
124            
125            final AbstractFile[] children2 = children;
126            try {
127                /*
128                 * Set cache to new value. This is invoked in swing thread
129                 * so event listeners are called from right thread. 
130                 */
131                SwingUtilities.invokeAndWait(new Runnable() {
132                    public void run() {
133                        setLsCache(children2, file.getDate());
134                    }
135                });
136            } catch (InterruptedException e) {
137                e.printStackTrace();
138            } catch (InvocationTargetException e) {
139                e.printStackTrace();
140            }
141        }
142    
143        /**
144         * Sets cache information.
145         * @param children array of children of this directory
146         * @param lsTimeStamp timestamp of cache
147         */
148        private synchronized void setLsCache(AbstractFile[] children, long lsTimeStamp) {
149            this.lsTimeStamp = lsTimeStamp;
150            this.cachedChildren = children;
151            setReadingChildren(false);
152        }
153    
154        /**
155         * Returns true if caching thread is running.
156         */
157        public synchronized boolean isReadingChildren() {
158            return readingChildren;
159        }
160    
161        /**
162         * Sets a flag that indicates if caching thread is running. This method also
163         * initializes spinning icon.
164         * @param readingChildren
165         */
166        private synchronized void setReadingChildren(boolean readingChildren) {
167            this.readingChildren = readingChildren;
168            cache.fireChildrenCached(this, readingChildren);
169        }
170    
171        /**
172         * Gets cached children.
173         * @return cached children.
174         */
175        public synchronized AbstractFile[] get() {
176            return cachedChildren;
177        }
178        
179        /**
180         * Gets a cached icon for this folder. 
181         * @return a cached icon
182         */
183        public Icon getCachedIcon() {
184            return cachedIcon;
185        }
186        
187        /**
188         * Sets a cached icon for this folder.
189         * @param cachedIcon a cached icon
190         */
191        public void setCachedIcon(Icon cachedIcon) {
192            this.cachedIcon = cachedIcon;
193        }
194    
195    }