001         package com.croftsoft.core.net;
002    
003         import java.io.*;
004         import java.net.*;
005         import java.util.*;
006    
007         import com.croftsoft.core.lang.StringLib;
008    
009         /*********************************************************************
010         * Downloads the content of a URL to a local File.
011         * 
012         * <p />
013         *
014         * @version
015         *   2003-11-06
016         * @since
017         *   1998-11-30
018         * @author
019         *   <a href="https://www.croftsoft.com/">David Wallace Croft</a>
020         *********************************************************************/
021    
022         public final class  Downloader
023         //////////////////////////////////////////////////////////////////////
024         //////////////////////////////////////////////////////////////////////
025         {
026    
027         /*********************************************************************
028         * Downloads files.
029         *
030         * <pre>
031         * if ( args.length < 4 )
032         * {
033         *   validate ( args [ 0 ], args [ 1 ], true, true );
034         * }
035         * else
036         * {
037         *   download ( args [ 0 ], args [ 1 ],
038         *     Integer.parseInt ( args [ 2 ] ),
039         *     Integer.parseInt ( args [ 3 ] ) );
040         * }
041         * </pre>
042         *********************************************************************/
043         public static void  main ( String [ ]  args )
044           throws Exception
045         //////////////////////////////////////////////////////////////////////
046         {
047           if ( args.length < 4 )
048           {
049             validate ( args [ 0 ], args [ 1 ], true, true );
050           }
051           else
052           {
053             download ( args [ 0 ], args [ 1 ],
054               Integer.parseInt ( args [ 2 ] ),
055               Integer.parseInt ( args [ 3 ] ) );
056           }
057         }
058    
059         //////////////////////////////////////////////////////////////////////
060         //////////////////////////////////////////////////////////////////////
061    
062         /*********************************************************************
063         * Downloads a sequence of files ending in a number.
064         *********************************************************************/
065         public static void  download (
066           String  urlPrefix,
067           String  filenamePrefix,
068           int     start,
069           int     stop )
070           throws IOException
071         //////////////////////////////////////////////////////////////////////
072         {
073           for ( int  index = start; index <= stop; index++ )
074           {
075             String  url = urlPrefix + index;
076    
077             String  filename = filenamePrefix + index;
078    
079             System.out.println (
080               "Downloading " + url + " to " + filename + "..." );
081    
082             download ( url, filename );
083           }       
084         }
085    
086         public static void  download (
087           String  urlString,
088           String  filename )
089           throws IOException, MalformedURLException
090         //////////////////////////////////////////////////////////////////////
091         {
092           download ( new URL ( urlString ), new File ( filename ) );
093         }
094    
095         /*********************************************************************
096         * Downloads the content at a URL to a local destination File.
097         *********************************************************************/
098         public static void  download ( URL  url, File  dest )
099           throws IOException
100         //////////////////////////////////////////////////////////////////////
101         {
102           download ( url.openStream ( ), dest );
103         }
104    
105         public static void  download (
106           InputStream  unbufferedInputStream,
107           File         destinationFile )
108           throws IOException
109         //////////////////////////////////////////////////////////////////////
110         {
111           BufferedInputStream   in  = null;
112    
113           BufferedOutputStream  out = null;
114    
115           try
116           {
117             in  = new BufferedInputStream ( unbufferedInputStream );
118    
119             out = new BufferedOutputStream (
120               new FileOutputStream ( destinationFile ) );
121    
122             int  i;
123    
124             while ( ( i = in.read ( ) ) > -1 )
125             {
126               out.write ( ( byte ) i );
127             }
128           }
129           finally
130           {
131             if ( out != null )
132             {
133               out.close ( );
134             }
135    
136             if ( in != null )
137             {
138               in.close ( );
139             }
140           }
141         }
142    
143         public static boolean  downloadResourceToDir (
144           URL  codebaseURL, String  name, File  destDir )
145         //////////////////////////////////////////////////////////////////////
146         {
147           if ( ( codebaseURL == null )
148             || ( name        == null )
149             || ( destDir     == null ) )
150    //       || !destDir.exists ( )
151    //       || !destDir.isDirectory ( ) )
152             return false;
153    
154           InputStream           inputStream = null;
155           BufferedInputStream   in          = null;
156           BufferedOutputStream  out         = null;
157    
158           try
159           {
160             // We create the file twice to make sure the path is resolved.
161    //System.out.println ( "destDir:  " + destDir.getCanonicalPath ( ) );
162    //System.out.println ( "name:  " + name );
163             File  cacheFile = new File ( destDir, name );
164    //System.out.println ( "cacheFile1:  " + cacheFile.getCanonicalPath ( ) );
165             cacheFile = new File ( cacheFile.getCanonicalPath ( ) );
166    //System.out.println ( "cacheFile2:  " + cacheFile.getCanonicalPath ( ) );
167    
168             File  parentFile = new File ( cacheFile.getParent ( ) );
169    //System.out.println ( "parentFile:  " + parentFile.getCanonicalPath ( ) );
170    //System.out.println ( "mkdirs:  " + parentFile.mkdirs ( ) );
171             parentFile.mkdirs ( );
172    
173             inputStream = downloadResource ( codebaseURL, name );
174             if ( inputStream == null )
175             {
176               System.out.println ( "  Download of \"" + name + "\" from\n"
177                 + "    \"" + codebaseURL + "\" failed." );
178               return false;
179             }
180    
181             in  = new BufferedInputStream ( inputStream );
182             out = new BufferedOutputStream (
183               new FileOutputStream ( cacheFile ) );
184    
185             int  i;
186             while ( ( i = in.read ( ) ) > -1 ) out.write ( ( byte ) i );
187    
188    System.out.println ( "  Successfully downloaded and saved." );
189    
190             return true;
191           }
192           catch ( Exception  ex )
193           {
194             ex.printStackTrace ( );
195           }
196           finally
197           {
198             try { out.close          ( ); } catch ( Exception  ex1 ) { }
199             try { in.close           ( ); } catch ( Exception  ex1 ) { }
200             try { inputStream.close  ( ); } catch ( Exception  ex1 ) { }
201           }
202    
203           return false;
204         }
205    
206         /*********************************************************************
207         * Returns null if the codebaseURL is null.
208         * Returns null upon failure.
209         *********************************************************************/
210         public static InputStream  downloadResource (
211           URL  codebaseURL, String  name )
212         //////////////////////////////////////////////////////////////////////
213         {
214           if ( codebaseURL == null ) return null;
215    
216           InputStream  inputStream = null;
217           
218           try
219           {
220             String  urlName
221               = replaceSpaces ( replaceSeparators ( name ) );
222    
223             URL  url = new URL ( codebaseURL, urlName );
224    System.out.println ( "Downloading \"" + name + "\" from\n  \"" + url + "\"" );
225    
226             URLConnection  urlConnection = url.openConnection ( );
227    
228             inputStream = urlConnection.getInputStream ( );
229    
230             return inputStream;
231           }
232           catch ( IOException  ex )
233           {
234             try { inputStream.close ( ); } catch ( Exception  ex1 ) { }
235             return null;
236           }
237         }
238    
239         /*********************************************************************
240         * Determines if the local file is valid.
241         *
242         * <p>
243         * If both <i>compareContentLength</i> and <i>compareLastModified</i>
244         * are false, this methods simply checks for the existence of the
245         * local file.
246         * </p>
247         *********************************************************************/
248         public static boolean  isValid (
249           URL      sourceURL,
250           File     localFile,
251           boolean  compareContentLength,
252           boolean  compareLastModified )
253           throws IOException, ProtocolException
254         //////////////////////////////////////////////////////////////////////
255         {
256           if ( !localFile.exists ( ) )
257           {
258             return false;
259           }
260    
261           if ( !compareContentLength && !compareLastModified )
262           {
263             return true;
264           }
265    
266           URLConnection  urlConnection = sourceURL.openConnection ( );
267    
268           if ( compareContentLength )
269           {
270             long  localLength = localFile.length ( );
271    
272    //       System.out.println ( "localLength:  " + localLength );
273    
274    // What if this is greater than Integer.MAX_VALUE?
275    
276             int  sourceLength = urlConnection.getContentLength ( );
277    
278    //       System.out.println ( "sourceLength:  " + sourceLength );
279    
280             if ( localLength != sourceLength )
281             {
282               return false;
283             }
284           }
285    
286           if ( compareLastModified )
287           {
288             long  localLastModified = localFile.lastModified ( );
289    
290             //System.out.println (
291             //  "localLastModified:  " + new Date ( localLastModified ) );
292    
293             long  sourceLastModified = urlConnection.getLastModified ( );
294    
295             //System.out.println (
296             //  "sourceLastModified:  " + new Date ( sourceLastModified ) );
297    
298             if ( localLastModified != sourceLastModified )
299             {
300               return false;
301             }
302           }
303    
304           return true;
305         }
306    
307         /*********************************************************************
308         * Determines if the local file is valid.
309         *
310         * <p>
311         * If both <i>compareContentLength</i> and <i>compareLastModified</i>
312         * are false, this methods simply checks for the existence of the
313         * local file.
314         * </p>
315         *
316         * @param  sourceURLName
317         *
318         *   An HTTP URL.
319         *********************************************************************/
320         public static boolean  isValid (
321           String   sourceURLName,
322           String   localFilename,
323           boolean  compareContentLength,
324           boolean  compareLastModified )
325           throws IOException, ProtocolException
326         //////////////////////////////////////////////////////////////////////
327         {
328           return isValid (
329             new URL  ( sourceURLName ),
330             new File ( localFilename ),
331             compareContentLength,
332             compareLastModified );
333         }
334    
335         /*********************************************************************
336         * Replaces every instance of File.separator with a forward slash
337         * character.  This is used for converting a local path name to
338         * a URL path name.
339         *********************************************************************/
340         public static String  replaceSeparators ( String  localPath )
341         //////////////////////////////////////////////////////////////////////
342         {
343           return StringLib.replace ( localPath, File.separator, "/" );
344         }
345    
346         /*********************************************************************
347         * Replaces every instance of space (" ") with the
348         * x-www-form-urlencoded equivalent ("%20").
349         * This is used for converting a remote filename with spaces so that
350         * it can be downloaded via a HTTP request.
351         *********************************************************************/
352         public static String  replaceSpaces ( String  remoteFilename )
353         //////////////////////////////////////////////////////////////////////
354         {
355           return StringLib.replace ( remoteFilename, " ", "%20" );
356         }
357    
358         /*********************************************************************
359         * Downloads a file if the local copy is missing or outdated.
360         *
361         * @return
362         *
363         *   True if a new copy needed to be downloaded.
364         *********************************************************************/
365         public static boolean  validate (
366           String   sourceURLName,
367           String   localFilename,
368           boolean  checkContentLength,
369           boolean  checkLastModified )
370           throws IOException, MalformedURLException
371         //////////////////////////////////////////////////////////////////////
372         {
373           return validate (
374             new URL  ( sourceURLName ),
375             new File ( localFilename ),
376             checkContentLength,
377             checkLastModified );
378         }
379    
380         /*********************************************************************
381         * Downloads a file if the local copy is missing or outdated.
382         *
383         * @return
384         *
385         *   True if a new copy needed to be downloaded.
386         *********************************************************************/
387         public static boolean  validate (
388           URL      sourceURL,
389           File     localFile,
390           boolean  checkContentLength,
391           boolean  checkLastModified )
392           throws IOException
393         //////////////////////////////////////////////////////////////////////
394         {
395           if ( !localFile.exists ( ) )
396           {
397             Downloader.download ( sourceURL, localFile );
398    
399             return true;
400           }
401           else if ( checkContentLength || checkLastModified )
402           {
403             long  localLength = localFile.length ( );
404    
405             //System.out.println ( "localLength:  " + localLength );
406    
407             long  localLastModified = localFile.lastModified ( );
408    
409             //System.out.println (
410             //  "localLastModified:  " + new Date ( localLastModified ) );
411    
412             URLConnection  urlConnection = sourceURL.openConnection ( );
413    
414             int  sourceLength = urlConnection.getContentLength ( );
415    
416             //System.out.println ( "sourceLength:  " + sourceLength );
417    
418             long  sourceLastModified = urlConnection.getLastModified ( );
419    
420             //System.out.println ( "sourceLastModified:  "
421             //  + new Date ( sourceLastModified ) );
422    
423             InputStream  inputStream = null;
424    
425             try
426             {
427               inputStream = urlConnection.getInputStream ( );
428    
429               if ( checkContentLength
430                      && ( sourceLength != localLength )
431                 || checkLastModified
432                      && ( sourceLastModified > localLastModified ) )
433               {
434                 //System.out.println ( "Downloading \"" + sourceURL
435                 //  + "\" to \"" + localFile + "\"..." );
436    
437                 download ( inputStream, localFile );
438    
439                 return true;
440               }
441             }
442             finally
443             {
444               if ( inputStream != null )
445               {
446                 inputStream.close ( );
447               }
448             }
449           }
450    
451           return false;
452         }
453         
454         //////////////////////////////////////////////////////////////////////
455         //////////////////////////////////////////////////////////////////////
456    
457         private  Downloader ( ) { }
458    
459         //////////////////////////////////////////////////////////////////////
460         //////////////////////////////////////////////////////////////////////
461         }