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.awt.Component; 007 import java.io.File; 008 import java.io.FileOutputStream; 009 import java.io.IOException; 010 import java.io.InputStream; 011 import java.io.OutputStream; 012 import java.net.HttpURLConnection; 013 import java.net.MalformedURLException; 014 import java.net.URL; 015 import java.util.Collection; 016 import java.util.LinkedList; 017 018 import org.openstreetmap.josm.Main; 019 import org.openstreetmap.josm.data.Version; 020 import org.openstreetmap.josm.gui.ExtendedDialog; 021 import org.openstreetmap.josm.gui.PleaseWaitRunnable; 022 import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 023 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 024 import org.openstreetmap.josm.tools.CheckParameterUtil; 025 import org.openstreetmap.josm.tools.Utils; 026 import org.xml.sax.SAXException; 027 028 029 /** 030 * Asynchronous task for downloading a collection of plugins. 031 * 032 * When the task is finished {@link #getDownloadedPlugins()} replies the list of downloaded plugins 033 * and {@link #getFailedPlugins()} replies the list of failed plugins. 034 * 035 */ 036 public class PluginDownloadTask extends PleaseWaitRunnable{ 037 private final Collection<PluginInformation> toUpdate = new LinkedList<PluginInformation>(); 038 private final Collection<PluginInformation> failed = new LinkedList<PluginInformation>(); 039 private final Collection<PluginInformation> downloaded = new LinkedList<PluginInformation>(); 040 private Exception lastException; 041 private boolean canceled; 042 private HttpURLConnection downloadConnection; 043 044 /** 045 * Creates the download task 046 * 047 * @param parent the parent component relative to which the {@link PleaseWaitDialog} is displayed 048 * @param toUpdate a collection of plugin descriptions for plugins to update/download. Must not be null. 049 * @param title the title to display in the {@link PleaseWaitDialog} 050 * @throws IllegalArgumentException thrown if toUpdate is null 051 */ 052 public PluginDownloadTask(Component parent, Collection<PluginInformation> toUpdate, String title) throws IllegalArgumentException{ 053 super(parent, title == null ? "" : title, false /* don't ignore exceptions */); 054 CheckParameterUtil.ensureParameterNotNull(toUpdate, "toUpdate"); 055 this.toUpdate.addAll(toUpdate); 056 } 057 058 /** 059 * Creates the task 060 * 061 * @param monitor a progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null 062 * @param toUpdate a collection of plugin descriptions for plugins to update/download. Must not be null. 063 * @param title the title to display in the {@link PleaseWaitDialog} 064 * @throws IllegalArgumentException thrown if toUpdate is null 065 */ 066 public PluginDownloadTask(ProgressMonitor monitor, Collection<PluginInformation> toUpdate, String title) { 067 super(title, monitor == null? NullProgressMonitor.INSTANCE: monitor, false /* don't ignore exceptions */); 068 CheckParameterUtil.ensureParameterNotNull(toUpdate, "toUpdate"); 069 this.toUpdate.addAll(toUpdate); 070 } 071 072 /** 073 * Sets the collection of plugins to update. 074 * 075 * @param toUpdate the collection of plugins to update. Must not be null. 076 * @throws IllegalArgumentException thrown if toUpdate is null 077 */ 078 public void setPluginsToDownload(Collection<PluginInformation> toUpdate) throws IllegalArgumentException{ 079 CheckParameterUtil.ensureParameterNotNull(toUpdate, "toUpdate"); 080 this.toUpdate.clear(); 081 this.toUpdate.addAll(toUpdate); 082 } 083 084 @Override protected void cancel() { 085 this.canceled = true; 086 synchronized(this) { 087 if (downloadConnection != null) { 088 downloadConnection.disconnect(); 089 } 090 } 091 } 092 093 @Override protected void finish() {} 094 095 protected void download(PluginInformation pi, File file) throws PluginDownloadException{ 096 if (pi.mainversion > Version.getInstance().getVersion()) { 097 ExtendedDialog dialog = new ExtendedDialog( 098 Main.parent, 099 tr("Skip download"), 100 new String[] { 101 tr("Download Plugin"), 102 tr("Skip Download") } 103 ); 104 dialog.setContent(tr("JOSM version {0} required for plugin {1}.", pi.mainversion, pi.name)); 105 dialog.setButtonIcons(new String[] { "download.png", "cancel.png" }); 106 dialog.showDialog(); 107 int answer = dialog.getValue(); 108 if (answer != 1) 109 throw new PluginDownloadException(tr("Download skipped")); 110 } 111 OutputStream out = null; 112 InputStream in = null; 113 try { 114 if (pi.downloadlink == null) { 115 String msg = tr("Warning: Cannot download plugin ''{0}''. Its download link is not known. Skipping download.", pi.name); 116 System.err.println(msg); 117 throw new PluginDownloadException(msg); 118 } 119 URL url = new URL(pi.downloadlink); 120 synchronized(this) { 121 downloadConnection = (HttpURLConnection)url.openConnection(); 122 downloadConnection.setRequestProperty("Cache-Control", "no-cache"); 123 downloadConnection.setRequestProperty("User-Agent",Version.getInstance().getAgentString()); 124 downloadConnection.setRequestProperty("Host", url.getHost()); 125 downloadConnection.connect(); 126 } 127 in = downloadConnection.getInputStream(); 128 out = new FileOutputStream(file); 129 byte[] buffer = new byte[8192]; 130 for (int read = in.read(buffer); read != -1; read = in.read(buffer)) { 131 out.write(buffer, 0, read); 132 } 133 out.close(); 134 in.close(); 135 } catch(MalformedURLException e) { 136 String msg = tr("Warning: Cannot download plugin ''{0}''. Its download link ''{1}'' is not a valid URL. Skipping download.", pi.name, pi.downloadlink); 137 System.err.println(msg); 138 throw new PluginDownloadException(msg); 139 } catch (IOException e) { 140 if (canceled) 141 return; 142 throw new PluginDownloadException(e); 143 } finally { 144 Utils.close(in); 145 synchronized(this) { 146 downloadConnection = null; 147 } 148 Utils.close(out); 149 } 150 } 151 152 @Override protected void realRun() throws SAXException, IOException { 153 File pluginDir = Main.pref.getPluginsDirectory(); 154 if (!pluginDir.exists()) { 155 if (!pluginDir.mkdirs()) { 156 lastException = new PluginDownloadException(tr("Failed to create plugin directory ''{0}''", pluginDir.toString())); 157 failed.addAll(toUpdate); 158 return; 159 } 160 } 161 getProgressMonitor().setTicksCount(toUpdate.size()); 162 for (PluginInformation d : toUpdate) { 163 if (canceled) return; 164 progressMonitor.subTask(tr("Downloading Plugin {0}...", d.name)); 165 progressMonitor.worked(1); 166 File pluginFile = new File(pluginDir, d.name + ".jar.new"); 167 try { 168 download(d, pluginFile); 169 } catch(PluginDownloadException e) { 170 e.printStackTrace(); 171 failed.add(d); 172 continue; 173 } 174 downloaded.add(d); 175 } 176 PluginHandler.installDownloadedPlugins(false); 177 } 178 179 /** 180 * Replies true if the task was canceled by the user 181 * 182 * @return 183 */ 184 public boolean isCanceled() { 185 return canceled; 186 } 187 188 /** 189 * Replies the list of successfully downloaded plugins 190 * 191 * @return the list of successfully downloaded plugins 192 */ 193 public Collection<PluginInformation> getFailedPlugins() { 194 return failed; 195 } 196 197 /** 198 * Replies the list of plugins whose download has failed 199 * 200 * @return the list of plugins whose download has failed 201 */ 202 public Collection<PluginInformation> getDownloadedPlugins() { 203 return downloaded; 204 } 205 }