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

Last change on this file since 6070 was 6070, checked in by stoecker, 11 years ago

see #8853 remove tabs, trailing spaces, windows line ends, strange characters

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