001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.plugins;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.io.File;
007    import java.io.FileInputStream;
008    import java.io.FilenameFilter;
009    import java.io.IOException;
010    import java.util.ArrayList;
011    import java.util.Collection;
012    import java.util.HashMap;
013    import java.util.List;
014    import java.util.Map;
015    
016    import org.openstreetmap.josm.gui.PleaseWaitRunnable;
017    import org.openstreetmap.josm.gui.progress.ProgressMonitor;
018    import org.openstreetmap.josm.io.OsmTransferException;
019    import org.openstreetmap.josm.tools.ImageProvider;
020    import org.openstreetmap.josm.tools.Utils;
021    import org.xml.sax.SAXException;
022    
023    /**
024     * This is an asynchronous task for reading plugin information from the files
025     * in the local plugin repositories.
026     *
027     * It scans the files in the local plugins repository (see {@link Preferences#getPluginsDirectory()}
028     * and extracts plugin information from three kind of files:
029     * <ul>
030     *   <li>.jar-files, assuming that they represent plugin jars</li>
031     *   <li>.jar.new-files, assuming that these are downloaded but not yet installed plugins</li>
032     *   <li>cached lists of available plugins, downloaded for instance from
033     *   <a href="http://josm.openstreetmap.de/plugins">http://josm.openstreetmap.de/plugins</a></li>
034     * </ul>
035     *
036     */
037    public class ReadLocalPluginInformationTask extends PleaseWaitRunnable {
038        private Map<String, PluginInformation> availablePlugins;
039        private boolean canceled;
040    
041        public ReadLocalPluginInformationTask() {
042            super(tr("Reading local plugin information.."), false);
043            availablePlugins = new HashMap<String, PluginInformation>();
044        }
045    
046        public ReadLocalPluginInformationTask(ProgressMonitor monitor) {
047            super(tr("Reading local plugin information.."),monitor, false);
048            availablePlugins = new HashMap<String, PluginInformation>();
049        }
050    
051        @Override
052        protected void cancel() {
053            canceled = true;
054        }
055    
056        @Override
057        protected void finish() {}
058    
059        protected void processJarFile(File f, String pluginName) throws PluginException{
060            PluginInformation info = new PluginInformation(
061                    f,
062                    pluginName
063            );
064            if (!availablePlugins.containsKey(info.getName())) {
065                info.localversion = info.version;
066                info.localmainversion = info.mainversion;
067                availablePlugins.put(info.getName(), info);
068            } else {
069                PluginInformation current = availablePlugins.get(info.getName());
070                current.localversion = info.version;
071                current.localmainversion = info.mainversion;
072                if (info.icon != null) {
073                    current.icon = info.icon;
074                }
075                current.early = info.early;
076                current.className = info.className;
077                current.libraries = info.libraries;
078                current.stage = info.stage;
079                current.requires = info.requires;
080            }
081        }
082    
083        protected void scanSiteCacheFiles(ProgressMonitor monitor, File pluginsDirectory) {
084            File[] siteCacheFiles = pluginsDirectory.listFiles(
085                    new FilenameFilter() {
086                        public boolean accept(File dir, String name) {
087                            return name.matches("^([0-9]+-)?site.*\\.txt$");
088                        }
089                    }
090            );
091            if (siteCacheFiles == null || siteCacheFiles.length == 0)
092                return;
093            monitor.subTask(tr("Processing plugin site cache files..."));
094            monitor.setTicksCount(siteCacheFiles.length);
095            for (File f: siteCacheFiles) {
096                String fname = f.getName();
097                monitor.setCustomText(tr("Processing file ''{0}''", fname));
098                try {
099                    processLocalPluginInformationFile(f);
100                } catch(PluginListParseException e) {
101                    System.err.println(tr("Warning: Failed to scan file ''{0}'' for plugin information. Skipping.", fname));
102                    e.printStackTrace();
103                }
104                monitor.worked(1);
105            }
106        }
107    
108        protected void scanIconCacheFiles(ProgressMonitor monitor, File pluginsDirectory) {
109            File[] siteCacheFiles = pluginsDirectory.listFiles(
110                    new FilenameFilter() {
111                        public boolean accept(File dir, String name) {
112                            return name.matches("^([0-9]+-)?site.*plugin-icons\\.zip$");
113                        }
114                    }
115            );
116            if (siteCacheFiles == null || siteCacheFiles.length == 0)
117                return;
118            monitor.subTask(tr("Processing plugin site cache icon files..."));
119            monitor.setTicksCount(siteCacheFiles.length);
120            for (File f: siteCacheFiles) {
121                String fname = f.getName();
122                monitor.setCustomText(tr("Processing file ''{0}''", fname));
123                for (PluginInformation pi : availablePlugins.values()) {
124                    if (pi.icon == null && pi.iconPath != null) {
125                        pi.icon = new ImageProvider(pi.name+".jar/"+pi.iconPath)
126                                        .setArchive(f)
127                                        .setMaxWidth(24)
128                                        .setMaxHeight(24)
129                                        .setOptional(true).get();
130                    }
131                }
132                monitor.worked(1);
133            }
134        }
135    
136        protected void scanPluginFiles(ProgressMonitor monitor, File pluginsDirectory) {
137            File[] pluginFiles = pluginsDirectory.listFiles(
138                    new FilenameFilter() {
139                        public boolean accept(File dir, String name) {
140                            return name.endsWith(".jar") || name.endsWith(".jar.new");
141                        }
142                    }
143            );
144            if (pluginFiles == null || pluginFiles.length == 0)
145                return;
146            monitor.subTask(tr("Processing plugin files..."));
147            monitor.setTicksCount(pluginFiles.length);
148            for (File f: pluginFiles) {
149                String fname = f.getName();
150                monitor.setCustomText(tr("Processing file ''{0}''", fname));
151                try {
152                    if (fname.endsWith(".jar")) {
153                        String pluginName = fname.substring(0, fname.length() - 4);
154                        processJarFile(f, pluginName);
155                    } else if (fname.endsWith(".jar.new")) {
156                        String pluginName = fname.substring(0, fname.length() - 8);
157                        processJarFile(f, pluginName);
158                    }
159                } catch(PluginException e){
160                    System.err.println(tr("Warning: Failed to scan file ''{0}'' for plugin information. Skipping.", fname));
161                    e.printStackTrace();
162                }
163                monitor.worked(1);
164            }
165        }
166    
167        protected void scanLocalPluginRepository(ProgressMonitor monitor, File pluginsDirectory) {
168            if (pluginsDirectory == null) return;
169            try {
170                monitor.beginTask("");
171                scanSiteCacheFiles(monitor, pluginsDirectory);
172                scanIconCacheFiles(monitor, pluginsDirectory);
173                scanPluginFiles(monitor, pluginsDirectory);
174            } finally {
175                monitor.setCustomText("");
176                monitor.finishTask();
177            }
178        }
179    
180        protected void processLocalPluginInformationFile(File file) throws PluginListParseException{
181            FileInputStream fin = null;
182            try {
183                fin = new FileInputStream(file);
184                List<PluginInformation> pis = new PluginListParser().parse(fin);
185                for (PluginInformation pi : pis) {
186                    // we always keep plugin information from a plugin site because it
187                    // includes information not available in the plugin jars Manifest, i.e.
188                    // the download link or localized descriptions
189                    //
190                    availablePlugins.put(pi.name, pi);
191                }
192            } catch(IOException e) {
193                throw new PluginListParseException(e);
194            } finally {
195                Utils.close(fin);
196            }
197        }
198    
199        protected void analyseInProcessPlugins() {
200            for (PluginProxy proxy : PluginHandler.pluginList) {
201                PluginInformation info = proxy.getPluginInformation();
202                if (canceled)return;
203                if (!availablePlugins.containsKey(info.name)) {
204                    availablePlugins.put(info.name, info);
205                } else {
206                    availablePlugins.get(info.name).localversion = info.localversion;
207                }
208            }
209        }
210    
211        protected void filterOldPlugins() {
212            for (PluginHandler.DeprecatedPlugin p : PluginHandler.DEPRECATED_PLUGINS) {
213                if (canceled)return;
214                if (availablePlugins.containsKey(p.name)) {
215                    availablePlugins.remove(p.name);
216                }
217            }
218        }
219    
220        @Override
221        protected void realRun() throws SAXException, IOException, OsmTransferException {
222            Collection<String> pluginLocations = PluginInformation.getPluginLocations();
223            getProgressMonitor().setTicksCount(pluginLocations.size() + 2);
224            if (canceled) return;
225            for (String location : pluginLocations) {
226                scanLocalPluginRepository(
227                        getProgressMonitor().createSubTaskMonitor(1, false),
228                        new File(location)
229                );
230                getProgressMonitor().worked(1);
231                if (canceled)return;
232            }
233            analyseInProcessPlugins();
234            getProgressMonitor().worked(1);
235            if (canceled)return;
236            filterOldPlugins();
237            getProgressMonitor().worked(1);
238        }
239    
240        /**
241         * Replies information about available plugins detected by this task.
242         *
243         * @return information about available plugins detected by this task.
244         */
245        public List<PluginInformation> getAvailablePlugins() {
246            return new ArrayList<PluginInformation>(availablePlugins.values());
247        }
248    
249        /**
250         * Replies true if the task was canceled by the user
251         *
252         * @return true if the task was canceled by the user
253         */
254        public boolean isCanceled() {
255            return canceled;
256        }
257    }