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