All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.octo.android.robospice.persistence.file.InFileObjectPersister Maven / Gradle / Ivy

The newest version!
package com.octo.android.robospice.persistence.file;

import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import roboguice.util.temp.Ln;
import android.app.Application;

import com.octo.android.robospice.persistence.DurationInMillis;
import com.octo.android.robospice.persistence.ObjectPersister;
import com.octo.android.robospice.persistence.exception.CacheCreationException;
import com.octo.android.robospice.persistence.exception.CacheLoadingException;
import com.octo.android.robospice.persistence.exception.KeySanitationExcepion;
import com.octo.android.robospice.persistence.keysanitation.KeySanitizer;

/**
 * An {@link ObjectPersister} that saves/loads data in a file.
 * @author sni
 * @param 
 *            the class of the data to load/save.
 */
public abstract class InFileObjectPersister extends ObjectPersister {

    // ----------------------------------
    // CONSTANTS
    // ----------------------------------

    /* package private */
    static final String CACHE_PREFIX_END = "_";

    /* package private */
    static final String DEFAULT_ROOT_CACHE_DIR = "robospice-cache";

    // ----------------------------------
    // ATTRIBUTES
    // ----------------------------------
    private KeySanitizer keySanitizer;

    private File cacheFolder;

    private String factoryCachePrefix = "";

    // ----------------------------------
    // CONSTRUCTOR
    // ----------------------------------

    public InFileObjectPersister(Application application, Class clazz) throws CacheCreationException {
        super(application, clazz);
        setCacheFolder(null);
    }

    public InFileObjectPersister(Application application, Class clazz, File cacheFolder) throws CacheCreationException {
        super(application, clazz);
        setCacheFolder(cacheFolder);
    }

    // ----------------------------------
    // PUBLIC API
    // ----------------------------------

    /**
     * Set the cacheFolder to use.
     * @param cacheFolder
     *            the new cache folder to use. Can be null, will then default to
     *            {@link #DEFAULT_ROOT_CACHE_DIR} sub folder in the application
     *            cache dir.
     * @throws CacheCreationException
     *             if the cache folder doesn't exist or can't be created.
     */
    public void setCacheFolder(File cacheFolder) throws CacheCreationException {
        if (cacheFolder == null) {
            cacheFolder = new File(getApplication().getCacheDir(), DEFAULT_ROOT_CACHE_DIR);
        }
        synchronized (cacheFolder.getAbsolutePath().intern()) {
            if (!cacheFolder.exists() && !cacheFolder.mkdirs()) {
                throw new CacheCreationException("The cache folder " + cacheFolder.getAbsolutePath() + " could not be created.");
            }
        }
        this.cacheFolder = cacheFolder;
    }

    public final File getCacheFolder() {
        return cacheFolder;
    }

    @Override
    public long getCreationDateInCache(Object cacheKey) throws CacheLoadingException {
        File cacheFile = getCacheFile(cacheKey);
        if (cacheFile.exists()) {
            return cacheFile.lastModified();
        } else {
            throw new CacheLoadingException(
                "Data could not be found in cache for cacheKey=" + cacheKey);
        }
    }

