001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.io; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.Component; 007import java.io.BufferedOutputStream; 008import java.io.File; 009import java.io.FileOutputStream; 010import java.io.IOException; 011import java.io.InputStream; 012import java.io.OutputStream; 013import java.net.HttpURLConnection; 014import java.net.MalformedURLException; 015import java.net.URL; 016import java.util.Enumeration; 017import java.util.zip.ZipEntry; 018import java.util.zip.ZipFile; 019 020import org.openstreetmap.josm.Main; 021import org.openstreetmap.josm.gui.PleaseWaitDialog; 022import org.openstreetmap.josm.gui.PleaseWaitRunnable; 023import org.openstreetmap.josm.tools.Utils; 024import org.xml.sax.SAXException; 025 026/** 027 * Asynchronous task for downloading and unpacking arbitrary file lists 028 * Shows progress bar when downloading 029 */ 030public class DownloadFileTask extends PleaseWaitRunnable{ 031 private final String address; 032 private final File file; 033 private final boolean mkdir; 034 private final boolean unpack; 035 036 /** 037 * Creates the download task 038 * 039 * @param parent the parent component relative to which the {@link PleaseWaitDialog} is displayed 040 * @param address the URL to download 041 * @param file The destination file 042 * @param mkdir {@code true} if the destination directory must be created, {@code false} otherwise 043 * @param unpack {@code true} if zip archives must be unpacked recursively, {@code false} otherwise 044 * @throws IllegalArgumentException if {@code parent} is null 045 */ 046 public DownloadFileTask(Component parent, String address, File file, boolean mkdir, boolean unpack) { 047 super(parent, tr("Downloading file"), false); 048 this.address = address; 049 this.file = file; 050 this.mkdir = mkdir; 051 this.unpack = unpack; 052 } 053 054 private static class DownloadException extends Exception { 055 public DownloadException(String msg) { 056 super(msg); 057 } 058 } 059 060 private boolean canceled; 061 private HttpURLConnection downloadConnection; 062 063 private synchronized void closeConnectionIfNeeded() { 064 if (downloadConnection != null) { 065 downloadConnection.disconnect(); 066 } 067 downloadConnection = null; 068 } 069 070 071 @Override 072 protected void cancel() { 073 this.canceled = true; 074 closeConnectionIfNeeded(); 075 } 076 077 @Override 078 protected void finish() {} 079 080 /** 081 * Performs download. 082 * @throws DownloadException if the URL is invalid or if any I/O error occurs. 083 */ 084 public void download() throws DownloadException { 085 OutputStream out = null; 086 InputStream in = null; 087 try { 088 if (mkdir) { 089 File newDir = file.getParentFile(); 090 if (!newDir.exists()) { 091 newDir.mkdirs(); 092 } 093 } 094 095 URL url = new URL(address); 096 int size; 097 synchronized(this) { 098 downloadConnection = Utils.openHttpConnection(url); 099 downloadConnection.setRequestProperty("Cache-Control", "no-cache"); 100 downloadConnection.connect(); 101 size = downloadConnection.getContentLength(); 102 } 103 104 progressMonitor.setTicksCount(100); 105 progressMonitor.subTask(tr("Downloading File {0}: {1} bytes...", file.getName(),size)); 106 107 in = downloadConnection.getInputStream(); 108 out = new FileOutputStream(file); 109 byte[] buffer = new byte[32768]; 110 int count=0; 111 int p1=0, p2=0; 112 for (int read = in.read(buffer); read != -1; read = in.read(buffer)) { 113 out.write(buffer, 0, read); 114 count+=read; 115 if (canceled) break; 116 p2 = 100 * count / size; 117 if (p2!=p1) { 118 progressMonitor.setTicks(p2); 119 p1=p2; 120 } 121 } 122 Utils.close(out); 123 if (!canceled) { 124 Main.info(tr("Download finished")); 125 if (unpack) { 126 Main.info(tr("Unpacking {0} into {1}", file.getAbsolutePath(), file.getParent())); 127 unzipFileRecursively(file, file.getParent()); 128 file.delete(); 129 } 130 } 131 } catch(MalformedURLException e) { 132 String msg = tr("Cannot download file ''{0}''. Its download link ''{1}'' is not a valid URL. Skipping download.", file.getName(), address); 133 Main.warn(msg); 134 throw new DownloadException(msg); 135 } catch (IOException e) { 136 if (canceled) 137 return; 138 throw new DownloadException(e.getMessage()); 139 } finally { 140 closeConnectionIfNeeded(); 141 Utils.close(out); 142 } 143 } 144 145 @Override 146 protected void realRun() throws SAXException, IOException { 147 if (canceled) return; 148 try { 149 download(); 150 } catch(DownloadException e) { 151 e.printStackTrace(); 152 } 153 } 154 155 /** 156 * Replies true if the task was canceled by the user 157 * 158 * @return {@code true} if the task was canceled by the user, {@code false} otherwise 159 */ 160 public boolean isCanceled() { 161 return canceled; 162 } 163 164 /** 165 * Recursive unzipping function 166 * TODO: May be placed somewhere else - Tools.Utils? 167 * @param file 168 * @param dir 169 * @throws IOException 170 */ 171 public static void unzipFileRecursively(File file, String dir) throws IOException { 172 OutputStream os = null; 173 InputStream is = null; 174 ZipFile zf = null; 175 try { 176 zf = new ZipFile(file); 177 Enumeration<?> es = zf.entries(); 178 ZipEntry ze; 179 while (es.hasMoreElements()) { 180 ze = (ZipEntry) es.nextElement(); 181 File newFile = new File(dir, ze.getName()); 182 if (ze.isDirectory()) { 183 newFile.mkdirs(); 184 } else { 185 is = zf.getInputStream(ze); 186 os = new BufferedOutputStream(new FileOutputStream(newFile)); 187 byte[] buffer = new byte[8192]; 188 int read; 189 while ((read = is.read(buffer)) != -1) { 190 os.write(buffer, 0, read); 191 } 192 Utils.close(os); 193 Utils.close(is); 194 } 195 } 196 } finally { 197 Utils.close(zf); 198 } 199 } 200} 201