nya.miku.wishmaster.cache.FileCache.java Source code

Java tutorial

Introduction

Here is the source code for nya.miku.wishmaster.cache.FileCache.java

Source

/*
 * Overchan Android (Meta Imageboard Client)
 * Copyright (C) 2014-2015  miku-nyan <https://github.com/miku-nyan>
 *     
 * This program 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.
 * 
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package nya.miku.wishmaster.cache;

import java.io.File;
import java.util.LinkedList;
import java.util.ListIterator;

import org.apache.commons.lang3.tuple.Pair;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.provider.BaseColumns;
import nya.miku.wishmaster.common.Logger;

/**
 *   ? (LRU)
 * @author miku-nyan
 *
 */
public class FileCache {
    private static final String TAG = "FileCache";

    public static final String PREFIX_ORIGINALS = "orig_";
    /*package*/ static final String PREFIX_BITMAPS = "thumb_";

    /*package*/ static final String PREFIX_PAGES = "page_";
    // ???, ?  ??   10%
    /*package*/ static final String PREFIX_DRAFTS = "draft_";

    /*package*/ static final String PREFIX_BOARDS = "boards_"; // ??? 
    /** ?  ? ????  */
    /*package*/ static final String TABS_FILENAME = "tabsstate"; // ??? 
    /** ?  ? ????  (?) */
    /*package*/ static final String TABS_FILENAME_2 = "tabsstate_2"; // ??? 

    private static final String NOMEDIA = ".nomedia";

    private static final float PAGES_QUOTE = 0.1f;

    private final FileCacheDB database;

    private final File directory;
    private long maxSize;
    private long maxPagesSize;

    private volatile long size;
    private volatile long pagesSize;

    /**
     * ?
     * @param directory ? ?
     * @param maxSize ?    (0 - )
     * @param dbContext ? ? ?   
     */
    public FileCache(File directory, long maxSize, Context dbContext) {
        this.directory = directory;
        this.database = new FileCacheDB(dbContext);
        makeDir();
        makeNomedia();

        long[] sizeInDB = database.getSize();
        if (sizeInDB[0] != 0) {
            this.size = sizeInDB[0];
            this.pagesSize = sizeInDB[1];
        } else {
            resetCache();
        }

        setMaxSize(maxSize);
    }

    /**
     * ? ?  ?
     * @param maxSize ?    (0 - )
     */
    public void setMaxSize(long maxSize) {
        this.maxSize = maxSize;
        this.maxPagesSize = (long) (maxSize * PAGES_QUOTE);
        trim();
    }

    /**
     *    ?
     * @return    
     */
    public long getCurrentSize() {
        return size;
    }

    /**
     *    ?  
     * @return    
     */
    public double getCurrentSizeMB() {
        return (double) getCurrentSize() / (1024 * 1024);
    }

    /**
     * ? ? ( ? )
     */
    public void clearCache() {
        for (File f : filesOfDir(directory)) {
            if (!isUndeletable(f))
                f.delete();
        }
        resetCache();
    }

    /**
     *    ?
     * @param fileName ? 
     * @return  , ?  ??,  null, ?  ??
     */
    public synchronized File get(String fileName) {
        File file = pathToFile(fileName);
        if (file.exists() && !file.isDirectory()) {
            file.setLastModified(System.currentTimeMillis());
            database.touch(fileName);
            return file;
        }
        return null;
    }

    /**
     *   ?   (?  ?    ??  ?,  ???).
     *   ? ?  (? ? ?)    {@link #put(File)},  ?   .
     * ??   ?  ( ?)  ? .  
     * @param fileName ? 
     * @return   {@link File}
     */
    public synchronized File create(String fileName) {
        makeDir();
        File file = pathToFile(fileName);
        if (file.exists()) {
            delete(file, false);
        }
        database.put(fileName, -1);
        return file;
    }

    /**
     *   ? , ?   ?,  ? ? ??? ? . 
     * @param file   {@link File}
     */
    public synchronized void put(File file) {
        size += file.length();
        if (isPageFile(file))
            pagesSize += file.length();
        database.put(file.getName(), file.length());
        trim();
    }

