--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
@@ -23,16 +23,17 @@ import org.mozilla.gecko.db.BrowserContr
import org.mozilla.gecko.db.BrowserContract.Highlights;
import org.mozilla.gecko.db.BrowserContract.History;
import org.mozilla.gecko.db.BrowserContract.Visits;
import org.mozilla.gecko.db.BrowserContract.Schema;
import org.mozilla.gecko.db.BrowserContract.Tabs;
import org.mozilla.gecko.db.BrowserContract.Thumbnails;
import org.mozilla.gecko.db.BrowserContract.TopSites;
import org.mozilla.gecko.db.BrowserContract.UrlAnnotations;
+import org.mozilla.gecko.db.BrowserContract.PageMetadata;
import org.mozilla.gecko.db.DBUtils.UpdateOperation;
import org.mozilla.gecko.icons.IconsHelper;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.util.ThreadUtils;
import android.content.BroadcastReceiver;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
@@ -82,16 +83,17 @@ public class BrowserProvider extends Sha
static final String TABLE_BOOKMARKS = Bookmarks.TABLE_NAME;
static final String TABLE_HISTORY = History.TABLE_NAME;
static final String TABLE_VISITS = Visits.TABLE_NAME;
static final String TABLE_FAVICONS = Favicons.TABLE_NAME;
static final String TABLE_THUMBNAILS = Thumbnails.TABLE_NAME;
static final String TABLE_TABS = Tabs.TABLE_NAME;
static final String TABLE_URL_ANNOTATIONS = UrlAnnotations.TABLE_NAME;
static final String TABLE_ACTIVITY_STREAM_BLOCKLIST = ActivityStreamBlocklist.TABLE_NAME;
+ static final String TABLE_PAGE_METADATA = PageMetadata.TABLE_NAME;
static final String VIEW_COMBINED = Combined.VIEW_NAME;
static final String VIEW_BOOKMARKS_WITH_FAVICONS = Bookmarks.VIEW_WITH_FAVICONS;
static final String VIEW_BOOKMARKS_WITH_ANNOTATIONS = Bookmarks.VIEW_WITH_ANNOTATIONS;
static final String VIEW_HISTORY_WITH_FAVICONS = History.VIEW_WITH_FAVICONS;
static final String VIEW_COMBINED_WITH_FAVICONS = Combined.VIEW_WITH_FAVICONS;
// Bookmark matches
@@ -133,16 +135,18 @@ public class BrowserProvider extends Sha
static final int VISITS = 1100;
static final int METADATA = 1200;
static final int HIGHLIGHTS = 1300;
static final int ACTIVITY_STREAM_BLOCKLIST = 1400;
+ static final int PAGE_METADATA = 1500;
+
static final String DEFAULT_BOOKMARKS_SORT_ORDER = Bookmarks.TYPE
+ " ASC, " + Bookmarks.POSITION + " ASC, " + Bookmarks._ID
+ " ASC";
static final String DEFAULT_HISTORY_SORT_ORDER = History.DATE_LAST_VISITED + " DESC";
static final String DEFAULT_VISITS_SORT_ORDER = Visits.DATE_VISITED + " DESC";
static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
@@ -150,16 +154,17 @@ public class BrowserProvider extends Sha
static final Map<String, String> BOOKMARKS_PROJECTION_MAP;
static final Map<String, String> HISTORY_PROJECTION_MAP;
static final Map<String, String> COMBINED_PROJECTION_MAP;
static final Map<String, String> SCHEMA_PROJECTION_MAP;
static final Map<String, String> FAVICONS_PROJECTION_MAP;
static final Map<String, String> THUMBNAILS_PROJECTION_MAP;
static final Map<String, String> URL_ANNOTATIONS_PROJECTION_MAP;
static final Map<String, String> VISIT_PROJECTION_MAP;
+ static final Map<String, String> PAGE_METADATA_PROJECTION_MAP;
static final Table[] sTables;
static {
sTables = new Table[] {
// See awful shortcut assumption hack in getURLMetadataTable.
new URLMetadataTable()
};
// We will reuse this.
@@ -276,16 +281,26 @@ public class BrowserProvider extends Sha
map.put(Combined.FAVICON_ID, Combined.FAVICON_ID);
map.put(Combined.FAVICON_URL, Combined.FAVICON_URL);
map.put(Combined.LOCAL_DATE_LAST_VISITED, Combined.LOCAL_DATE_LAST_VISITED);
map.put(Combined.REMOTE_DATE_LAST_VISITED, Combined.REMOTE_DATE_LAST_VISITED);
map.put(Combined.LOCAL_VISITS_COUNT, Combined.LOCAL_VISITS_COUNT);
map.put(Combined.REMOTE_VISITS_COUNT, Combined.REMOTE_VISITS_COUNT);
COMBINED_PROJECTION_MAP = Collections.unmodifiableMap(map);
+ map = new HashMap<>();
+ map.put(PageMetadata._ID, PageMetadata._ID);
+ map.put(PageMetadata.HISTORY_GUID, PageMetadata.HISTORY_GUID);
+ map.put(PageMetadata.DATE_CREATED, PageMetadata.DATE_CREATED);
+ map.put(PageMetadata.HAS_IMAGE, PageMetadata.HAS_IMAGE);
+ map.put(PageMetadata.JSON, PageMetadata.JSON);
+ PAGE_METADATA_PROJECTION_MAP = Collections.unmodifiableMap(map);
+
+ URI_MATCHER.addURI(BrowserContract.AUTHORITY, "page_metadata", PAGE_METADATA);
+
// Schema
URI_MATCHER.addURI(BrowserContract.AUTHORITY, "schema", SCHEMA);
map = new HashMap<String, String>();
map.put(Schema.VERSION, Schema.VERSION);
SCHEMA_PROJECTION_MAP = Collections.unmodifiableMap(map);
@@ -553,20 +568,23 @@ public class BrowserProvider extends Sha
beginWrite(db);
/**
* Deletes from Sync are actual DELETE statements, which will cascade delete relevant visits.
* Fennec's deletes mark records as deleted and wipe out all information (except for GUID).
* Eventually, Fennec will purge history records that were marked as deleted for longer than some
* period of time (e.g. 20 days).
* See {@link SharedBrowserDatabaseProvider#cleanUpSomeDeletedRecords(Uri, String)}.
*/
+ final ArrayList<String> historyGUIDs = getHistoryGUIDsFromSelection(db, uri, selection, selectionArgs);
+
if (!isCallerSync(uri)) {
- deleteVisitsForHistory(uri, selection, selectionArgs);
+ deleteVisitsForHistory(db, historyGUIDs);
}
- deleted = deleteHistory(uri, selection, selectionArgs);
+ deletePageMetadataForHistory(db, historyGUIDs);
+ deleted = deleteHistory(db, uri, selection, selectionArgs);
deleteUnusedImages(uri);
break;
}
case VISITS:
trace("Deleting visits: " + uri);
beginWrite(db);
deleted = deleteVisits(uri, selection, selectionArgs);
@@ -616,16 +634,21 @@ public class BrowserProvider extends Sha
break;
}
case URL_ANNOTATIONS:
trace("Delete on URL_ANNOTATIONS: " + uri);
deleteUrlAnnotation(uri, selection, selectionArgs);
break;
+ case PAGE_METADATA:
+ trace("Delete on PAGE_METADATA: " + uri);
+ deleted = deletePageMetadata(uri, selection, selectionArgs);
+ break;
+
default: {
Table table = findTableFor(match);
if (table == null) {
throw new UnsupportedOperationException("Unknown delete URI " + uri);
}
trace("Deleting TABLE: " + uri);
beginWrite(db);
deleted = table.delete(db, uri, match, selection, selectionArgs);
@@ -682,16 +705,22 @@ public class BrowserProvider extends Sha
}
case ACTIVITY_STREAM_BLOCKLIST: {
trace("Insert on ACTIVITY_STREAM_BLOCKLIST: " + uri);
id = insertActivityStreamBlocklistSite(uri, values);
break;
}
+ case PAGE_METADATA: {
+ trace("Insert on PAGE_METADATA: " + uri);
+ id = insertPageMetadata(uri, values);
+ break;
+ }
+
default: {
Table table = findTableFor(match);
if (table == null) {
throw new UnsupportedOperationException("Unknown insert URI " + uri);
}
trace("Insert on TABLE: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
@@ -1412,16 +1441,24 @@ public class BrowserProvider extends Sha
}
case HIGHLIGHTS: {
debug("Highlights query: " + uri);
return getHighlights(db, limit);
}
+ case PAGE_METADATA: {
+ debug("PageMetadata query: " + uri);
+
+ qb.setProjectionMap(PAGE_METADATA_PROJECTION_MAP);
+ qb.setTables(TABLE_PAGE_METADATA);
+ break;
+ }
+
default: {
Table table = findTableFor(match);
if (table == null) {
throw new UnsupportedOperationException("Unknown query URI " + uri);
}
trace("Update TABLE: " + uri);
return table.query(db, uri, match, projection, selection, selectionArgs, sortOrder, groupBy, limit);
}
@@ -1927,32 +1964,54 @@ public class BrowserProvider extends Sha
final SQLiteDatabase db = getWritableDatabase(uri);
values.put(ActivityStreamBlocklist.CREATED, System.currentTimeMillis());
beginWrite(db);
return db.insertOrThrow(TABLE_ACTIVITY_STREAM_BLOCKLIST, null, values);
}
+ private long insertPageMetadata(final Uri uri, final ContentValues values) {
+ final SQLiteDatabase db = getWritableDatabase(uri);
+
+ if (!values.containsKey(PageMetadata.DATE_CREATED)) {
+ values.put(PageMetadata.DATE_CREATED, System.currentTimeMillis());
+ }
+
+ beginWrite(db);
+
+ // Perform INSERT OR REPLACE, there might be page metadata present and we want to replace it.
+ // Depends on a conflict arising from unique foreign key (history_guid) constraint violation.
+ return db.insertWithOnConflict(
+ TABLE_PAGE_METADATA, null, values, SQLiteDatabase.CONFLICT_REPLACE);
+ }
+
private long insertUrlAnnotation(final Uri uri, final ContentValues values) {
final String url = values.getAsString(UrlAnnotations.URL);
trace("Inserting url annotations for URL: " + url);
final SQLiteDatabase db = getWritableDatabase(uri);
beginWrite(db);
return db.insertOrThrow(TABLE_URL_ANNOTATIONS, null, values);
}
private void deleteUrlAnnotation(final Uri uri, final String selection, final String[] selectionArgs) {
trace("Deleting url annotation for URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
db.delete(TABLE_URL_ANNOTATIONS, selection, selectionArgs);
}
+ private int deletePageMetadata(final Uri uri, final String selection, final String[] selectionArgs) {
+ trace("Deleting page metadata for URI: " + uri);
+
+ final SQLiteDatabase db = getWritableDatabase(uri);
+ return db.delete(TABLE_PAGE_METADATA, selection, selectionArgs);
+ }
+
private void updateUrlAnnotation(final Uri uri, final ContentValues values, final String selection, final String[] selectionArgs) {
trace("Updating url annotation for URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
db.update(TABLE_URL_ANNOTATIONS, values, selection, selectionArgs);
}
private int updateOrInsertThumbnail(Uri uri, ContentValues values, String selection,
@@ -1988,21 +2047,19 @@ public class BrowserProvider extends Sha
}
/**
* This method does not create a new transaction. Its first operation is
* guaranteed to be a write, which in the case of a new enclosing
* transaction will guarantee that a read does not need to be upgraded to
* a write.
*/
- private int deleteHistory(Uri uri, String selection, String[] selectionArgs) {
+ private int deleteHistory(SQLiteDatabase db, Uri uri, String selection, String[] selectionArgs) {
debug("Deleting history entry for URI: " + uri);
- final SQLiteDatabase db = getWritableDatabase(uri);
-
if (isCallerSync(uri)) {
return db.delete(TABLE_HISTORY, selection, selectionArgs);
}
debug("Marking history entry as deleted for URI: " + uri);
ContentValues values = new ContentValues();
values.put(History.IS_DELETED, 1);
@@ -2026,55 +2083,66 @@ public class BrowserProvider extends Sha
cleanUpSomeDeletedRecords(uri, TABLE_HISTORY);
} catch (Exception e) {
// We don't care.
Log.e(LOGTAG, "Unable to clean up deleted history records: ", e);
}
return updated;
}
- private int deleteVisitsForHistory(Uri uri, String selection, String[] selectionArgs) {
- final SQLiteDatabase db = getWritableDatabase(uri);
+ private ArrayList<String> getHistoryGUIDsFromSelection(SQLiteDatabase db, Uri uri, String selection, String[] selectionArgs) {
+ final ArrayList<String> historyGUIDs = new ArrayList<>();
final Cursor cursor = db.query(
History.TABLE_NAME, new String[] {History.GUID}, selection, selectionArgs,
null, null, null);
if (cursor == null) {
Log.e(LOGTAG, "Null cursor while trying to delete visits for history URI: " + uri);
- return 0;
+ return historyGUIDs;
}
- ArrayList<String> historyGUIDs = new ArrayList<>();
try {
if (!cursor.moveToFirst()) {
trace("No history items for which to remove visits matched for URI: " + uri);
- return 0;
+ return historyGUIDs;
}
final int historyColumn = cursor.getColumnIndexOrThrow(History.GUID);
while (!cursor.isAfterLast()) {
historyGUIDs.add(cursor.getString(historyColumn));
cursor.moveToNext();
}
} finally {
cursor.close();
}
+ return historyGUIDs;
+ }
+
+ private int deletePageMetadataForHistory(SQLiteDatabase db, ArrayList<String> historyGUIDs) {
+ return bulkDeleteByHistoryGUID(db, historyGUIDs, PageMetadata.TABLE_NAME, PageMetadata.HISTORY_GUID);
+ }
+
+ private int deleteVisitsForHistory(SQLiteDatabase db, ArrayList<String> historyGUIDs) {
+ return bulkDeleteByHistoryGUID(db, historyGUIDs, Visits.TABLE_NAME, Visits.HISTORY_GUID);
+ }
+
+ private int bulkDeleteByHistoryGUID(SQLiteDatabase db, ArrayList<String> historyGUIDs, String table, String historyGUIDColumn) {
// Due to SQLite's maximum variable limitation, we need to chunk our delete statements.
// For example, if there were 1200 GUIDs, this will perform 2 delete statements.
int deleted = 0;
for (int chunk = 0; chunk <= historyGUIDs.size() / DBUtils.SQLITE_MAX_VARIABLE_NUMBER; chunk++) {
final int chunkStart = chunk * DBUtils.SQLITE_MAX_VARIABLE_NUMBER;
int chunkEnd = (chunk + 1) * DBUtils.SQLITE_MAX_VARIABLE_NUMBER;
if (chunkEnd > historyGUIDs.size()) {
chunkEnd = historyGUIDs.size();
}
final List<String> chunkGUIDs = historyGUIDs.subList(chunkStart, chunkEnd);
deleted += db.delete(
- Visits.TABLE_NAME,
- DBUtils.computeSQLInClause(chunkGUIDs.size(), Visits.HISTORY_GUID),
+ table,
+ DBUtils.computeSQLInClause(chunkGUIDs.size(), historyGUIDColumn),
chunkGUIDs.toArray(new String[chunkGUIDs.size()])
);
}
return deleted;
}
private int deleteVisits(Uri uri, String selection, String[] selectionArgs) {