Ticket #11869: cache_files_to_jcs.patch

File cache_files_to_jcs.patch, 15.4 KB (added by wiktorn, 11 years ago)
  • src/org/openstreetmap/josm/gui/layer/geoimage/ThumbsLoader.java

    diff --git src/org/openstreetmap/josm/gui/layer/geoimage/ThumbsLoader.java src/org/openstreetmap/josm/gui/layer/geoimage/ThumbsLoader.java
    index eeffd3b..c2debc8 100644
    import java.awt.Rectangle;  
    88import java.awt.Toolkit;
    99import java.awt.geom.AffineTransform;
    1010import java.awt.image.BufferedImage;
     11import java.io.ByteArrayOutputStream;
     12import java.io.File;
     13import java.io.IOException;
    1114import java.util.ArrayList;
    1215import java.util.List;
    1316
     17import javax.imageio.ImageIO;
     18
     19import org.apache.commons.jcs.access.behavior.ICacheAccess;
    1420import org.openstreetmap.josm.Main;
    15 import org.openstreetmap.josm.io.CacheFiles;
     21import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry;
     22import org.openstreetmap.josm.data.cache.JCSCacheManager;
    1623import org.openstreetmap.josm.tools.ExifReader;
    1724
    1825public class ThumbsLoader implements Runnable {
    public class ThumbsLoader implements Runnable {  
    2229    private List<ImageEntry> data;
    2330    private GeoImageLayer layer;
    2431    private MediaTracker tracker;
    25     private CacheFiles cache;
     32    private ICacheAccess<String , BufferedImageCacheEntry> cache;
    2633    private boolean cacheOff = Main.pref.getBoolean("geoimage.noThumbnailCache", false);
    2734
    2835    public ThumbsLoader(GeoImageLayer layer) {
    2936        this.layer = layer;
    3037        this.data = new ArrayList<>(layer.data);
    3138        if (!cacheOff) {
    32             cache = new CacheFiles("geoimage-thumbnails", false);
    33             cache.setExpire(CacheFiles.EXPIRE_NEVER, false);
    34             cache.setMaxSize(120, false);
     39            try {
     40                cache = JCSCacheManager.getCache("geoimage-thumbnails", 0, 120, Main.pref.getCacheDirectory().getPath() + File.separator + "geoimage-thumbnails");
     41            } catch (IOException e) {
     42                Main.warn(e);
     43            }
    3544        }
    3645    }
    3746
    public class ThumbsLoader implements Runnable {  
    6170        final String cacheIdent = entry.getFile()+":"+maxSize;
    6271
    6372        if (!cacheOff) {
    64             BufferedImage cached = cache.getImg(cacheIdent);
    65             if (cached != null) {
    66                 Main.debug(" from cache");
    67                 return cached;
     73            try {
     74                BufferedImageCacheEntry cacheEntry = cache.get(cacheIdent);
     75                if (cacheEntry != null && cacheEntry.getImage() != null) {
     76                    Main.debug(" from cache");
     77                    return cacheEntry.getImage();
     78                }
     79            } catch (IOException e) {
     80                Main.warn(e);
    6881            }
    6982        }
    7083
    public class ThumbsLoader implements Runnable {  
    118131        }
    119132
    120133        if (!cacheOff) {
    121             cache.saveImg(cacheIdent, scaledBI);
     134            try {
     135                ByteArrayOutputStream output = new ByteArrayOutputStream();
     136                ImageIO.write(scaledBI,"png", output);
     137                cache.put(cacheIdent, new BufferedImageCacheEntry(output.toByteArray()));
     138            } catch (IOException e) {
     139                Main.warn(e);
     140            }
    122141        }
    123142
    124143        return scaledBI;
  • deleted file src/org/openstreetmap/josm/io/CacheFiles.java

    diff --git src/org/openstreetmap/josm/io/CacheFiles.java src/org/openstreetmap/josm/io/CacheFiles.java
    deleted file mode 100644
    index d60a7ca..0000000
    + -  
    1 // License: GPL. For details, see LICENSE file.
    2 package org.openstreetmap.josm.io;
    3 
    4 import java.awt.image.BufferedImage;
    5 import java.io.File;
    6 import java.io.RandomAccessFile;
    7 import java.math.BigInteger;
    8 import java.nio.charset.StandardCharsets;
    9 import java.security.MessageDigest;
    10 import java.util.Iterator;
    11 import java.util.Set;
    12 import java.util.SortedMap;
    13 import java.util.TreeMap;
    14 
    15 import javax.imageio.ImageIO;
    16 
    17 import org.openstreetmap.josm.Main;
    18 import org.openstreetmap.josm.tools.ImageProvider;
    19 
    20 /**
    21  * Use this class if you want to cache a lot of files that shouldn't be kept in memory. You can
    22  * specify how much data should be stored and after which date the files should be expired.
    23  * This works on a last-access basis, so files get deleted after they haven't been used for x days.
    24  * You can turn this off by calling setUpdateModTime(false). Files get deleted on a first-in-first-out
    25  * basis.
    26  * @author xeen
    27  *
    28  */
    29 public class CacheFiles {
    30     /**
    31      * Common expirey dates
    32      */
    33     public static final int EXPIRE_NEVER = -1;
    34     public static final int EXPIRE_DAILY = 60 * 60 * 24;
    35     public static final int EXPIRE_WEEKLY = EXPIRE_DAILY * 7;
    36     public static final int EXPIRE_MONTHLY = EXPIRE_WEEKLY * 4;
    37 
    38     private final File dir;
    39     private final String ident;
    40     private final boolean enabled;
    41 
    42     private long expire;  // in seconds
    43     private long maxsize; // in megabytes
    44     private boolean updateModTime = true;
    45 
    46     // If the cache is full, we don't want to delete just one file
    47     private static final int CLEANUP_TRESHOLD = 20;
    48     // We don't want to clean after every file-write
    49     private static final int CLEANUP_INTERVAL = 5;
    50     // Stores how many files have been written
    51     private int writes = 0;
    52 
    53     /**
    54      * Creates a new cache class. The ident will be used to store the files on disk and to save
    55      * expire/space settings. Set plugin state to <code>true</code>.
    56      * @param ident cache identifier
    57      */
    58     public CacheFiles(String ident) {
    59         this(ident, true);
    60     }
    61 
    62     /**
    63      * Creates a new cache class. The ident will be used to store the files on disk and to save
    64      * expire/space settings.
    65      * @param ident cache identifier
    66      * @param isPlugin Whether this is a plugin or not (changes cache path)
    67      */
    68     public CacheFiles(String ident, boolean isPlugin) {
    69         String pref = isPlugin ?
    70                 Main.pref.getPluginsDirectory().getPath() + File.separator + "cache" :
    71                 Main.pref.getCacheDirectory().getPath();
    72 
    73         boolean dir_writeable;
    74         this.ident = ident;
    75         String cacheDir = Main.pref.get("cache." + ident + "." + "path", pref + File.separator + ident + File.separator);
    76         this.dir = new File(cacheDir);
    77         try {
    78             this.dir.mkdirs();
    79             dir_writeable = true;
    80         } catch (Exception e) {
    81             // We have no access to this directory, so don't do anything
    82             dir_writeable = false;
    83         }
    84         this.enabled = dir_writeable;
    85         this.expire = Main.pref.getLong("cache." + ident + "." + "expire", EXPIRE_DAILY);
    86         if (this.expire < 0) {
    87             this.expire = CacheFiles.EXPIRE_NEVER;
    88         }
    89         this.maxsize = Main.pref.getLong("cache." + ident + "." + "maxsize", 50);
    90         if (this.maxsize < 0) {
    91             this.maxsize = -1;
    92         }
    93     }
    94 
    95     /**
    96      * Loads the data for the given ident as an byte array. Returns null if data not available.
    97      * @param ident cache identifier
    98      * @return stored data
    99      */
    100     public byte[] getData(String ident) {
    101         if (!enabled) return null;
    102         try {
    103             File data = getPath(ident);
    104             if (!data.exists())
    105                 return null;
    106 
    107             if (isExpired(data)) {
    108                 data.delete();
    109                 return null;
    110             }
    111 
    112             // Update last mod time so we don't expire recently used data
    113             if (updateModTime) {
    114                 data.setLastModified(System.currentTimeMillis());
    115             }
    116 
    117             byte[] bytes = new byte[(int) data.length()];
    118             try (RandomAccessFile raf = new RandomAccessFile(data, "r")) {
    119                 raf.readFully(bytes);
    120             }
    121             return bytes;
    122         } catch (Exception e) {
    123             Main.warn(e);
    124         }
    125         return null;
    126     }
    127 
    128     /**
    129      * Writes an byte-array to disk
    130      * @param ident cache identifier
    131      * @param data data to store
    132      */
    133     public void saveData(String ident, byte[] data) {
    134         if (!enabled) return;
    135         try {
    136             File f = getPath(ident);
    137             if (f.exists()) {
    138                 f.delete();
    139             }
    140             // rws also updates the file meta-data, i.e. last mod time
    141             try (RandomAccessFile raf = new RandomAccessFile(f, "rws")) {
    142                 raf.write(data);
    143             }
    144         } catch (Exception e) {
    145             Main.warn(e);
    146         }
    147 
    148         writes++;
    149         checkCleanUp();
    150     }
    151 
    152     /**
    153      * Loads the data for the given ident as an image. If no image is found, null is returned
    154      * @param ident cache identifier
    155      * @return BufferedImage or null
    156      */
    157     public BufferedImage getImg(String ident) {
    158         if (!enabled) return null;
    159         try {
    160             File img = getPath(ident, "png");
    161             if (!img.exists())
    162                 return null;
    163 
    164             if (isExpired(img)) {
    165                 img.delete();
    166                 return null;
    167             }
    168             // Update last mod time so we don't expire recently used images
    169             if (updateModTime) {
    170                 img.setLastModified(System.currentTimeMillis());
    171             }
    172             return ImageProvider.read(img, false, false);
    173         } catch (Exception e) {
    174             Main.warn(e);
    175         }
    176         return null;
    177     }
    178 
    179     /**
    180      * Saves a given image and ident to the cache
    181      * @param ident cache identifier
    182      * @param image imaga data for storage
    183      */
    184     public void saveImg(String ident, BufferedImage image) {
    185         if (!enabled) return;
    186         try {
    187             ImageIO.write(image, "png", getPath(ident, "png"));
    188         } catch (Exception e) {
    189             Main.warn(e);
    190         }
    191 
    192         writes++;
    193         checkCleanUp();
    194     }
    195 
    196     /**
    197      * Sets the amount of time data is stored before it gets expired
    198      * @param amount of time in seconds
    199      * @param force will also write it to the preferences
    200      */
    201     public void setExpire(int amount, boolean force) {
    202         String key = "cache." + ident + "." + "expire";
    203         if (!Main.pref.get(key).isEmpty() && !force)
    204             return;
    205 
    206         this.expire = amount > 0 ? amount : EXPIRE_NEVER;
    207         Main.pref.putLong(key, this.expire);
    208     }
    209 
    210     /**
    211      * Sets the amount of data stored in the cache
    212      * @param amount in Megabytes
    213      * @param force will also write it to the preferences
    214      */
    215     public void setMaxSize(int amount, boolean force) {
    216         String key = "cache." + ident + "." + "maxsize";
    217         if (!Main.pref.get(key).isEmpty() && !force)
    218             return;
    219 
    220         this.maxsize = amount > 0 ? amount : -1;
    221         Main.pref.putLong(key, this.maxsize);
    222     }
    223 
    224     /**
    225      * Call this with <code>true</code> to update the last modification time when a file it is read.
    226      * Call this with <code>false</code> to not update the last modification time when a file is read.
    227      * @param to update state
    228      */
    229     public void setUpdateModTime(boolean to) {
    230         updateModTime = to;
    231     }
    232 
    233     /**
    234      * Checks if a clean up is needed and will do so if necessary
    235      */
    236     public void checkCleanUp() {
    237         if (this.writes > CLEANUP_INTERVAL) {
    238             cleanUp();
    239         }
    240     }
    241 
    242     /**
    243      * Performs a default clean up with the set values (deletes oldest files first)
    244      */
    245     public void cleanUp() {
    246         if (!this.enabled || maxsize == -1) return;
    247 
    248         SortedMap<Long, File> modtime = new TreeMap<>();
    249         long dirsize = 0;
    250 
    251         File[] files = dir.listFiles();
    252         if (files != null) {
    253             for (File f : files) {
    254                 if (isExpired(f)) {
    255                     f.delete();
    256                 } else {
    257                     dirsize += f.length();
    258                     modtime.put(f.lastModified(), f);
    259                 }
    260             }
    261         }
    262 
    263         if (dirsize < maxsize*1000*1000) return;
    264 
    265         Set<Long> keySet = modtime.keySet();
    266         Iterator<Long> it = keySet.iterator();
    267         int i = 0;
    268         while (it.hasNext()) {
    269             i++;
    270             modtime.get(it.next()).delete();
    271 
    272             // Delete a couple of files, then check again
    273             if (i % CLEANUP_TRESHOLD == 0 && getDirSize() < maxsize)
    274                 return;
    275         }
    276         writes = 0;
    277     }
    278 
    279     public static final int CLEAN_ALL = 0;
    280     public static final int CLEAN_SMALL_FILES = 1;
    281     public static final int CLEAN_BY_DATE = 2;
    282 
    283     /**
    284      * Performs a non-default, specified clean up
    285      * @param type any of the CLEAN_XX constants.
    286      * @param size for CLEAN_SMALL_FILES: deletes all files smaller than (size) bytes
    287      */
    288     public void customCleanUp(int type, int size) {
    289         File[] files;
    290         switch(type) {
    291         case CLEAN_ALL:
    292             files = dir.listFiles();
    293             if (files != null) {
    294                 for (File f : files) {
    295                     f.delete();
    296                 }
    297             }
    298             break;
    299         case CLEAN_SMALL_FILES:
    300             files = dir.listFiles();
    301             if (files != null) {
    302                 for (File f: files) {
    303                     if (f.length() < size) {
    304                         f.delete();
    305                     }
    306                 }
    307             }
    308             break;
    309         case CLEAN_BY_DATE:
    310             cleanUp();
    311             break;
    312         }
    313     }
    314 
    315     /**
    316      * Calculates the size of the directory
    317      * @return long Size of directory in bytes
    318      */
    319     private long getDirSize() {
    320         if (!enabled) return -1;
    321         long dirsize = 0;
    322 
    323         File[] files = dir.listFiles();
    324         if (files != null) {
    325             for (File f : files) {
    326                 dirsize += f.length();
    327             }
    328         }
    329         return dirsize;
    330     }
    331 
    332     /**
    333      * Returns a short and unique file name for a given long identifier
    334      * @return String short filename
    335      */
    336     private static String getUniqueFilename(String ident) {
    337         try {
    338             MessageDigest md = MessageDigest.getInstance("MD5");
    339             BigInteger number = new BigInteger(1, md.digest(ident.getBytes(StandardCharsets.UTF_8)));
    340             return number.toString(16);
    341         } catch (Exception e) {
    342             // Fall back. Remove unsuitable characters and some random ones to shrink down path length.
    343             // Limit it to 70 characters, that leaves about 190 for the path on Windows/NTFS
    344             ident = ident.replaceAll("[^a-zA-Z0-9]", "");
    345             ident = ident.replaceAll("[acegikmoqsuwy]", "");
    346             return ident.substring(ident.length() - 70);
    347         }
    348     }
    349 
    350     /**
    351      * Gets file path for ident with customizable file-ending
    352      * @param ident cache identifier
    353      * @param ending file extension
    354      * @return file structure
    355      */
    356     private File getPath(String ident, String ending) {
    357         return new File(dir, getUniqueFilename(ident) + "." + ending);
    358     }
    359 
    360     /**
    361      * Gets file path for ident
    362      * @param ident cache identifier
    363      * @return file structure
    364      */
    365     private File getPath(String ident) {
    366         return new File(dir, getUniqueFilename(ident));
    367     }
    368 
    369     /**
    370      * Checks whether a given file is expired
    371      * @param file file description structure
    372      * @return expired state
    373      */
    374     private boolean isExpired(File file) {
    375         if (CacheFiles.EXPIRE_NEVER == this.expire)
    376             return false;
    377         return file.lastModified() < (System.currentTimeMillis() - expire*1000);
    378     }
    379 }