/* This file is part of Subsonic. Subsonic is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Subsonic is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Subsonic. If not, see <http://www.gnu.org/licenses/>. Copyright 2009 (C) Sindre Mehus */ package net.sourceforge.subsonic.dao; import com.db4o.Db4oEmbedded; import com.db4o.EmbeddedObjectContainer; import com.db4o.ObjectSet; import com.db4o.config.EmbeddedConfiguration; import com.db4o.query.Predicate; import net.sourceforge.subsonic.Logger; import net.sourceforge.subsonic.domain.CacheElement; import net.sourceforge.subsonic.service.SettingsService; import java.io.File; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * Provides database services for caching. * * @author Sindre Mehus */ public class CacheDao { private static final Logger LOG = Logger.getLogger(CacheDao.class); private static final int BATCH_SIZE = 100; private EmbeddedObjectContainer db; private final ReadWriteLock dbLock = new ReentrantReadWriteLock(); private int writeCount = 0; private final File dbFile; public CacheDao() { File subsonicHome = SettingsService.getSubsonicHome(); File dbDir = new File(subsonicHome, "cache"); dbFile = new File(dbDir, "cache.dat"); if (!dbDir.exists()) { dbDir.mkdirs(); } // if (dbFile.exists()) { // try { // Defragment.defrag(dbFile.getPath()); // }catch (IOException e) { // e.printStackTrace(); // } // } try { openDatabase(dbFile); } catch (Throwable x) { LOG.error("Failed to open " + dbFile + ", deleting it: " + x); dbFile.delete(); openDatabase(dbFile); } } private void openDatabase(File dbFile) { EmbeddedConfiguration config = Db4oEmbedded.newConfiguration(); config.common().objectClass(CacheElement.class).objectField("id").indexed(true); config.common().objectClass(CacheElement.class).cascadeOnUpdate(true); config.common().objectClass(CacheElement.class).cascadeOnDelete(true); config.common().objectClass(CacheElement.class).cascadeOnActivate(true); db = Db4oEmbedded.openFile(config, dbFile.getPath()); } /** * Recreates the database. */ public void clearDatabase() { dbLock.writeLock().lock(); try { db.close(); dbFile.delete(); openDatabase(dbFile); } finally{ dbLock.writeLock().unlock(); } } /** * Creates a new cache element. * * @param element The cache element to create (or update). */ public void createCacheElement(CacheElement element) { dbLock.writeLock().lock(); try{ deleteCacheElement(element); db.store(element); if (writeCount++ == BATCH_SIZE) { db.commit(); writeCount = 0; } } finally{ dbLock.writeLock().unlock(); } } public CacheElement getCacheElement(int type, String key) { dbLock.readLock().lock(); try{ ObjectSet<CacheElement> result = db.query(new CacheElementPredicate(type, key)); if (result.size() > 1) { LOG.error("Programming error. Got " + result.size() + " cache elements of type " + type + " and key " + key); } return result.isEmpty() ? null : result.get(0); } finally{ dbLock.readLock().unlock(); } } /** * Deletes the cache element with the given type and key. */ private void deleteCacheElement(CacheElement element) { // Retrieve it from the database first. element = getCacheElement(element.getType(), element.getKey()); if (element != null) { db.delete(element); } } private static class CacheElementPredicate extends Predicate<CacheElement> { private static final long serialVersionUID = 54911003002373726L; private final long id; public CacheElementPredicate(int type, String key) { id = CacheElement.createId(type, key); } @Override public boolean match(CacheElement candidate) { return candidate.getId() == id; } } }