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