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

Last change on this file since 1728 was 1705, checked in by stoecker, 15 years ago

fix #2706 - patch by bilbo - allow to set WMS cache path again

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