Ticket #2613: Add CacheFiles class.2.patch

File Add CacheFiles class.2.patch, 11.8 KB (added by xeen, 17 years ago)

Good catch. Implemented. Any value below 0 will do :)

  • src/org/openstreetmap/josm/io/CacheCustomContent.java

     
    1010
    1111import org.openstreetmap.josm.Main;
    1212
     13/**
     14 * Use this class if you want to cache and store a single file that gets updated regularly.
     15 * Unless you flush() it will be kept in memory. If you want to cache a lot of data and/or files,
     16 * use CacheFiles
     17 * @author xeen
     18 *
     19 */
    1320public abstract class CacheCustomContent {
    1421    /**
    1522     * Common intervals
  • src/org/openstreetmap/josm/io/CacheFiles.java

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