    @Override
    public List getAllCacheKeys() {
        final String prefix = getCachePrefix();
        int prefixLength = prefix.length();
        String[] cacheFileNameList = getCacheFolder().list(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String filename) {
                // patch from florianmski
                return filename.startsWith(prefix);
            }
        });
        if (cacheFileNameList == null) {
            return Collections.emptyList();
        }
        List result = new ArrayList(cacheFileNameList.length);
        for (String cacheFileName : cacheFileNameList) {
            String cacheKey = cacheFileName.substring(prefixLength);
            result.add(fromKey(cacheKey));
        }
        return result;

    }

    @Override
    public List loadAllDataFromCache() throws CacheLoadingException {
        List allCacheKeys = getAllCacheKeys();
        List result = new ArrayList(allCacheKeys.size());
        for (Object key : allCacheKeys) {
            result.add(loadDataFromCache(key, DurationInMillis.ALWAYS_RETURNED));
        }
        return result;
    }

    @Override
    public boolean removeDataFromCache(Object cacheKey) {
        return getCacheFile(cacheKey).delete();
    }

    @Override
    public void removeAllDataFromCache() {
        File cacheFolder = getCacheFolder();
        File[] cacheFileList = cacheFolder.listFiles(new FileFilter() {

            @Override
            public boolean accept(File file) {
                return file.getName().startsWith(getCachePrefix());
            }
        });

        boolean allDeleted = true;
        for (File cacheFile : cacheFileList) {
            allDeleted = cacheFile.delete() && allDeleted;
        }
        if (allDeleted || cacheFileList.length == 0) {
            Ln.d("Some file could not be deleted from cache.");
        }
    }

    @Override
    public T loadDataFromCache(Object cacheKey, long maxTimeInCache) throws CacheLoadingException {

        File file = getCacheFile(cacheKey);
        if (isCachedAndNotExpired(file, maxTimeInCache)) {
            return readCacheDataFromFile(file);
        }

        return null;
    }

    @Override
    public boolean isDataInCache(Object cacheKey, long maxTimeInCacheBeforeExpiry) {
        File file = getCacheFile(cacheKey);
        return isCachedAndNotExpired(file, maxTimeInCacheBeforeExpiry);
    }

    /**
     * @return Whether or not this {@link InFileObjectPersister} uses a
     *         {@link KeySanitizer}.
     */
    public boolean isUsingKeySanitizer() {
        return keySanitizer != null;
    }

    /**
     * @param keySanitizer
     *            the new key sanitizer to be used by this
     *            {@link InFileObjectPersister}. May be null, in that case no
     *            key sanitation will be used default). If key sanitation fails
     *            on a given cache key (by throwing a
     *            {@link KeySanitationExcepion}, original (unsanitized) cache
     *            keys will be used directly.
     */
    public void setKeySanitizer(KeySanitizer keySanitizer) {
        this.keySanitizer = keySanitizer;
    }

    /**
     * @return the key sanitizer used by this {@link InFileObjectPersister}. May
     *         be null, in that case no key sanitation will be used default).
     */
    public KeySanitizer getKeySanitizer() {
        return keySanitizer;
    }

    public final File getCacheFile(Object cacheKey) {
        return new File(getCacheFolder(), getCachePrefix() + toKey(cacheKey.toString()));
    }

    // ----------------------------------
    // PROTECTED METHODS
    // ----------------------------------
    /* package-private */
    void setFactoryCachePrefix(String factoryCachePrefix) {
        this.factoryCachePrefix = factoryCachePrefix;
    }

    /**
     * Get a key that may be sanitized if a {@link KeySanitizer} is used.
     * @param cacheKey
     *            a non-sanitized cacheKey.
     * @return a key that will be sanitized if a {@link KeySanitizer} is used.
     */
    protected final String toKey(String cacheKey) {
        if (isUsingKeySanitizer()) {
            try {
                return (String) keySanitizer.sanitizeKey(cacheKey);
            } catch (KeySanitationExcepion e) {
                Ln.e(e, "Key could not be sanitized, falling back on original key.");
                return cacheKey;
            }
        } else {
            return cacheKey;
        }
    }

    /**
     * Get a cache key that may be de-sanitized if a {@link KeySanitizer} is
     * used.
     * @param cacheKey
     *            a possibly sanitized cacheKey.
     * @return a key that will be de-sanitized if a {@link KeySanitizer} is
     *         used.
     */
    protected final String fromKey(String cacheKey) {
        if (isUsingKeySanitizer()) {
            try {
                return (String) keySanitizer.desanitizeKey(cacheKey);
            } catch (KeySanitationExcepion e) {
                Ln.e(e, "Key could not be desanitized, falling back on original key.");
                return cacheKey;
            }
        } else {
            return cacheKey;
        }
    }

    protected abstract T readCacheDataFromFile(File file) throws CacheLoadingException;

    protected final String getCachePrefix() {
        return factoryCachePrefix + getClass().getSimpleName() + CACHE_PREFIX_END + getHandledClass().getSimpleName() + CACHE_PREFIX_END;
    }

    protected boolean isCachedAndNotExpired(Object cacheKey, long maxTimeInCacheBeforeExpiry) {
        File cacheFile = getCacheFile(cacheKey);
        return isCachedAndNotExpired(cacheFile, maxTimeInCacheBeforeExpiry);
    }

    protected boolean isCachedAndNotExpired(File cacheFile, long maxTimeInCacheBeforeExpiry) {
        if (cacheFile.exists()) {
            long timeInCache = System.currentTimeMillis() - cacheFile.lastModified();
            if (maxTimeInCacheBeforeExpiry == DurationInMillis.ALWAYS_RETURNED || timeInCache <= maxTimeInCacheBeforeExpiry) {
                return true;
            }
        }
        return false;
    }

}