    /**
     *    ?
     * @param file   {@link File}
     * @return true, ?   ?, false   ?
     */
    public synchronized boolean delete(File file) {
        return delete(file, true);
    }

    public synchronized boolean delete(File file, boolean removeFromDB) {
        size -= file.length();
        if (isPageFile(file))
            pagesSize -= file.length();
        if (file.delete()) {
            if (removeFromDB)
                database.remove(file.getName());
            return true;
        } else {
            resetCache();
            return false;
        }
    }

    private File pathToFile(String fileName) {
        return new File(directory, fileName);
    }

    private void makeDir() {
        if (!directory.exists()) {
            if (!directory.mkdirs()) {
                Logger.e(TAG, "Unable to create file cache dir " + directory.getPath());
            }
        }
    }

    private void makeNomedia() {
        try {
            pathToFile(NOMEDIA).createNewFile();
        } catch (Exception e) {
            Logger.e(TAG, "couldn't create .nomedia file", e);
        }
    }

    private synchronized void trim() {
        for (int i = 0; i < 3; ++i) {
            if (maxSize == 0 || size <= maxSize)
                return;

            LinkedList<Pair<String, Long>> files = database.getFilesForTrim(size - maxSize);

            while (size > maxSize) {
                File oldest = null;
                for (ListIterator<Pair<String, Long>> it = files.listIterator(); it.hasNext();) {
                    File file = pathToFile(it.next().getLeft());
                    if (isPageFile(file) && pagesSize < maxPagesSize)
                        continue;
                    it.remove();
                    oldest = file;
                    break;
                }
                if (oldest == null) {
                    Logger.e(TAG, "No files to trim");
                    break;
                } else {
                    Logger.d(TAG, "Deleting " + oldest.getPath());
                    if (!delete(oldest)) {
                        Logger.e(TAG, "Cannot delete cache file: " + oldest.getPath());
                        break;
                    }
                }
            }
        }
    }

    private synchronized void resetCache() {
        database.resetDB();
        for (File file : filesOfDir(directory))
            database.put(file.getName(), file.length());
        long[] sizeInDB = database.getSize();
        size = sizeInDB[0];
        pagesSize = sizeInDB[1];
    }

    private boolean isUndeletable(File file) {
        return isUndeletable(file.getName());
    }

    private static boolean isUndeletable(String filename) {
        return filename.equals(TABS_FILENAME) || filename.equals(TABS_FILENAME_2)
                || filename.startsWith(PREFIX_BOARDS) || filename.equals(NOMEDIA);
    }

    private boolean isPageFile(File file) {
        return isPageFile(file.getName());
    }

    private static boolean isPageFile(String filename) {
        return filename.startsWith(PREFIX_PAGES) || filename.startsWith(PREFIX_DRAFTS);
    }

    private File[] filesOfDir(File directory) {
        File[] files = directory.listFiles();
        if (files == null)
            return new File[0];
        return files;
    }

    private static class FileCacheDB {
        private static final int DB_VERSION = 1000;
        private static final String DB_NAME = "filecache.db";

        private static final String TABLE_NAME = "files";
        private static final String COL_FILENAME = "name";
        private static final String COL_FILESIZE = "size";
        private static final String COL_TIMESTAMP = "time";

        private final DBHelper dbHelper;

        public FileCacheDB(Context context) {
            dbHelper = new DBHelper(context);
        }

        public boolean isExists(String filename) {
            Cursor c = dbHelper.getReadableDatabase().query(TABLE_NAME, null, COL_FILENAME + " = ?",
                    new String[] { filename }, null, null, null);
            boolean result = false;
            if (c != null && c.moveToFirst())
                result = true;
            if (c != null)
                c.close();
            return result;
        }

        public void touch(String filename) {
            ContentValues cv = new ContentValues();
            cv.put(COL_TIMESTAMP, System.currentTimeMillis());
            dbHelper.getWritableDatabase().update(TABLE_NAME, cv, COL_FILENAME + " = ?", new String[] { filename });
        }

