Bug 672956 - Add backend for expiring old history entries. r=lucasr

This commit is contained in:
Wes Johnston 2012-10-19 17:35:37 -07:00
parent e86eff5cd3
commit bb43b6dccc
4 changed files with 96 additions and 8 deletions

View File

@ -29,6 +29,12 @@ public class BrowserContract {
public static final String PARAM_IS_TEST = "test";
public static final String PARAM_INSERT_IF_NEEDED = "insert_if_needed";
public static final String PARAM_INCREMENT_VISITS = "increment_visits";
public static final String PARAM_EXPIRE_PRIORITY = "priority";
static public enum ExpirePriority {
NORMAL,
AGGRESSIVE
}
public interface CommonColumns {
public static final String _ID = "_id";
@ -111,6 +117,7 @@ public class BrowserContract {
public static final class History implements CommonColumns, URLColumns, HistoryColumns, ImageColumns, SyncColumns {
private History() {}
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "history");
public static final Uri CONTENT_OLD_URI = Uri.withAppendedPath(AUTHORITY_URI, "history/old");
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/browser-history";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/browser-history";
}

View File

@ -5,6 +5,8 @@
package org.mozilla.gecko.db;
import org.mozilla.gecko.db.BrowserContract.ExpirePriority;
import android.content.ContentResolver;
import android.database.ContentObserver;
import android.database.Cursor;
@ -43,6 +45,8 @@ public class BrowserDB {
public Cursor getRecentHistory(ContentResolver cr, int limit);
public void expireHistory(ContentResolver cr, ExpirePriority priority);
public void removeHistoryEntry(ContentResolver cr, int id);
public void clearHistory(ContentResolver cr);
@ -124,6 +128,12 @@ public class BrowserDB {
return sDb.getRecentHistory(cr, limit);
}
public static void expireHistory(ContentResolver cr, ExpirePriority priority) {
if (priority == null)
priority = ExpirePriority.NORMAL;
sDb.expireHistory(cr, priority);
}
public static void removeHistoryEntry(ContentResolver cr, int id) {
sDb.removeHistoryEntry(cr, id);
}

View File

@ -34,6 +34,7 @@ import org.mozilla.gecko.db.BrowserContract.Schema;
import org.mozilla.gecko.db.BrowserContract.SyncColumns;
import org.mozilla.gecko.db.BrowserContract.URLColumns;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.DBUtils;
import org.mozilla.gecko.ProfileMigrator;
import org.mozilla.gecko.sync.Utils;
@ -82,6 +83,13 @@ public class BrowserProvider extends ContentProvider {
// query (currently 3).
static final int MAX_POSITION_UPDATES_PER_QUERY = 100;
// Minimum number of records to keep when expiring history.
static final int DEFAULT_EXPIRY_RETAIN_COUNT = 2000;
static final int AGGRESSIVE_EXPIRY_RETAIN_COUNT = 500;
// Minimum duration to keep when expiring.
static final long DEFAULT_EXPIRY_PRESERVE_WINDOW = 1000L * 60L * 60L * 24L * 28L; // Four weeks.
static final String TABLE_BOOKMARKS = "bookmarks";
static final String TABLE_HISTORY = "history";
static final String TABLE_IMAGES = "images";
@ -104,6 +112,7 @@ public class BrowserProvider extends ContentProvider {
// History matches
static final int HISTORY = 200;
static final int HISTORY_ID = 201;
static final int HISTORY_OLD = 202;
// Image matches
static final int IMAGES = 300;
@ -176,6 +185,7 @@ public class BrowserProvider extends ContentProvider {
// History
URI_MATCHER.addURI(BrowserContract.AUTHORITY, "history", HISTORY);
URI_MATCHER.addURI(BrowserContract.AUTHORITY, "history/#", HISTORY_ID);
URI_MATCHER.addURI(BrowserContract.AUTHORITY, "history/old", HISTORY_OLD);
map = new HashMap<String, String>();
map.put(History._ID, History._ID);
@ -1324,6 +1334,49 @@ public class BrowserProvider extends ContentProvider {
}
}
/**
* Remove enough history items to bring the database count below <code>retain</code>,
* removing no items with a modified time after <code>keepAfter</code>.
*
* Provide <code>keepAfter</code> less than or equal to zero to skip that check.
*
* Items will be removed according to an approximate frecency calculation.
*
* Call this method within a transaction.
*/
public void expireHistory(final SQLiteDatabase db, final int retain, final long keepAfter) {
final long rows = DatabaseUtils.queryNumEntries(db, TABLE_HISTORY);
if (retain >= rows) {
debug("Not expiring history: only have " + rows + " rows.");
return;
}
final long toRemove = rows - retain;
final long now = System.currentTimeMillis();
debug("Expiring at most " + toRemove + " rows earlier than " + keepAfter + ".");
final String age = "(" + Combined.DATE_LAST_VISITED + " - " + now + ") / 86400000";
final String sortOrder = Combined.VISITS + " * MAX(1, 100 * 225 / (" + age + "*" + age + " + 225)) ASC";
final String sql;
if (keepAfter > 0) {
// If we don't bind these paramaters dynamically, the WHERE clause here can return null
sql = "DELETE FROM " + TABLE_HISTORY + " " +
"WHERE MAX(" + History.DATE_LAST_VISITED + ", " + History.DATE_MODIFIED +") < " + keepAfter + " " +
" AND " + History._ID + " " + "IN ( SELECT " +
History._ID + " FROM " + TABLE_HISTORY + " " +
"ORDER BY " + sortOrder + " LIMIT " + toRemove +
")";
} else {
sql = "DELETE FROM " + TABLE_HISTORY + " WHERE " + History._ID + " " +
"IN ( SELECT " + History._ID + " FROM " + TABLE_HISTORY + " " +
"ORDER BY " + sortOrder + " LIMIT " + toRemove + ")";
}
trace("Deleting using query: " + sql);
db.execSQL(sql);
}
private boolean isCallerSync(Uri uri) {
String isSync = uri.getQueryParameter(BrowserContract.PARAM_IS_SYNC);
return !TextUtils.isEmpty(isSync);
@ -1412,16 +1465,16 @@ public class BrowserProvider extends ContentProvider {
trace("Beginning delete transaction: " + uri);
db.beginTransaction();
try {
deleted = deleteInTransaction(uri, selection, selectionArgs);
deleted = deleteInTransaction(db, uri, selection, selectionArgs);
db.setTransactionSuccessful();
trace("Successful delete transaction: " + uri);
} finally {
db.endTransaction();
}
} else {
deleted = deleteInTransaction(uri, selection, selectionArgs);
deleted = deleteInTransaction(db, uri, selection, selectionArgs);
}
if (deleted > 0)
getContext().getContentResolver().notifyChange(uri, null);
@ -1429,7 +1482,7 @@ public class BrowserProvider extends ContentProvider {
}
@SuppressWarnings("fallthrough")
public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {
public int deleteInTransaction(SQLiteDatabase db, Uri uri, String selection, String[] selectionArgs) {
trace("Calling delete in transaction on URI: " + uri);
final int match = URI_MATCHER.match(uri);
@ -1464,6 +1517,19 @@ public class BrowserProvider extends ContentProvider {
break;
}
case HISTORY_OLD: {
String priority = uri.getQueryParameter(BrowserContract.PARAM_EXPIRE_PRIORITY);
long keepAfter = System.currentTimeMillis() - DEFAULT_EXPIRY_PRESERVE_WINDOW;
int retainCount = DEFAULT_EXPIRY_RETAIN_COUNT;
if (BrowserContract.ExpirePriority.AGGRESSIVE.toString().equals(priority)) {
keepAfter = 0;
retainCount = AGGRESSIVE_EXPIRY_RETAIN_COUNT;
}
expireHistory(db, retainCount, keepAfter);
break;
}
case IMAGES_ID:
debug("Delete on IMAGES_ID: " + uri);
@ -2073,10 +2139,6 @@ public class BrowserProvider extends ContentProvider {
values.put(Bookmarks.DATE_CREATED, now);
}
if (!values.containsKey(Bookmarks.DATE_MODIFIED)) {
values.put(Bookmarks.DATE_MODIFIED, now);
}
if (!values.containsKey(Bookmarks.GUID)) {
values.put(Bookmarks.GUID, Utils.generateGuid());
}

View File

@ -12,6 +12,7 @@ import org.mozilla.gecko.db.BrowserContract.ImageColumns;
import org.mozilla.gecko.db.BrowserContract.Images;
import org.mozilla.gecko.db.BrowserContract.SyncColumns;
import org.mozilla.gecko.db.BrowserContract.URLColumns;
import org.mozilla.gecko.db.BrowserContract.ExpirePriority;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
@ -54,6 +55,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
private final Uri mBookmarksUriWithProfile;
private final Uri mParentsUriWithProfile;
private final Uri mHistoryUriWithProfile;
private final Uri mHistoryExpireUriWithProfile;
private final Uri mImagesUriWithProfile;
private final Uri mCombinedUriWithProfile;
private final Uri mDeletedHistoryUriWithProfile;
@ -78,6 +80,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
mBookmarksUriWithProfile = appendProfile(Bookmarks.CONTENT_URI);
mParentsUriWithProfile = appendProfile(Bookmarks.PARENTS_CONTENT_URI);
mHistoryUriWithProfile = appendProfile(History.CONTENT_URI);
mHistoryExpireUriWithProfile = appendProfile(History.CONTENT_OLD_URI);
mImagesUriWithProfile = appendProfile(Images.CONTENT_URI);
mCombinedUriWithProfile = appendProfile(Combined.CONTENT_URI);
@ -285,6 +288,12 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
return new LocalDBCursor(c);
}
public void expireHistory(ContentResolver cr, ExpirePriority priority) {
Uri url = mHistoryExpireUriWithProfile;
url = url.buildUpon().appendQueryParameter(BrowserContract.PARAM_EXPIRE_PRIORITY, priority.toString()).build();
cr.delete(url, null, null);
}
public void removeHistoryEntry(ContentResolver cr, int id) {
cr.delete(mHistoryUriWithProfile,
History._ID + " = ?",