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