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

Last change on this file since 5299 was 4932, checked in by stoecker, 12 years ago

fix 6833 - use WindowGeometry for toggle dialogs and mainwindow replacing old custom methods, improve dual screen handling

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