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

Last change on this file since 8337 was 8308, checked in by Don-vip, 9 years ago

fix potential NPEs and Sonar issues related to serialization

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