View Javadoc

1   package org.lcsim.util.cache;
2   
3   import java.io.File;
4   import java.io.FileOutputStream;
5   import java.io.IOException;
6   import java.io.InputStream;
7   import java.io.OutputStream;
8   import java.io.PrintStream;
9   import java.net.URL;
10  import java.net.URLConnection;
11  import java.net.URLEncoder;
12  
13  /**
14   * A utility for downloading and caching files
15   * @author tonyj
16   */
17  public class FileCache {
18      private static final File home = new File(getCacheRoot(), ".cache");
19      private static final ByteFormat format = new ByteFormat();
20      private File cache;
21      private PrintStream print = System.out;
22  
23      public static File getCacheRoot() {
24          String cacheDir = System.getProperty("org.lcsim.cacheDir");
25          if (cacheDir == null)
26              cacheDir = System.getProperty("user.home");
27          return new File(cacheDir);
28      }
29  
30      /**
31       * Create a FileCache using the default cache location
32       * @throws java.io.IOException If the cache directory cannot be created
33       */
34      public FileCache() throws IOException {
35          this(home);
36      }
37  
38      /**
39       * Create a file cache using an explicit cache location
40       * @param cacheDirectory The directory to use for storing cached files
41       * @throws java.io.IOException If the cache directory cannot be created
42       */
43      public FileCache(File cacheDirectory) throws IOException {
44          setCacheDirectory(cacheDirectory);
45      }
46  
47      /**
48       * Get a file from the cache. If the file is already in the cache and
49       * up-to-date the existing file will be returned. Otherwise the file will be
50       * downloaded first, and then moved to the cache.
51       * @param url The URL of the file to download
52       * @throws java.io.IOException If the file cannot be downloaded, or if an
53       *             error occurs writing to the cache.
54       * @return The location of the cached file
55       */
56      public File getCachedFile(URL url) throws IOException {
57          return getCachedFile(url, null);
58      }
59  
60      /**
61       * Get a file from the cache. If the file is already in the cache and
62       * up-to-date the existing file will be returned. Otherwise the file will be
63       * downloaded first, validated, and then moved to the cache.
64       * @param url Teh URL of the file to download
65       * @param validator A Validator that will be used to check the integrity of
66       *            the downloaded file before it is placed in the cache
67       * @throws java.io.IOException If the file cannot be downloaded, or if an
68       *             error occurs writing to the cache, or if the file fails
69       *             validation.
70       * @return The location of the cached file
71       */
72      public File getCachedFile(URL url, Validator validator) throws IOException {
73          File cacheFile = new File(cache, URLEncoder.encode(url.toExternalForm(), "UTF-8"));
74          boolean downloadRequired = !cacheFile.exists();
75          if (cacheFile.exists() && !Boolean.getBoolean("org.lcsim.offline")) {
76              // If we can access the URL, check if the cachefile is up-to-date
77              try {
78                  URLConnection connection = url.openConnection();
79                  connection.setConnectTimeout(5000);
80                  connection.setReadTimeout(5000);
81                  long updated = connection.getLastModified();
82                  long cached = cacheFile.lastModified();
83                  if (updated > cached)
84                      downloadRequired = true;
85              } catch (IOException x) {
86                  // if (print != null)
87                  // print.println("Warning: file cache could not access "+url+", "+x.getMessage());
88              }
89          }
90          if (downloadRequired) {
91              URLConnection connection = url.openConnection();
92              connection.setConnectTimeout(10000);
93              connection.setReadTimeout(10000);
94              long size = connection.getContentLength();
95              InputStream in = connection.getInputStream();
96              if (in != null) {
97                  if (print != null)
98                      print.println("Downloading..." + url);
99                  File temp = File.createTempFile("det", null, cache);
100                 temp.deleteOnExit();
101                 OutputStream out = new FileOutputStream(temp);
102                 byte[] buffer = new byte[8096];
103                 try {
104                     long got = 0;
105                     for (;;) {
106                         int l = in.read(buffer);
107                         if (l < 0)
108                             break;
109                         out.write(buffer, 0, l);
110                         if (out != null) {
111                             got += l;
112                             if (print != null) {
113                                 print.print("Got ");
114                                 print.print(format.format(got));
115                                 print.print('/');
116                                 print.print(format.format(size));
117                                 print.print('\r');
118                                 print.flush();
119                             }
120                         }
121                     }
122                 } finally {
123                     in.close();
124                     out.close();
125                 }
126                 if (validator != null)
127                     validator.checkValidity(url, temp);
128                 // File looks ok, move it into the cache
129                 if (cacheFile.exists()) {
130                     boolean ok = cacheFile.delete();
131                     if (!ok)
132                         throw new IOException("Error while deleting old cache file");
133                 }
134                 boolean ok = temp.renameTo(cacheFile);
135                 if (!ok)
136                     throw new IOException("Error while moving file to cache");
137             } else
138                 throw new IOException("Could not open " + url);
139         }
140         return cacheFile;
141     }
142 
143     /**
144      * Get the directory used for caching
145      * @return The cache directory
146      */
147     public File getCacheDirectory() {
148         return this.cache;
149     }
150 
151     /**
152      * Set a new cache directory location
153      * @param cacheDirectory The directory to use.
154      * @throws java.io.IOException If the specified directory cannot be created,
155      *             or already exists and is not a directory.
156      */
157     public void setCacheDirectory(File cacheDirectory) throws IOException {
158         if (!cacheDirectory.exists()) {
159             boolean ok = cacheDirectory.mkdirs();
160             if (!ok)
161                 throw new IOException("Unable to create: " + cacheDirectory.getAbsoluteFile());
162         } else if (!cacheDirectory.isDirectory()) {
163             throw new IOException("Not a directory");
164         }
165         this.cache = cacheDirectory;
166     }
167 
168     /**
169      * Gets the print stream used for diagnostic messages if a file is
170      * downloaded.
171      * @return The current diagnostic print stream, or <CODE>null</CODE> if none
172      *         exists.
173      */
174     public PrintStream getPrintStream() {
175 
176         return this.print;
177     }
178 
179     /**
180      * Set the print stream to be used for diagnostic messages
181      * @param printStream The print stream to use, or <CODE>null</CODE> to
182      *            disable diagnostic messages
183      */
184     public void setPrintStream(PrintStream printStream) {
185         this.print = printStream;
186     }
187 
188     /**
189      * An interface to be implemented by cache validators
190      * @see #getCachedFile(URL,Validator)
191      */
192     public static interface Validator {
193         /**
194          * Called after a file has been downloaded but before it is placed in
195          * the cache, This method should throw an IOException if the file fails
196          * to validate.
197          * @param url The URL being downloaded
198          * @param file The file that needs to be validated
199          * @throws java.io.IOException If validation fails
200          */
201         void checkValidity(URL url, File file) throws IOException;
202     }
203 }