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