package net.filebot; import static java.nio.charset.StandardCharsets.*; import static net.filebot.Logging.*; import static net.filebot.Settings.*; import static net.filebot.util.FileUtilities.*; import java.io.File; import java.io.IOException; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.file.StandardOpenOption; import java.util.Scanner; import java.util.logging.Level; import net.sf.ehcache.CacheException; import net.sf.ehcache.config.Configuration; import net.sf.ehcache.config.DiskStoreConfiguration; public class CacheManager { private static final CacheManager instance = new CacheManager(); public static CacheManager getInstance() { return instance; } private final File diskStore; private final net.sf.ehcache.CacheManager manager; public CacheManager() { try { this.diskStore = acquireDiskStore(); this.manager = net.sf.ehcache.CacheManager.create(new Configuration().diskStore(new DiskStoreConfiguration().path(diskStore.getPath()))); } catch (IOException e) { throw new CacheException(e); } } public synchronized Cache getCache(String name, CacheType type) { if (!manager.cacheExists(name)) { manager.addCache(new net.sf.ehcache.Cache(type.getConfiguration(name))); } return new Cache(manager.getCache(name), type); } public synchronized void clearAll() { manager.clearAll(); // clear all caches that have not been added yet clearDiskStore(diskStore); } public synchronized void shutdown() { manager.shutdown(); } private void clearDiskStore(File cache) { getChildren(cache).stream().filter(f -> f.isFile() && !f.getName().startsWith(".")).forEach(f -> { try { delete(f); } catch (Exception e) { debug.warning(format("Failed to delete cache: %s => %s", f, e)); } }); } private File acquireDiskStore() throws IOException { // prepare cache folder for this application instance File cacheRoot = ApplicationFolder.Cache.getCanonicalFile(); for (int i = 0; i < 10; i++) { File cache = new File(cacheRoot, Integer.toString(i)); // make sure cache is readable and writable createFolders(cache); final File lockFile = new File(cache, ".lock"); boolean isNewCache = !lockFile.exists(); final FileChannel channel = FileChannel.open(lockFile.toPath(), StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); final FileLock lock = channel.tryLock(); if (lock != null) { debug.config(format("Using persistent disk cache %s", cache)); int applicationRevision = getApplicationRevisionNumber(); int cacheRevision = 0; try { cacheRevision = new Scanner(channel, "UTF-8").nextInt(); } catch (Exception e) { // ignore } if (cacheRevision != applicationRevision && applicationRevision > 0 && !isNewCache) { debug.config(format("Current application version (r%d) does not match cache version (r%d): reset cache", applicationRevision, cacheRevision)); // tag cache with new revision number isNewCache = true; // delete all files related to previous cache instances clearDiskStore(cache); } if (isNewCache) { // set new cache revision channel.position(0); channel.write(UTF_8.encode(String.valueOf(applicationRevision))); channel.truncate(channel.position()); } // make sure to orderly shutdown cache Runtime.getRuntime().addShutdownHook(new ShutdownHook(this, channel, lock)); // cache for this application instance is successfully set up and locked return cache; } // try next lock file channel.close(); } // serious error, abort throw new IOException("Unable to acquire cache lock: " + cacheRoot); } private static class ShutdownHook extends Thread { private final CacheManager manager; private final FileChannel channel; private final FileLock lock; public ShutdownHook(CacheManager manager, FileChannel channel, FileLock lock) { this.manager = manager; this.channel = channel; this.lock = lock; } @Override public void run() { try { manager.shutdown(); } catch (Exception e) { debug.log(Level.WARNING, "Shutdown hook failed: shutdown", e); } try { lock.release(); } catch (Exception e) { debug.log(Level.WARNING, "Shutdown hook failed: release", e); } try { channel.close(); } catch (Exception e) { debug.log(Level.WARNING, "Shutdown hook failed: close", e); } } } }