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

Last change on this file since 2512 was 2512, checked in by stoecker, 14 years ago

i18n updated, fixed files to reduce problems when applying patches, fix #4017

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