diff options
Diffstat (limited to 'src/com/cyanogenmod/filemanager/mstaru/MostFrequentlyUsedProvider.java')
-rw-r--r-- | src/com/cyanogenmod/filemanager/mstaru/MostFrequentlyUsedProvider.java | 320 |
1 files changed, 320 insertions, 0 deletions
diff --git a/src/com/cyanogenmod/filemanager/mstaru/MostFrequentlyUsedProvider.java b/src/com/cyanogenmod/filemanager/mstaru/MostFrequentlyUsedProvider.java new file mode 100644 index 00000000..552a796e --- /dev/null +++ b/src/com/cyanogenmod/filemanager/mstaru/MostFrequentlyUsedProvider.java @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.filemanager.mstaru; + +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; +import android.database.sqlite.SQLiteStatement; +import android.net.Uri; + +import android.util.Log; +import com.cyanogenmod.filemanager.mstaru.MostFrequentlyUsedContract.Item; + +import java.util.HashMap; + +/** + * This provider provides access to help in keeping track and querying of the most frequently + * accessed items. + */ +public class MostFrequentlyUsedProvider extends ContentProvider { + private static final String TAG = MostFrequentlyUsedProvider.class.getSimpleName(); + + private static final boolean DEBUG = true; + + private static class MFUOpenHelper extends SQLiteOpenHelper { + + private static final String NAME = "mfu"; + private static final int VERSION = 1; + + /** + * {@inheritDoc} + */ + public MFUOpenHelper(Context context) { + super(context, NAME, null, VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + MostFrequentlyUsedContract.Item.TABLE_NAME + "(" + + MostFrequentlyUsedContract.Item._ID + + " INTEGER PRIMARY KEY AUTOINCREMENT," + + Item.KEY + " TEXT," + + Item.COUNT + " INTEGER NOT NULL DEFAULT 1," + + Item._LAST_DEGRADE_TIMESTAMP + + " INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP," + + "UNIQUE(" + MostFrequentlyUsedContract.Item.KEY + ')' + + ");" + ); + + db.execSQL("CREATE INDEX " + Item.TABLE_NAME + "_count_idx" + + " ON " + Item.TABLE_NAME + "(" + Item.COUNT + ");"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // No upgrades yet + } + } + + private static final String STMT_UPDATE_COUNT = "UPDATE " + Item.TABLE_NAME + + " SET " + Item.COUNT + "=" + Item.COUNT + "+1, " + + Item._LAST_DEGRADE_TIMESTAMP + "=CURRENT_TIMESTAMP" + + " WHERE " + Item.KEY + "=?;"; + + private static final String STMT_DEGRADE_COUNT = "UPDATE " + Item.TABLE_NAME + + " SET " + Item.COUNT +"=" + Item.COUNT + "-CAST(" + + "JULIANDAY(CURRENT_TIMESTAMP)" + + "-JULIANDAY(" + Item._LAST_DEGRADE_TIMESTAMP + ")" + + " AS INT)," + + Item._LAST_DEGRADE_TIMESTAMP + "=CURRENT_TIMESTAMP" + + " WHERE " + + "JULIANDAY() - JULIANDAY(" + Item._LAST_DEGRADE_TIMESTAMP + ") > 1;"; + + + private static final int MAX_RESULTS = 10; + + private static UriMatcher sUriMatcher; + + private static final int MATCH_ITEMS = 0; + + private static HashMap<String, String> sFilesProjectionMap; + + static { + sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + sUriMatcher.addURI(MostFrequentlyUsedContract.AUTHORITY, "/" + Item.FOLDER, MATCH_ITEMS); + + sFilesProjectionMap = new HashMap<String, String>(); + sFilesProjectionMap.put(Item._ID, Item._ID); + sFilesProjectionMap.put(MostFrequentlyUsedContract.Item.KEY, + MostFrequentlyUsedContract.Item.KEY); + sFilesProjectionMap.put(Item.COUNT, Item.COUNT); + } + + private MFUOpenHelper mOpenHelper; + + private ContentResolver mResolver; + + @Override + public boolean onCreate() { + mOpenHelper = new MFUOpenHelper(getContext()); + mResolver = getContext().getContentResolver(); + + return true; + } + + /** + * This degrades all columns by the number of days since the last degrade for each column. + * + * Eventually, everything will end up in the negatives, however, given our logic, we always keep + * at least {@link #MAX_RESULTS} items if that number exists. + * + * @param db + */ + private int degrade(SQLiteDatabase db) { + try { + SQLiteStatement stmt = db.compileStatement(STMT_DEGRADE_COUNT); + int cnt = stmt.executeUpdateDelete(); + + if (DEBUG) Log.d(TAG, "Degraded " + cnt + " items"); + + return cnt; + } catch(SQLiteException e) { + Log.e(TAG, "Failed to degrade ", e); + // Do nothing, we'll try again some other time + } + return 0; + } + + private int prune(SQLiteDatabase db) { + Cursor c; + try { + c = db.query(Item.TABLE_NAME, new String[]{Item._ID}, null, null, null, null, + Item.COUNT + " DESC", "" + MAX_RESULTS); + StringBuilder b = new StringBuilder(); + + String[] selectionArgs = new String[c.getCount()]; + b.append(Item.COUNT) + .append(" < 0"); + int i = 0; + + while (c.moveToNext()) { + b.append(" AND ") + .append(Item._ID) + .append("!=?"); + selectionArgs[i++] = "" + c.getLong(0); + } + int cnt = db.delete(Item.TABLE_NAME, b.toString(), selectionArgs); + if (DEBUG) Log.d(TAG, "Pruned " + cnt + " items"); + + return cnt; + } catch (SQLiteException e) { + // We can safely ignore this and just prune next time + } + return 0; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + + if (sortOrder == null) { + sortOrder = Item.COUNT + " DESC"; + } + + int which = sUriMatcher.match(uri); + switch(which) { + case MATCH_ITEMS: + qb.setTables(Item.TABLE_NAME); + qb.setProjectionMap(sFilesProjectionMap); + break; + default: + throw new UnsupportedOperationException("Unknown uri " + uri); + } + + SQLiteDatabase db = null; + try { + db = mOpenHelper.getWritableDatabase(); + db.beginTransaction(); + degrade(db); + prune(db); + db.setTransactionSuccessful(); + } catch(SQLiteException e) { + // Do nothing + } finally { + if (db != null) db.endTransaction(); + } + + db = mOpenHelper.getWritableDatabase(); + + Cursor c = qb.query( + db, + projection, + selection, + selectionArgs, + null, + null, + sortOrder, + "" + MAX_RESULTS); + return c; + } + + @Override + public String getType(Uri uri) { + switch(sUriMatcher.match(uri)) { + case MATCH_ITEMS: + return Item.CONTENT_TYPE; + default: + return null; + } + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + int which = sUriMatcher.match(uri); + String table; + switch(which) { + case MATCH_ITEMS: + table = Item.TABLE_NAME; + break; + default: + throw new UnsupportedOperationException("Unknown uri " + uri); + } + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + long id = db.insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_IGNORE); + if (id == -1) { + SQLiteStatement stmt = db.compileStatement(STMT_UPDATE_COUNT); + stmt.bindString(1, values.getAsString(Item.KEY)); + int cnt = stmt.executeUpdateDelete(); + if (cnt > 0) { + Cursor c = null; + try { + String selection = Item.KEY + "=?"; + String[] selectionArgs = new String[]{ + values.getAsString(Item.KEY), + }; + c = db.query(MostFrequentlyUsedContract.Item.TABLE_NAME, + new String[]{Item._ID}, + selection, + selectionArgs, + null, null, null, null); + if (c.moveToFirst()) { + id = c.getLong(0); + } + } finally { + if (c != null) c.close(); + } + } + } + if (id > -1) { + mResolver.notifyChange(uri, null); + } + return Uri.withAppendedPath(uri, "" + id); + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + int which = sUriMatcher.match(uri); + String table; + switch(which) { + case MATCH_ITEMS: + table = MostFrequentlyUsedContract.Item.TABLE_NAME; + break; + default: + throw new UnsupportedOperationException("Unknown uri " + uri); + } + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + int cnt = db.delete(table, selection, selectionArgs); + if (cnt > 0) { + mResolver.notifyChange(uri, null); + } + return cnt; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + int which = sUriMatcher.match(uri); + String table; + switch(which) { + case MATCH_ITEMS: + table = Item.TABLE_NAME; + break; + default: + throw new UnsupportedOperationException("Unknown uri " + uri); + } + + values = new ContentValues(values); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + int cnt = db.update(table, values, selection, selectionArgs); + values.put(Item.COUNT, Item.COUNT + " + 1"); + if (cnt > 0) { + mResolver.notifyChange(uri, null); + } + return cnt; + } +} |