]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/gui/map/DiskTileCacher.java
Version 16, February 2014
[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                                         System.err.println("createImage: " + e.getClass().getName() + " _ " + e.getMessage());
68                                 }
69                         }
70                 }
71                 return image;
72         }
73
74         /**
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
81          */
82         public static boolean saveTile(URL inUrl, String inBasePath, String inTilePath, ImageObserver inObserver)
83         {
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
89                         return false;
90                 }
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;}
96
97                 File dir = tileFile.getParentFile();
98                 // Start a new thread to load the image if necessary
99                 if ((dir.exists() || dir.mkdirs()) && dir.canWrite())
100                 {
101                         new DiskTileCacher(inUrl, tileFile, inObserver);
102                         return true;
103                 }
104                 return false; // couldn't write the file
105         }
106
107         /**
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
111          */
112         private static boolean isBeingLoaded(File inFile)
113         {
114                 File tempFile = new File(inFile.getAbsolutePath() + ".temp");
115                 if (!tempFile.exists()) {
116                         return false;
117                 }
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
121         }
122
123         /**
124          * Run method for loading URL asynchronously and saving to file
125          */
126         public void run()
127         {
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)
134                 {
135                         if (tempFile.exists()) {tempFile.delete();}
136                         try {
137                                 if (!tempFile.createNewFile()) {return;}
138                         }
139                         catch (Exception e) {return;}
140                 }
141                 try
142                 {
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();
149                         int d = 0;
150                         // Loop over each byte in the stream (maybe buffering is more efficient?)
151                         while ((d = in.read()) >= 0) {
152                                 out.write(d);
153                         }
154                         finished = true;
155                 } catch (IOException e) {
156                         System.err.println("ioe: " + e.getClass().getName() + " - " + e.getMessage());
157                         BLOCKED_URLS.add(_url.toString());
158                 }
159                 finally
160                 {
161                         // clean up files
162                         try {in.close();} catch (Exception e) {} // ignore
163                         try {out.close();} catch (Exception e) {} // ignore
164                         if (!finished) {
165                                 tempFile.delete();
166                         }
167                 }
168                 // Move temp file to desired file location
169                 if (!tempFile.renameTo(_file))
170                 {
171                         // File couldn't be moved - delete both to be sure
172                         tempFile.delete();
173                         _file.delete();
174                 }
175                 // Tell parent that load is finished (parameters ignored)
176                 _observer.imageUpdate(null, ImageObserver.ALLBITS, 0, 0, 0, 0);
177         }
178 }