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