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

Last change on this file since 2474 was 2016, checked in by stoecker, 15 years ago

fixed #3289 - patch by xeen - cache.wmsplugin.expire and cache.wmsplugin.maxsize are not honored

File size: 10.2 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 String cacheDir = Main.pref.get("cache." + ident + "." + "path", pref + "/" + ident + "/cache/");
61 this.dir = new File(cacheDir);
62 try {
63 this.dir.mkdirs();
64 dir_writeable = true;
65 } catch(Exception e) {
66 // We have no access to this directory, so don't do anything
67 dir_writeable = false;
68 }
69 this.enabled = dir_writeable;
70 this.expire = Main.pref.getLong("cache." + ident + "." + "expire", EXPIRE_DAILY);
71 if(this.expire < 0) {
72 this.expire = CacheFiles.EXPIRE_NEVER;
73 }
74 this.maxsize = Main.pref.getLong("cache." + ident + "." + "maxsize", 50);
75 if(this.maxsize < 0) {
76 this.maxsize = -1;
77 }
78 }
79
80 /**
81 * Loads the data for the given ident as an byte array. Returns null if data not available.
82 * @param ident
83 * @return
84 */
85 public byte[] getData(String ident) {
86 if(!enabled) return null;
87 try {
88 File data = getPath(ident);
89 if(!data.exists())
90 return null;
91
92 if(isExpired(data)) {
93 data.delete();
94 return null;
95 }
96
97 // Update last mod time so we don't expire recently used data
98 if(updateModTime) {
99 data.setLastModified(new Date().getTime());
100 }
101
102 byte[] bytes = new byte[(int) data.length()];
103 new RandomAccessFile(data, "r").readFully(bytes);
104 return bytes;
105 } catch(Exception e) {
106 System.out.println(e.getMessage());
107 }
108 return null;
109 }
110
111 /**
112 * Writes an byte-array to disk
113 * @param ident
114 * @param data
115 */
116 public void saveData(String ident, byte[] data) {
117 if(!enabled) return;
118 try {
119 File f = getPath(ident);
120 if(f.exists()) {
121 f.delete();
122 }
123 // rws also updates the file meta-data, i.e. last mod time
124 new RandomAccessFile(f, "rws").write(data);
125 } catch(Exception e){
126 System.out.println(e.getMessage());
127 }
128
129 writes++;
130 checkCleanUp();
131 }
132
133 /**
134 * Loads the data for the given ident as an image. If no image is found, null is returned
135 * @param ident Identifier
136 * @return BufferedImage or null
137 */
138 public BufferedImage getImg(String ident) {
139 if(!enabled) return null;
140 try {
141 File img = getPath(ident, "png");
142 if(!img.exists())
143 return null;
144
145 if(isExpired(img)) {
146 img.delete();
147 return null;
148 }
149 // Update last mod time so we don't expire recently used images
150 if(updateModTime) {
151 img.setLastModified(new Date().getTime());
152 }
153 return ImageIO.read(img);
154 } catch(Exception e) {
155 System.out.println(e.getMessage());
156 }
157 return null;
158 }
159
160 /**
161 * Saves a given image and ident to the cache
162 * @param ident
163 * @param image
164 */
165 public void saveImg(String ident, BufferedImage image) {
166 if(!enabled) return;
167 try {
168 ImageIO.write(image, "png", getPath(ident, "png"));
169 } catch(Exception e){
170 System.out.println(e.getMessage());
171 }
172
173 writes++;
174 checkCleanUp();
175 }
176
177
178 /**
179 * Sets the amount of time data is stored before it gets expired
180 * @param amount of time in seconds
181 * @param force will also write it to the preferences
182 */
183 public void setExpire(int amount, boolean force) {
184 String key = "cache." + ident + "." + "expire";
185 if(Main.pref.hasKey(key) && !force)
186 return;
187
188 this.expire = amount > 0 ? amount : EXPIRE_NEVER;
189 Main.pref.putLong(key, this.expire);
190 }
191
192 /**
193 * Sets the amount of data stored in the cache
194 * @param amount in Megabytes
195 * @param force will also write it to the preferences
196 */
197 public void setMaxSize(int amount, boolean force) {
198 String key = "cache." + ident + "." + "maxsize";
199 if(Main.pref.hasKey(key) && !force)
200 return;
201
202 this.maxsize = amount > 0 ? amount : -1;
203 Main.pref.putLong(key, this.maxsize);
204 }
205
206 /**
207 * Call this with true to update the last modification time when a file it is read.
208 * Call this with false to not update the last modification time when a file is read.
209 * @param to
210 */
211 public void setUpdateModTime(boolean to) {
212 updateModTime = to;
213 }
214
215 /**
216 * Checks if a clean up is needed and will do so if necessary
217 */
218 public void checkCleanUp() {
219 if(this.writes > this.cleanUpInterval) {
220 cleanUp();
221 }
222 }
223
224 /**
225 * Performs a default clean up with the set values (deletes oldest files first)
226 */
227 public void cleanUp() {
228 if(!this.enabled || maxsize == -1) return;
229
230 TreeMap<Long, File> modtime = new TreeMap<Long, File>();
231 long dirsize = 0;
232
233 for(File f : dir.listFiles()) {
234 if(isExpired(f)) {
235 f.delete();
236 } else {
237 dirsize += f.length();
238 modtime.put(f.lastModified(), f);
239 }
240 }
241
242 if(dirsize < maxsize*1000*1000) return;
243
244 Set<Long> keySet = modtime.keySet();
245 Iterator<Long> it = keySet.iterator();
246 int i=0;
247 while (it.hasNext()) {
248 i++;
249 modtime.get(it.next()).delete();
250
251 // Delete a couple of files, then check again
252 if(i % cleanUpThreshold == 0 && getDirSize() < maxsize)
253 return;
254 }
255 writes = 0;
256 }
257
258 final static public int CLEAN_ALL = 0;
259 final static public int CLEAN_SMALL_FILES = 1;
260 final static public int CLEAN_BY_DATE = 2;
261 /**
262 * Performs a non-default, specified clean up
263 * @param type any of the CLEAN_XX constants.
264 * @param size for CLEAN_SMALL_FILES: deletes all files smaller than (size) bytes
265 */
266 public void customCleanUp(int type, int size) {
267 switch(type) {
268 case CLEAN_ALL:
269 for(File f : dir.listFiles()) {
270 f.delete();
271 }
272 break;
273 case CLEAN_SMALL_FILES:
274 for(File f: dir.listFiles())
275 if(f.length() < size) {
276 f.delete();
277 }
278 break;
279 case CLEAN_BY_DATE:
280 cleanUp();
281 break;
282 }
283 }
284
285 /**
286 * Calculates the size of the directory
287 * @return long Size of directory in bytes
288 */
289 private long getDirSize() {
290 if(!enabled) return -1;
291 long dirsize = 0;
292
293 for(File f : this.dir.listFiles()) {
294 dirsize += f.length();
295 }
296 return dirsize;
297 }
298
299 /**
300 * Returns a short and unique file name for a given long identifier
301 * @return String short filename
302 */
303 private static String getUniqueFilename(String ident) {
304 try {
305 MessageDigest md = MessageDigest.getInstance("MD5");
306 BigInteger number = new BigInteger(1, md.digest(ident.getBytes()));
307 return number.toString(16);
308 } catch(Exception e) {
309 // Fall back. Remove unsuitable characters and some random ones to shrink down path length.
310 // Limit it to 70 characters, that leaves about 190 for the path on Windows/NTFS
311 ident = ident.replaceAll("[^a-zA-Z0-9]", "");
312 ident = ident.replaceAll("[acegikmoqsuwy]", "");
313 return ident.substring(ident.length() - 70);
314 }
315 }
316
317 /**
318 * Gets file path for ident with customizable file-ending
319 * @param ident
320 * @param ending
321 * @return File
322 */
323 private File getPath(String ident, String ending) {
324 return new File(dir, getUniqueFilename(ident) + "." + ending);
325 }
326
327 /**
328 * Gets file path for ident
329 * @param ident
330 * @param ending
331 * @return File
332 */
333 private File getPath(String ident) {
334 return new File(dir, getUniqueFilename(ident));
335 }
336
337 /**
338 * Checks whether a given file is expired
339 * @param file
340 * @return expired?
341 */
342 private boolean isExpired(File file) {
343 if(CacheFiles.EXPIRE_NEVER == this.expire)
344 return false;
345 return (file.lastModified() < (new Date().getTime() - expire*1000));
346 }
347}
Note: See TracBrowser for help on using the repository browser.