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

Revision 4932, 10.5 KB checked in by stoecker, 3 months ago (diff)

fix 6833 - use WindowGeometry for toggle dialogs and mainwindow replacing old custom methods, improve dual screen handling

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