Ticket #2613: Add CacheFiles class.patch

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