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    }