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

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

checkstyle: enable relevant whitespace checks and fix them

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