183 lines
6.6 KiB
Java
183 lines
6.6 KiB
Java
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
package org.mozilla.gecko;
|
|
|
|
import android.content.ContentProviderClient;
|
|
import android.os.Bundle;
|
|
import android.support.annotation.NonNull;
|
|
import android.support.annotation.VisibleForTesting;
|
|
import android.text.TextUtils;
|
|
import android.util.Log;
|
|
|
|
import org.json.JSONException;
|
|
import org.json.JSONObject;
|
|
import org.mozilla.gecko.db.BrowserContract;
|
|
import org.mozilla.gecko.db.BrowserDB;
|
|
import org.mozilla.gecko.util.BundleEventListener;
|
|
import org.mozilla.gecko.util.EventCallback;
|
|
import org.mozilla.gecko.util.ThreadUtils;
|
|
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.Map;
|
|
|
|
/**
|
|
* Provides access to metadata information about websites.
|
|
*
|
|
* While storing, in case of timing issues preventing us from looking up History GUID by a given uri,
|
|
* we queue up metadata and wait for GlobalHistory to let us know history record is now available.
|
|
*
|
|
* TODO Bug 1313515: selection of metadata for a given uri/history_GUID
|
|
*
|
|
* @author grisha
|
|
*/
|
|
/* package-local */ class GlobalPageMetadata implements BundleEventListener {
|
|
private static final String LOG_TAG = "GeckoGlobalPageMetadata";
|
|
|
|
private static final GlobalPageMetadata instance = new GlobalPageMetadata();
|
|
|
|
private static final String KEY_HAS_IMAGE = "hasImage";
|
|
private static final String KEY_METADATA_JSON = "metadataJSON";
|
|
|
|
private static final int MAX_METADATA_QUEUE_SIZE = 15;
|
|
|
|
private final Map<String, Bundle> queuedMetadata = Collections.synchronizedMap(new LimitedLinkedHashMap<String, Bundle>());
|
|
|
|
public static GlobalPageMetadata getInstance() {
|
|
return instance;
|
|
}
|
|
|
|
private static class LimitedLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
|
|
private static final long serialVersionUID = 6359725112736360244L;
|
|
|
|
@Override
|
|
protected boolean removeEldestEntry(Entry<K, V> eldest) {
|
|
if (size() > MAX_METADATA_QUEUE_SIZE) {
|
|
Log.w(LOG_TAG, "Page metadata queue is full. Dropping oldest metadata.");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private GlobalPageMetadata() {}
|
|
|
|
public void init() {
|
|
EventDispatcher
|
|
.getInstance()
|
|
.registerBackgroundThreadListener(this, GlobalHistory.EVENT_URI_AVAILABLE_IN_HISTORY);
|
|
}
|
|
|
|
public void add(BrowserDB db, ContentProviderClient contentProviderClient, String uri, boolean hasImage, @NonNull String metadataJSON) {
|
|
ThreadUtils.assertOnBackgroundThread();
|
|
|
|
// NB: Other than checking that JSON is valid and trimming it,
|
|
// we do not process metadataJSON in any way, trusting our source.
|
|
doAddOrQueue(db, contentProviderClient, uri, hasImage, metadataJSON);
|
|
}
|
|
|
|
@VisibleForTesting
|
|
/*package-local */ void doAddOrQueue(BrowserDB db, ContentProviderClient contentProviderClient, String uri, boolean hasImage, @NonNull String metadataJSON) {
|
|
final String preparedMetadataJSON;
|
|
try {
|
|
preparedMetadataJSON = prepareJSON(metadataJSON);
|
|
} catch (JSONException e) {
|
|
Log.e(LOG_TAG, "Couldn't process metadata JSON", e);
|
|
return;
|
|
}
|
|
|
|
// Don't bother queuing this if deletions fails to find a corresponding history record.
|
|
// If we can't delete metadata because it didn't exist yet, that's OK.
|
|
if (preparedMetadataJSON.equals("{}")) {
|
|
final int deleted = db.deletePageMetadata(contentProviderClient, uri);
|
|
// We could delete none if history record for uri isn't present.
|
|
// We must delete one if history record for uri is present.
|
|
if (deleted != 0 && deleted != 1) {
|
|
throw new IllegalStateException("Deleted unexpected number of page metadata records: " + deleted);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// If we could insert page metadata, we're done.
|
|
if (db.insertPageMetadata(contentProviderClient, uri, hasImage, preparedMetadataJSON)) {
|
|
return;
|
|
}
|
|
|
|
// Otherwise, we need to queue it for future insertion when history record is available.
|
|
Bundle bundledMetadata = new Bundle();
|
|
bundledMetadata.putBoolean(KEY_HAS_IMAGE, hasImage);
|
|
bundledMetadata.putString(KEY_METADATA_JSON, preparedMetadataJSON);
|
|
queuedMetadata.put(uri, bundledMetadata);
|
|
}
|
|
|
|
@VisibleForTesting
|
|
/* package-local */ int getMetadataQueueSize() {
|
|
return queuedMetadata.size();
|
|
}
|
|
|
|
@Override
|
|
public void handleMessage(String event, Bundle message, EventCallback callback) {
|
|
ThreadUtils.assertOnBackgroundThread();
|
|
|
|
if (!GlobalHistory.EVENT_URI_AVAILABLE_IN_HISTORY.equals(event)) {
|
|
return;
|
|
}
|
|
|
|
final String uri = message.getString(GlobalHistory.EVENT_PARAM_URI);
|
|
if (TextUtils.isEmpty(uri)) {
|
|
return;
|
|
}
|
|
|
|
final Bundle bundledMetadata;
|
|
synchronized (queuedMetadata) {
|
|
if (!queuedMetadata.containsKey(uri)) {
|
|
return;
|
|
}
|
|
|
|
bundledMetadata = queuedMetadata.get(uri);
|
|
queuedMetadata.remove(uri);
|
|
}
|
|
|
|
insertMetadataBundleForUri(uri, bundledMetadata);
|
|
}
|
|
|
|
private void insertMetadataBundleForUri(String uri, Bundle bundledMetadata) {
|
|
final boolean hasImage = bundledMetadata.getBoolean(KEY_HAS_IMAGE);
|
|
final String metadataJSON = bundledMetadata.getString(KEY_METADATA_JSON);
|
|
|
|
// Acquire CPC, must be released in this function.
|
|
final ContentProviderClient contentProviderClient = GeckoAppShell.getApplicationContext()
|
|
.getContentResolver()
|
|
.acquireContentProviderClient(BrowserContract.PageMetadata.CONTENT_URI);
|
|
|
|
// Pre-conditions...
|
|
if (contentProviderClient == null) {
|
|
Log.e(LOG_TAG, "Couldn't acquire content provider client");
|
|
return;
|
|
}
|
|
|
|
if (TextUtils.isEmpty(metadataJSON)) {
|
|
Log.e(LOG_TAG, "Metadata bundle contained empty metadata json");
|
|
return;
|
|
}
|
|
|
|
// Insert!
|
|
try {
|
|
add(
|
|
BrowserDB.from(GeckoThread.getActiveProfile()),
|
|
contentProviderClient,
|
|
uri, hasImage, metadataJSON
|
|
);
|
|
} finally {
|
|
contentProviderClient.release();
|
|
}
|
|
}
|
|
|
|
private String prepareJSON(String json) throws JSONException {
|
|
return (new JSONObject(json)).toString();
|
|
}
|
|
}
|