package com.limegroup.gnutella.licenses; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.IOException; import java.util.Iterator; import java.util.Map; import java.util.HashMap; import org.apache.commons.httpclient.URI; import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.Log; import com.limegroup.gnutella.URN; import com.limegroup.gnutella.ErrorService; import com.limegroup.gnutella.util.CommonUtils; /** * A repository of licenses. */ class LicenseCache { private static final Log LOG = LogFactory.getLog(LicenseCache.class); /** * The amount of time to keep a license in the cache. */ private static final long EXPIRY_TIME = 7 * 24 * 60 * 60 * 1000; // one week. /** * File where the licenses are serialized. */ private final File CACHE_FILE = new File(CommonUtils.getUserSettingsDir(), "licenses.cache"); /** * A map of Licenses. One License per URI. */ private Map /* URI -> License */ licenses; /** * An extra map of data that Licenses can use * to cache info. This information lasts forever. */ private Map /* Object -> Object */ data; /** * Whether or not data is dirty since the last time we wrote to disk. */ private boolean dirty = false; private static final LicenseCache INSTANCE = new LicenseCache(); private LicenseCache() { deserialize(); } public static LicenseCache instance() { return INSTANCE; } /** * Adds a verified license. */ synchronized void addVerifiedLicense(License license) { licenses.put(license.getLicenseURI(), license); dirty = true; } /** * Adds data. */ synchronized void addData(Object key, Object value) { data.put(key, value); dirty = true; } /** * Retrieves the cached license for the specified URI, substituting * the license string for a new one. */ synchronized License getLicense(String licenseString, URI licenseURI) { License license = (License)licenses.get(licenseURI); if(license != null) return license.copy(licenseString, licenseURI); else return null; } /** * Gets details. */ synchronized Object getData(Object key) { return data.get(key); } /** * Determines if the license is verified for the given URN and URI. */ synchronized boolean isVerifiedAndValid(URN urn, URI uri) { License license = (License)licenses.get(uri); return license != null && license.isValid(urn); } /** * Loads values from cache file, if available */ private void deserialize() { ObjectInputStream ois = null; try { ois = new ObjectInputStream( new BufferedInputStream( new FileInputStream(CACHE_FILE))); Map map = (Map)ois.readObject(); if(map != null) { for(Iterator i = map.entrySet().iterator(); i.hasNext(); ) { // Remove values that aren't correct. Map.Entry next = (Map.Entry)i.next(); Object key = next.getKey(); Object value = next.getValue(); if( !(key instanceof URI) || !(value instanceof License) ) { if(LOG.isWarnEnabled()) LOG.warn("Invalid k[" + key + "], v[" + value + "]"); i.remove(); } } } else map = new HashMap(); licenses = map; data = (Map)ois.readObject(); removeOldEntries(); } catch(Throwable t) { LOG.error("Can't read licenses", t); } finally { if (ois != null) { try { ois.close(); } catch (IOException e) { // all we can do is try to close it } } if(licenses == null) licenses = new HashMap(); if(data == null) data = new HashMap(); } } /** * Removes any stale entries from the map so that they will automatically * be replaced. */ private void removeOldEntries() { long cutoff = System.currentTimeMillis() - EXPIRY_TIME; // discard outdated info for(Iterator i = licenses.values().iterator(); i.hasNext(); ) { License license = (License)i.next(); if(license.getLastVerifiedTime() < cutoff) { dirty = true; i.remove(); } } } /** * Write cache so that we only have to calculate them once. */ public synchronized void persistCache() { if(!dirty) return; ObjectOutputStream oos = null; try { oos = new ObjectOutputStream( new BufferedOutputStream(new FileOutputStream(CACHE_FILE))); oos.writeObject(licenses); oos.writeObject(data); oos.flush(); } catch (IOException e) { ErrorService.error(e); } finally { if(oos != null) { try { oos.close(); } catch(IOException ignored) {} } } dirty = false; } }