svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.library / org.gvsig.installer / org.gvsig.installer.lib / org.gvsig.installer.lib.impl / src / main / java / org / gvsig / installer / lib / impl / utils / Download.java @ 40560
History | View | Annotate | Download (8.72 KB)
1 | 40560 | jjdelcerro | /**
|
---|---|---|---|
2 | * gvSIG. Desktop Geographic Information System.
|
||
3 | 40435 | jjdelcerro | *
|
4 | 40560 | jjdelcerro | * Copyright (C) 2007-2013 gvSIG Association.
|
5 | 40435 | jjdelcerro | *
|
6 | * This program is free software; you can redistribute it and/or
|
||
7 | * modify it under the terms of the GNU General Public License
|
||
8 | 40560 | jjdelcerro | * as published by the Free Software Foundation; either version 3
|
9 | 40435 | jjdelcerro | * of the License, or (at your option) any later version.
|
10 | *
|
||
11 | * This program is distributed in the hope that it will be useful,
|
||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
14 | * GNU General Public License for more details.
|
||
15 | *
|
||
16 | * You should have received a copy of the GNU General Public License
|
||
17 | * along with this program; if not, write to the Free Software
|
||
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||
19 | * MA 02110-1301, USA.
|
||
20 | *
|
||
21 | 40560 | jjdelcerro | * For any additional information, do not hesitate to contact us
|
22 | * at info AT gvsig.com, or visit our website www.gvsig.com.
|
||
23 | 40435 | jjdelcerro | */
|
24 | package org.gvsig.installer.lib.impl.utils; |
||
25 | |||
26 | import java.io.BufferedInputStream; |
||
27 | import java.io.BufferedOutputStream; |
||
28 | import java.io.File; |
||
29 | import java.io.FileOutputStream; |
||
30 | import java.io.IOException; |
||
31 | import java.net.URL; |
||
32 | import java.net.URLConnection; |
||
33 | import java.util.Date; |
||
34 | import java.util.StringTokenizer; |
||
35 | |||
36 | import org.slf4j.Logger; |
||
37 | import org.slf4j.LoggerFactory; |
||
38 | |||
39 | import org.gvsig.tools.ToolsLocator; |
||
40 | import org.gvsig.tools.task.AbstractMonitorableTask; |
||
41 | import org.gvsig.tools.task.SimpleTaskStatus; |
||
42 | import org.gvsig.tools.task.TaskStatusManager; |
||
43 | |||
44 | /**
|
||
45 | * @author gvSIG Team
|
||
46 | * @version $Id$
|
||
47 | *
|
||
48 | */
|
||
49 | public class Download extends AbstractMonitorableTask { |
||
50 | |||
51 | private static final Logger LOG = LoggerFactory.getLogger(Download.class); |
||
52 | |||
53 | /**
|
||
54 | * If file size provided by URLConnection is smaller than this,
|
||
55 | * then the downloaded file size will always be accepted (unit: bytes)
|
||
56 | */
|
||
57 | private static final int FILE_SIZE_ESTIMATED_MINIMUM = 10000; |
||
58 | |||
59 | /**
|
||
60 | * Downloaded file size will be accepted if it is bigger than this ratio
|
||
61 | * multiplied by the estimated file size provided by URLConnection
|
||
62 | * (unit: bytes)
|
||
63 | */
|
||
64 | private static final double FILE_SIZE_ESTIMATED_RATIO = 0.85; |
||
65 | |||
66 | private boolean selfCreatedTaskStatus = true; |
||
67 | |||
68 | /**
|
||
69 | * @param taskName
|
||
70 | */
|
||
71 | public Download(SimpleTaskStatus taskStatus) {
|
||
72 | this();
|
||
73 | |||
74 | /*
|
||
75 | * constructor without params is adding
|
||
76 | * itself to manager, so we remove it
|
||
77 | */
|
||
78 | TaskStatusManager manager = ToolsLocator.getTaskStatusManager(); |
||
79 | manager.remove(getTaskStatus()); |
||
80 | |||
81 | /*
|
||
82 | * The received taskstatus is being managed by somebody else,
|
||
83 | * we don not add it to the manager
|
||
84 | */
|
||
85 | this.taskStatus = taskStatus;
|
||
86 | selfCreatedTaskStatus = false;
|
||
87 | } |
||
88 | |||
89 | public Download() {
|
||
90 | /**
|
||
91 | * this call is adding the task status to the manager
|
||
92 | */
|
||
93 | super("Downloading..."); |
||
94 | } |
||
95 | |||
96 | public File downloadFile(URL url, String defaultFileName) |
||
97 | throws IOException { |
||
98 | |||
99 | URL downloadURL = url;
|
||
100 | |||
101 | // check if the URL ends with '/' and append the file name
|
||
102 | if (defaultFileName != null) { |
||
103 | String urlStr = url.toString();
|
||
104 | if (urlStr.endsWith("/")) { |
||
105 | urlStr = urlStr.concat(defaultFileName); |
||
106 | downloadURL = new URL(urlStr); |
||
107 | } |
||
108 | } |
||
109 | |||
110 | URLConnection connection = downloadURL.openConnection();
|
||
111 | connection.setUseCaches(false);
|
||
112 | connection.setConnectTimeout(30000);
|
||
113 | connection.setReadTimeout(20000);
|
||
114 | |||
115 | String fileName = getFileName(connection);
|
||
116 | |||
117 | // if (LOG.isDebugEnabled()) {
|
||
118 | Date date = new Date(connection.getLastModified()); |
||
119 | LOG |
||
120 | .info( |
||
121 | "Downloading file {} from URL {}, with last modified date: {}",
|
||
122 | new Object[] { fileName, downloadURL, date }); |
||
123 | // }
|
||
124 | |||
125 | String fileNamePrefix = fileName;
|
||
126 | String fileNameSuffix = "zip"; |
||
127 | int dotPosition = fileName.lastIndexOf('.'); |
||
128 | if ((dotPosition > -1) && (dotPosition < fileName.length() - 1)) { |
||
129 | fileNamePrefix = fileName.substring(0, dotPosition);
|
||
130 | fileNameSuffix = fileName.substring(dotPosition); |
||
131 | } |
||
132 | |||
133 | BufferedInputStream bis = new BufferedInputStream(connection.getInputStream()); |
||
134 | |||
135 | File localFile = File.createTempFile(fileNamePrefix, fileNameSuffix); |
||
136 | |||
137 | BufferedOutputStream bos = new BufferedOutputStream( |
||
138 | new FileOutputStream(localFile)); |
||
139 | |||
140 | int expected_size = connection.getContentLength();
|
||
141 | this.taskStatus.setRangeOfValues(0, expected_size); |
||
142 | |||
143 | |||
144 | byte[] data = new byte[1024]; |
||
145 | int count = 0; |
||
146 | long totalCount = 0; // total bytes read |
||
147 | while ((count = bis.read(data, 0, 1024)) >= 0) { |
||
148 | |||
149 | bos.write(data, 0, count);
|
||
150 | totalCount += count; |
||
151 | |||
152 | this.taskStatus.setCurValue(totalCount);
|
||
153 | |||
154 | if (this.taskStatus.isCancellationRequested()) { |
||
155 | break;
|
||
156 | } |
||
157 | } |
||
158 | |||
159 | try {
|
||
160 | bis.close(); |
||
161 | bos.flush(); |
||
162 | bos.close(); |
||
163 | } catch (Exception ex) { |
||
164 | LOG.info("Error while closing download streams: " + ex.getMessage());
|
||
165 | } |
||
166 | |||
167 | if (selfCreatedTaskStatus) {
|
||
168 | this.taskStatus.terminate();
|
||
169 | this.taskStatus.remove();
|
||
170 | } |
||
171 | |||
172 | // out from the read loop
|
||
173 | // perhaps it was cancelled:
|
||
174 | if (this.taskStatus.isCancellationRequested()) { |
||
175 | return null; |
||
176 | } |
||
177 | |||
178 | String md5_ = MD5BinaryFileUtils.getMD5InRemoteFile(downloadURL);
|
||
179 | |||
180 | if (md5_ != null) { |
||
181 | // ==========================================
|
||
182 | // check md5
|
||
183 | String local_md5 = null; |
||
184 | try {
|
||
185 | local_md5 = MD5BinaryFileUtils.getMD5Checksum(localFile); |
||
186 | } catch (Exception e) { |
||
187 | throw new IOException("Unable to get MD5 for file: " + downloadURL.toString()); |
||
188 | } |
||
189 | if (local_md5.compareTo(md5_) != 0) { |
||
190 | throw new IOException("MD5 does not match for file: " + downloadURL.toString()); |
||
191 | } |
||
192 | // ==========================================
|
||
193 | } else {
|
||
194 | // ==========================================
|
||
195 | // check real size and expected size:
|
||
196 | if (!acceptableFileSize(localFile, expected_size)) {
|
||
197 | throw new IOException("Bad download file size (" |
||
198 | + localFile.length() |
||
199 | + " / "
|
||
200 | + expected_size |
||
201 | + ")");
|
||
202 | } |
||
203 | // ==========================================
|
||
204 | } |
||
205 | |||
206 | // everything seems to be OK
|
||
207 | return localFile;
|
||
208 | |||
209 | } |
||
210 | |||
211 | /**
|
||
212 | * Returns the file name associated to an url connection.<br />
|
||
213 | * The result is not a path but just a file name.
|
||
214 | *
|
||
215 | * @param urlConnection
|
||
216 | * - the url connection
|
||
217 | * @return the file name
|
||
218 | *
|
||
219 | * @throws IOException
|
||
220 | * Signals that an I/O exception has occurred.
|
||
221 | */
|
||
222 | private String getFileName(URLConnection urlConnection) throws IOException { |
||
223 | String fileName = null; |
||
224 | |||
225 | String contentDisposition = urlConnection
|
||
226 | .getHeaderField("content-disposition");
|
||
227 | |||
228 | if (contentDisposition != null) { |
||
229 | fileName = extractFileNameFromContentDisposition(contentDisposition); |
||
230 | } |
||
231 | |||
232 | // if the file name cannot be extracted from the content-disposition
|
||
233 | // header, using the url.getFilename() method
|
||
234 | if (fileName == null) { |
||
235 | StringTokenizer st = new StringTokenizer(urlConnection.getURL() |
||
236 | .getFile(), "/");
|
||
237 | while (st.hasMoreTokens()) {
|
||
238 | fileName = st.nextToken(); |
||
239 | } |
||
240 | } |
||
241 | |||
242 | return fileName;
|
||
243 | } |
||
244 | |||
245 | /**
|
||
246 | * Extract the file name from the content disposition header.
|
||
247 | * <p>
|
||
248 | * See <a
|
||
249 | * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html">http:
|
||
250 | * //www.w3.org/Protocols/rfc2616/rfc2616-sec19.html</a> for detailled
|
||
251 | * information regarding the headers in HTML.
|
||
252 | *
|
||
253 | * @param contentDisposition
|
||
254 | * - the content-disposition header. Cannot be <code>null>/code>.
|
||
255 | * @return the file name, or <code>null</code> if the content-disposition
|
||
256 | * header does not contain the filename attribute.
|
||
257 | */
|
||
258 | private String extractFileNameFromContentDisposition( |
||
259 | String contentDisposition) {
|
||
260 | String[] attributes = contentDisposition.split(";"); |
||
261 | |||
262 | for (String a : attributes) { |
||
263 | if (a.toLowerCase().contains("filename")) { |
||
264 | // The attribute is the file name. The filename is between
|
||
265 | // quotes.
|
||
266 | return a.substring(a.indexOf('\"') + 1, a.lastIndexOf('\"')); |
||
267 | } |
||
268 | } |
||
269 | |||
270 | // not found
|
||
271 | return null; |
||
272 | |||
273 | } |
||
274 | |||
275 | /**
|
||
276 | * {@link URLConnection}'s method getContentLength() does not give
|
||
277 | * exact size.
|
||
278 | *
|
||
279 | * @return whether the size is acceptable
|
||
280 | */
|
||
281 | public static boolean acceptableFileSize(File f, int estimated_size) { |
||
282 | |||
283 | if (f == null) { |
||
284 | return false; |
||
285 | } |
||
286 | |||
287 | if (estimated_size == -1) { |
||
288 | // -1 means unknown
|
||
289 | return true; |
||
290 | } |
||
291 | |||
292 | if (estimated_size < FILE_SIZE_ESTIMATED_MINIMUM) {
|
||
293 | return true; |
||
294 | } |
||
295 | |||
296 | return f.length() > (FILE_SIZE_ESTIMATED_RATIO * estimated_size);
|
||
297 | } |
||
298 | } |