1 package tim.prune.gui.map;
4 import java.awt.Toolkit;
5 import java.awt.image.ImageObserver;
7 import java.io.FileOutputStream;
8 import java.io.IOException;
9 import java.io.InputStream;
11 import java.net.URLConnection;
12 import java.util.HashSet;
14 import tim.prune.GpsPrune;
17 * Class to control the reading and saving of map tiles
20 public class DiskTileCacher implements Runnable
22 /** URL to get image from */
23 private URL _url = null;
24 /** File to save image to */
25 private File _file = null;
26 /** Observer to be notified */
27 private ImageObserver _observer = null;
28 /** Time limit to cache images for */
29 private static final long CACHE_TIME_LIMIT = 20 * 24 * 60 * 60 * 1000; // 20 days in ms
30 /** Hashset of all blocked / 404 tiles to avoid requesting them again */
31 private static final HashSet<String> BLOCKED_URLS = new HashSet<String>();
35 * @param inUrl URL to get
36 * @param inFile file to save to
38 private DiskTileCacher(URL inUrl, File inFile, ImageObserver inObserver)
42 _observer = inObserver;
43 new Thread(this).start();
47 * Get the specified tile from the disk cache
48 * @param inBasePath base path to whole disk cache
49 * @param inTilePath relative path to requested tile
50 * @param inCheckAge true to check age of file, false to ignore
51 * @return tile image if available, or null if not there
53 public static Image getTile(String inBasePath, String inTilePath, boolean inCheckAge)
55 if (inBasePath == null) {return null;}
56 File tileFile = new File(inBasePath, inTilePath);
58 if (tileFile.exists() && tileFile.canRead() && tileFile.length() > 0)
60 long fileStamp = tileFile.lastModified();
61 if (!inCheckAge || ((System.currentTimeMillis()-fileStamp) < CACHE_TIME_LIMIT))
64 image = Toolkit.getDefaultToolkit().createImage(tileFile.getAbsolutePath());
67 System.err.println("createImage: " + e.getClass().getName() + " _ " + e.getMessage());
75 * Save the specified image tile to disk
76 * @param inUrl url to get image from
77 * @param inBasePath base path to disk cache
78 * @param inTilePath relative path to this tile
79 * @param inObserver observer to inform when load complete
80 * @return true if successful, false for failure
82 public static boolean saveTile(URL inUrl, String inBasePath, String inTilePath, ImageObserver inObserver)
84 if (inBasePath == null || inTilePath == null) {return false;}
85 // save file if possible
86 File basePath = new File(inBasePath);
87 if (!basePath.exists() || !basePath.isDirectory() || !basePath.canWrite()) {
88 // Can't write to base path
91 File tileFile = new File(basePath, inTilePath);
92 // Check if this file is already being loaded
93 if (isBeingLoaded(tileFile)) {return true;}
94 // Check if it has already failed
95 if (BLOCKED_URLS.contains(inUrl.toString())) {return true;}
97 File dir = tileFile.getParentFile();
98 // Start a new thread to load the image if necessary
99 if ((dir.exists() || dir.mkdirs()) && dir.canWrite())
101 new DiskTileCacher(inUrl, tileFile, inObserver);
104 return false; // couldn't write the file
108 * Check whether the given tile is already being loaded
109 * @param inFile desired file
110 * @return true if temporary file with this name exists
112 private static boolean isBeingLoaded(File inFile)
114 File tempFile = new File(inFile.getAbsolutePath() + ".temp");
115 if (!tempFile.exists()) {
118 // File exists, so check if it was created recently
119 final long fileAge = System.currentTimeMillis() - tempFile.lastModified();
120 return fileAge < 1000000L; // overwrite if the temp file is still there after 1000s
124 * Run method for loading URL asynchronously and saving to file
128 boolean finished = false;
129 InputStream in = null;
130 FileOutputStream out = null;
131 File tempFile = new File(_file.getAbsolutePath() + ".temp");
132 // Use a synchronized block across all threads to make sure this url is only fetched once
133 synchronized (DiskTileCacher.class)
135 if (tempFile.exists()) {tempFile.delete();}
137 if (!tempFile.createNewFile()) {return;}
139 catch (Exception e) {return;}
143 // Open streams from URL and to file
144 out = new FileOutputStream(tempFile);
145 // Set http user agent on connection
146 URLConnection conn = _url.openConnection();
147 conn.setRequestProperty("User-Agent", "GpsPrune v" + GpsPrune.VERSION_NUMBER);
148 in = conn.getInputStream();
150 // Loop over each byte in the stream (maybe buffering is more efficient?)
151 while ((d = in.read()) >= 0) {
155 } catch (IOException e) {
156 System.err.println("ioe: " + e.getClass().getName() + " - " + e.getMessage());
157 BLOCKED_URLS.add(_url.toString());
162 try {in.close();} catch (Exception e) {} // ignore
163 try {out.close();} catch (Exception e) {} // ignore
168 // Move temp file to desired file location
169 if (!tempFile.renameTo(_file))
171 // File couldn't be moved - delete both to be sure
175 // Tell parent that load is finished (parameters ignored)
176 _observer.imageUpdate(null, ImageObserver.ALLBITS, 0, 0, 0, 0);