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