Merge commit '087a6e8e7f1b0e63633831948e99ae110b92ae45'
[fanfix.git] / src / be / nikiroo / utils / Cache.java
index dcbde74ea4b053ae512c7ed9149114af5f4aa77e..6233082742606f72538fb5ccb2a3ffd4c64e9136 100644 (file)
@@ -1,13 +1,14 @@
 package be.nikiroo.utils;
 
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
 import java.util.Date;
 
+import be.nikiroo.utils.streams.MarkableFileInputStream;
+
 /**
  * A generic cache system, with special support for {@link URL}s.
  * <p>
@@ -21,6 +22,12 @@ public class Cache {
        private long tooOldStable;
        private TraceHandler tracer = new TraceHandler();
 
+       /**
+        * Only for inheritance.
+        */
+       protected Cache() {
+       }
+
        /**
         * Create a new {@link Cache} object.
         * 
@@ -41,8 +48,8 @@ public class Cache {
        public Cache(File dir, int hoursChanging, int hoursStable)
                        throws IOException {
                this.dir = dir;
-               this.tooOldChanging = 1000 * 60 * 60 * hoursChanging;
-               this.tooOldStable = 1000 * 60 * 60 * hoursStable;
+               this.tooOldChanging = 1000L * 60 * 60 * hoursChanging;
+               this.tooOldStable = 1000L * 60 * 60 * hoursStable;
 
                if (dir != null && !dir.exists()) {
                        dir.mkdirs();
@@ -70,9 +77,31 @@ public class Cache {
         *            the new traces handler
         */
        public void setTraceHandler(TraceHandler tracer) {
+               if (tracer == null) {
+                       tracer = new TraceHandler(false, false, false);
+               }
+
                this.tracer = tracer;
        }
 
