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

Last change on this file since 3965 was 3083, checked in by bastiK, 14 years ago

added svn:eol-style=native to source files

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