source: josm/trunk/src/org/openstreetmap/josm/io/CacheFiles.java@ 1663

Last change on this file since 1663 was 1610, checked in by stoecker, 15 years ago

add generic caching class (e.g. for WMS plugin) - patch by xeen

File size: 10.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io;
3
4import java.awt.image.BufferedImage;
5import java.io.File;
6import java.io.RandomAccessFile;
7import java.math.BigInteger;
8import java.security.MessageDigest;
9import java.util.Date;
10import java.util.Iterator;
11import java.util.Set;
12import java.util.TreeMap;
13
14import javax.imageio.ImageIO;
15
16import org.openstreetmap.josm.Main;
17
18/**
19 * Use this class if you want to cache a lot of files that shouldn't be kept in memory. You can
20 * specify how much data should be stored and after which date the files should be expired.
21 * This works on a last-access basis, so files get deleted after they haven't been used for x days.
22 * You can turn this off by calling setUpdateModTime(false). Files get deleted on a first-in-first-out
23 * basis.
24 * @author xeen
25 *
26 */
27public class CacheFiles {
28 /**
29 * Common expirey dates
30 */
31 final static public int EXPIRE_NEVER = -1;
32 final static public int EXPIRE_DAILY = 60 * 60 * 24;
33 final static public int EXPIRE_WEEKLY = EXPIRE_DAILY * 7;
34 final static public int EXPIRE_MONTHLY = EXPIRE_WEEKLY * 4;
35
36 final private File dir;
37 final private String ident;
38 final private boolean enabled;
39
40 private long expire; // in seconds
41 private long maxsize; // in megabytes
42 private boolean updateModTime = true;
43
44 // If the cache is full, we don't want to delete just one file
45 private final int cleanUpThreshold = 20;
46 // We don't want to clean after every file-write
47 private final int cleanUpInterval = 5;
48 // Stores how many files have been written
49 private int writes = 0;
50
51 /**
52 * Creates a new cache class. The ident will be used to store the files on disk and to save
53 * expire/space settings.
54 * @param ident
55 */
56 public CacheFiles(String ident) {
57 String pref = Main.pref.getPluginsDirFile().getPath();
58 boolean dir_writeable;
59 this.ident = ident;
60 this.dir = new File(pref + "/" + ident + "/cache/");
61 try {
62 this.dir.mkdirs();
63 dir_writeable = true;
64 } catch(Exception e) {
65 // We have no access to this directory, so don't to anything
66 dir_writeable = false;
67 }
68 this.enabled = dir_writeable;
69 this.expire = Main.pref.getLong("cache." + ident + "." + "expire", EXPIRE_DAILY);
70 if(this.expire < 0)
71 this.expire = CacheFiles.EXPIRE_NEVER;
72 this.maxsize = Main.pref.getLong("cache." + ident + "." + "maxsize", 50);
73 if(this.maxsize < 0)
74 this.maxsize = -1;
75 }
76
77 /**
78 * Loads the data for the given ident as an byte array. Returns null if data not available.
79 * @param ident
80 * @return
81 */
82 public byte[] getData(String ident) {
83 if(!enabled) return null;
84 try {
85 File data = getPath(ident);
86 if(!data.exists())
87 return null;
88
89 if(isExpired(data)) {
90 data.delete();
91 return null;
92 }
93
94 // Update last mod time so we don't expire recently used data
95 if(updateModTime)
96 data.setLastModified(new Date().getTime());
97
98 byte[] bytes = new byte[(int) data.length()];
99 new RandomAccessFile(data, "r").readFully(bytes);
100 return bytes;
101 } catch(Exception e) {
102 System.out.println(e.getMessage());
103 }
104 return null;
105 }
106
107 /**
108 * Writes an byte-array to disk
109 * @param ident
110 * @param data
111 */
112 public void saveData(String ident, byte[] data) {
113 if(!enabled) return;
114 try {
115 File f = getPath(ident);
116 if(f.exists())
117 f.delete();
118 // rws also updates the file meta-data, i.e. last mod time
119 new RandomAccessFile(f, "rws").write(data);
120 } catch(Exception e){
121 System.out.println(e.getMessage());
122 }
123
124 writes++;
125 checkCleanUp();
126 }
127
128 /**
129 * Loads the data for the given ident as an image. If no image is found, null is returned
130 * @param ident Identifier
131 * @return BufferedImage or null
132 */
133 public BufferedImage getImg(String ident) {
134 if(!enabled) return null;
135 try {
136 File img = getPath(ident, "png");
137 if(!img.exists())
138 return null;
139
140 if(isExpired(img)) {
141 img.delete();
142 return null;
143 }
144 // Update last mod time so we don't expire recently used images
145 if(updateModTime)
146 img.setLastModified(new Date().getTime());
147 return ImageIO.read(img);
148 } catch(Exception e) {
149 System.out.println(e.getMessage());
150 }
151 return null;
152 }
153
154 /**
155 * Saves a given image and ident to the cache
156 * @param ident
157 * @param image
158 */
159 public void saveImg(String ident, BufferedImage image) {
160 if(!enabled) return;
161 try {
162 ImageIO.write(image, "png", getPath(ident, "png"));
163 } catch(Exception e){
164 System.out.println(e.getMessage());
165 }
166
167 writes++;
168 checkCleanUp();
169 }
170
171
172 /**
173 * Sets the amount of time data is stored before it gets expired
174 * @param amount of time in seconds
175 * @param force will also write it to the preferences
176 */
177 public void setExpire(int amount, boolean force) {
178 if(amount < 0)
179 this.expire = EXPIRE_NEVER;
180 else
181 this.expire = amount;
182 String key = "cache." + ident + "." + "expire";
183 if(force || !Main.pref.hasKey(key))
184 Main.pref.putLong(key, this.expire);
185 }
186
187 /**
188 * Sets the amount of data stored in the cache
189 * @param amount in Megabytes
190 * @param force will also write it to the preferences
191 */
192 public void setMaxSize(int amount, boolean force) {
193 this.maxsize = amount > 0 ? amount : -1;
194 String key = "cache." + ident + "." + "maxsize";
195 if(force || !Main.pref.hasKey(key))
196 Main.pref.putLong(key, this.maxsize);
197 }
198
199 /**
200 * Call this with true to update the last modification time when a file it is read.
201 * Call this with false to not update the last modification time when a file is read.
202 * @param to
203 */
204 public void setUpdateModTime(boolean to) {
205 updateModTime = to;
206 }
207
208 /**
209 * Checks if a clean up is needed and will do so if necessary
210 */
211 public void checkCleanUp() {
212 if(this.writes > this.cleanUpInterval)
213 cleanUp();
214 }
215
216 /**
217 * Performs a default clean up with the set values (deletes oldest files first)
218 */
219 public void cleanUp() {
220 if(!this.enabled || maxsize == -1) return;
221
222 TreeMap<Long, File> modtime = new TreeMap<Long, File>();
223 long dirsize = 0;
224
225 for(File f : dir.listFiles()) {
226 if(isExpired(f))
227 f.delete();
228 else {
229 dirsize += f.length();
230 modtime.put(f.lastModified(), f);
231 }
232 }
233
234 if(dirsize < maxsize*1000*1000) return;
235
236 Set<Long> keySet = modtime.keySet();
237 Iterator<Long> it = keySet.iterator();
238 int i=0;
239 while (it.hasNext()) {
240 i++;
241 modtime.get(it.next()).delete();
242
243 // Delete a couple of files, then check again
244 if(i % cleanUpThreshold == 0 && getDirSize() < maxsize)
245 return;
246 }
247 writes = 0;
248 }
249
250 final static public int CLEAN_ALL = 0;
251 final static public int CLEAN_SMALL_FILES = 1;
252 final static public int CLEAN_BY_DATE = 2;
253 /**
254 * Performs a non-default, specified clean up
255 * @param type any of the CLEAN_XX constants.
256 * @param size for CLEAN_SMALL_FILES: deletes all files smaller than (size) bytes
257 */
258 public void customCleanUp(int type, int size) {
259 switch(type) {
260 case CLEAN_ALL:
261 for(File f : dir.listFiles())
262 f.delete();
263 break;
264 case CLEAN_SMALL_FILES:
265 for(File f: dir.listFiles())
266 if(f.length() < size)
267 f.delete();
268 break;
269 case CLEAN_BY_DATE:
270 cleanUp();
271 break;
272 }
273 }
274
275 /**
276 * Calculates the size of the directory
277 * @return long Size of directory in bytes
278 */
279 private long getDirSize() {
280 if(!enabled) return -1;
281 long dirsize = 0;
282
283 for(File f : this.dir.listFiles())
284 dirsize += f.length();
285 return dirsize;
286 }
287
288 /**
289 * Returns a short and unique file name for a given long identifier
290 * @return String short filename
291 */
292 private static String getUniqueFilename(String ident) {
293 try {
294 MessageDigest md = MessageDigest.getInstance("MD5");
295 BigInteger number = new BigInteger(1, md.digest(ident.getBytes()));
296 return number.toString(16);
297 } catch(Exception e) {
298 // Fall back. Remove unsuitable characters and some random ones to shrink down path length.
299 // Limit it to 70 characters, that leaves about 190 for the path on Windows/NTFS
300 ident = ident.replaceAll("[^a-zA-Z0-9]", "");
301 ident = ident.replaceAll("[acegikmoqsuwy]", "");
302 return ident.substring(ident.length() - 70);
303 }
304 }
305
306 /**
307 * Gets file path for ident with customizable file-ending
308 * @param ident
309 * @param ending
310 * @return File
311 */
312 private File getPath(String ident, String ending) {
313 return new File(dir, getUniqueFilename(ident) + "." + ending);
314 }
315
316 /**
317 * Gets file path for ident
318 * @param ident
319 * @param ending
320 * @return File
321 */
322 private File getPath(String ident) {
323 return new File(dir, getUniqueFilename(ident));
324 }
325
326 /**
327 * Checks whether a given file is expired
328 * @param file
329 * @return expired?
330 */
331 private boolean isExpired(File file) {
332 if(CacheFiles.EXPIRE_NEVER == this.expire)
333 return false;
334 return (file.lastModified() < (new Date().getTime() - expire*1000));
335 }
336}
Note: See TracBrowser for help on using the repository browser.