+       /**
+        * Check the resource to see if it is in the cache.
+        * 
+        * @param uniqueID
+        *            the resource to check
+        * @param allowTooOld
+        *            allow files even if they are considered too old
+        * @param stable
+        *            a stable file (that dones't change too often) -- parameter
+        *            used to check if the file is too old to keep or not
+        * 
+        * @return TRUE if it is
+        * 
+        */
+       public boolean check(String uniqueID, boolean allowTooOld, boolean stable) {
+               return check(getCached(uniqueID), allowTooOld, stable);
+       }
+
        /**
         * Check the resource to see if it is in the cache.
         * 
@@ -88,9 +117,31 @@ public class Cache {
         * 
         */
        public boolean check(URL url, boolean allowTooOld, boolean stable) {
-               File file = getCached(url);
-               if (file.exists()) {
-                       if (allowTooOld || !isOld(file, stable)) {
+               return check(getCached(url), allowTooOld, stable);
+       }
+
+       /**
+        * Check the resource to see if it is in the cache.
+        * 
+        * @param cached
+        *            the resource to check
+        * @param allowTooOld
+        *            allow files even if they are considered too old
+        * @param stable
+        *            a stable file (that dones't change too often) -- parameter
+        *            used to check if the file is too old to keep or not
+        * 
+        * @return TRUE if it is
+        * 
+        */
+       private boolean check(File cached, boolean allowTooOld, boolean stable) {
+               if (cached.exists() && cached.isFile()) {
+                       if (!allowTooOld && isOld(cached, stable)) {
+                               if (!cached.delete()) {
+                                       tracer.error("Cannot delete temporary file: "
+                                                       + cached.getAbsolutePath());
+                               }
+                       } else {
                                return true;
                        }
                }
@@ -108,7 +159,16 @@ public class Cache {
         * @return the number of cleaned items
         */
        public int clean(boolean onlyOld) {
-               return clean(onlyOld, dir);
+               long ms = System.currentTimeMillis();
+
+               tracer.trace("Cleaning cache from old files...");
+
+               int num = clean(onlyOld, dir, -1);
+
+               tracer.trace(num + "cache items cleaned in "
+                               + (System.currentTimeMillis() - ms) + " ms");
+
+               return num;
        }
 
        /**
@@ -119,21 +179,31 @@ public class Cache {
         *            resources
         * @param cacheDir
         *            the cache directory to clean
+        * @param limit
+        *            stop after limit files deleted, or -1 for unlimited
         * 
         * @return the number of cleaned items
         */
-       private int clean(boolean onlyOld, File cacheDir) {
+       private int clean(boolean onlyOld, File cacheDir, int limit) {
                int num = 0;
-               for (File file : cacheDir.listFiles()) {
-                       if (file.isDirectory()) {
-                               num += clean(onlyOld, file);
-                       } else {
-                               if (!onlyOld || isOld(file, true)) {
-                                       if (file.delete()) {
-                                               num++;
-                                       } else {
-                                               tracer.error("Cannot delete temporary file: "
-                                                               + file.getAbsolutePath());
+               File[] files = cacheDir.listFiles();
+               if (files != null) {
+                       for (File file : files) {
+                               if (limit >= 0 && num >= limit) {
+                                       return num;
+                               }
+
+                               if (file.isDirectory()) {
+                                       num += clean(onlyOld, file, limit);
+                                       file.delete(); // only if empty
+                               } else {
+                                       if (!onlyOld || isOld(file, true)) {
+                                               if (file.delete()) {
+                                                       num++;
+                                               } else {
+                                                       tracer.error("Cannot delete temporary file: "
+                                                                       + file.getAbsolutePath());
+                                               }
                                        }
                                }
                        }
@@ -167,8 +237,9 @@ public class Cache {
         * @param allowTooOld
         *            allow files even if they are considered too old
         * @param stable
-        *            a stable file (that dones't change too often) -- parameter
-        *            used to check if the file is too old to keep or not
+        *            a stable file (that doesn't change too often) -- parameter
+        *            used to check if the file is too old to keep or not in the
+        *            cache
         * 
         * @return the opened resource if found, NULL if not
         */
@@ -190,9 +261,10 @@ public class Cache {
         * @return the opened resource if found, NULL if not
         */
        private InputStream load(File cached, boolean allowTooOld, boolean stable) {
-               if (cached.exists() && (allowTooOld || !isOld(cached, stable))) {
+               if (cached.exists() && cached.isFile()
+                               && (allowTooOld || !isOld(cached, stable))) {
                        try {
-                               return new MarkableFileInputStream(new FileInputStream(cached));
+                               return new MarkableFileInputStream(cached);
                        } catch (FileNotFoundException e) {
                                return null;
                        }
@@ -209,12 +281,12 @@ public class Cache {
         * @param uniqueID
         *            a unique ID used to locate the cached resource
         * 
-        * @return the resulting {@link File}
+        * @return the number of bytes written
         * 
         * @throws IOException
         *             in case of I/O error
         */
-       public File save(InputStream in, String uniqueID) throws IOException {
+       public long save(InputStream in, String uniqueID) throws IOException {
                File cached = getCached(uniqueID);
                cached.getParentFile().mkdirs();
                return save(in, cached);
@@ -228,32 +300,39 @@ public class Cache {
         * @param url
         *            the {@link URL} used to locate the cached resource
         * 
-        * @return the actual cache file
+        * @return the number of bytes written
         * 
         * @throws IOException
         *             in case of I/O error
         */
-       public File save(InputStream in, URL url) throws IOException {
+       public long save(InputStream in, URL url) throws IOException {
                File cached = getCached(url);
                return save(in, cached);
        }
 
        /**
         * Save the given resource to the cache.
+        * <p>
+        * Will also clean the {@link Cache} from old files.
         * 
         * @param in
         *            the input data
         * @param cached
         *            the cached {@link File} to save to
         * 
-        * @return the actual cache file
+        * @return the number of bytes written
         * 
         * @throws IOException
         *             in case of I/O error
         */
-       private File save(InputStream in, File cached) throws IOException {
-               IOUtils.write(in, cached);
-               return cached;
+       private long save(InputStream in, File cached) throws IOException {
+               // We want to force at least an immediate SAVE/LOAD to work for some
+               // workflows, even if we don't accept cached files (times set to "0"
+               // -- and not "-1" or a positive value)
+               clean(true, dir, 10);
+               cached.getParentFile().mkdirs(); // in case we deleted our own parent
+               long bytes = IOUtils.write(in, cached);
+               return bytes;
        }
 
        /**
@@ -328,7 +407,11 @@ public class Cache {
                if (name == null || name.isEmpty()) {
                        // File
                        File file = new File(url.getFile());
-                       subdir = new File(file.getParent().replace("..", "__"));
+                       if (file.getParent() == null) {
+                               subdir = new File("+");
+                       } else {
+                               subdir = new File(file.getParent().replace("..", "__"));
+                       }
                        subdir = new File(dir, allowedChars(subdir.getPath()));
                        name = allowedChars(url.getFile());
                } else {