        public void put(String filename, long size) {
            ContentValues cv = new ContentValues();
            cv.put(COL_FILENAME, filename);
            cv.put(COL_FILESIZE, size);
            cv.put(COL_TIMESTAMP, System.currentTimeMillis());

            if (isExists(filename)) {
                dbHelper.getWritableDatabase().update(TABLE_NAME, cv, COL_FILENAME + " = ?",
                        new String[] { filename });
            } else {
                dbHelper.getWritableDatabase().insert(TABLE_NAME, null, cv);
            }
        }

        public LinkedList<Pair<String, Long>> getFilesForTrim(long size) {
            LinkedList<Pair<String, Long>> list = new LinkedList<>();
            Cursor c = dbHelper.getReadableDatabase().query(TABLE_NAME, null, null, null, null, null, COL_TIMESTAMP,
                    "1000");
            if (c != null) {
                if (c.moveToFirst()) {
                    int nameIndex = c.getColumnIndex(COL_FILENAME);
                    int timeIndex = c.getColumnIndex(COL_TIMESTAMP);
                    int sizeIndex = c.getColumnIndex(COL_FILESIZE);
                    long tSize = 0;
                    do {
                        String filename = c.getString(nameIndex);
                        if (!isUndeletable(filename)) {
                            list.add(Pair.of(filename, c.getLong(timeIndex)));
                            if (!isPageFile(filename))
                                tSize += c.getLong(sizeIndex);
                            if (tSize >= size)
                                break;
                        }
                    } while (c.moveToNext());
                }
                c.close();
            }
            return list;
        }

        public long[] getSize() {
            long[] result = new long[] { 0, 0 };
            Cursor c = dbHelper.getReadableDatabase().query(TABLE_NAME, null, COL_FILESIZE + " = -1", null, null,
                    null, null);
            if (c != null) {
                if (c.moveToFirst()) {
                    c.close();
                    resetDB();
                    return result;
                }
                c.close();
            }

            c = dbHelper.getReadableDatabase().query(TABLE_NAME, null, null, null, null, null, null);
            if (c != null) {
                if (c.moveToFirst()) {
                    int nameIndex = c.getColumnIndex(COL_FILENAME);
                    int sizeIndex = c.getColumnIndex(COL_FILESIZE);
                    do {
                        String name = c.getString(nameIndex);
                        long size = c.getLong(sizeIndex);
                        result[0] += size;
                        if (isPageFile(name))
                            result[1] += size;
                    } while (c.moveToNext());
                }
                c.close();
            }
            return result;
        }

        public void remove(String filename) {
            dbHelper.getWritableDatabase().delete(TABLE_NAME, COL_FILENAME + " = ?", new String[] { filename });
        }

        public void resetDB() {
            dbHelper.resetDB();
        }

        private static class DBHelper extends SQLiteOpenHelper implements BaseColumns {
            public DBHelper(Context context) {
                super(context, DB_NAME, null, DB_VERSION);
            }

            @Override
            public void onCreate(SQLiteDatabase db) {
                db.execSQL(createTable(TABLE_NAME, new String[] { COL_FILENAME, COL_FILESIZE, COL_TIMESTAMP },
                        new String[] { "text", "integer", "integer" }));
            }

            @Override
            public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
                if (oldVersion < newVersion) {
                    db.execSQL(dropTable(TABLE_NAME));
                    onCreate(db);
                }
            }

            @Override
            public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
                onUpgrade(db, oldVersion, newVersion);
            }

            private static String createTable(String tableName, String[] columns, String[] types) {
                StringBuilder sql = new StringBuilder(110).append("create table ").append(tableName).append(" (")
                        .append(_ID).append(" integer primary key autoincrement,");
                for (int i = 0; i < columns.length; ++i) {
                    sql.append(columns[i]).append(' ').append(types == null ? "text" : types[i]).append(',');
                }
                sql.setCharAt(sql.length() - 1, ')');
                return sql.append(';').toString();
            }

            private static String dropTable(String tableName) {
                return "DROP TABLE IF EXISTS " + tableName;
            }

            private void resetDB() {
                SQLiteDatabase db = getWritableDatabase();
                db.execSQL(dropTable(TABLE_NAME));
                onCreate(db);
            }

        }
    }

}