michael@0: /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko; michael@0: michael@0: import org.mozilla.gecko.db.BrowserDB; michael@0: import org.mozilla.gecko.util.ThreadUtils; michael@0: michael@0: import android.database.Cursor; michael@0: import android.net.Uri; michael@0: import android.os.Handler; michael@0: import android.util.Log; michael@0: michael@0: import java.lang.ref.SoftReference; michael@0: import java.util.HashSet; michael@0: import java.util.LinkedList; michael@0: import java.util.Queue; michael@0: import java.util.Set; michael@0: michael@0: class GlobalHistory { michael@0: private static final String LOGTAG = "GeckoGlobalHistory"; michael@0: michael@0: private static GlobalHistory sInstance = new GlobalHistory(); michael@0: michael@0: static GlobalHistory getInstance() { michael@0: return sInstance; michael@0: } michael@0: michael@0: // this is the delay between receiving a URI check request and processing it. michael@0: // this allows batching together multiple requests and processing them together, michael@0: // which is more efficient. michael@0: private static final long BATCHING_DELAY_MS = 100; michael@0: michael@0: private final Handler mHandler; // a background thread on which we can process requests michael@0: private final Queue mPendingUris; // URIs that need to be checked michael@0: private SoftReference> mVisitedCache; // cache of the visited URI list michael@0: private final Runnable mNotifierRunnable; // off-thread runnable used to check URIs michael@0: private boolean mProcessing; // = false // whether or not the runnable is queued/working michael@0: michael@0: private GlobalHistory() { michael@0: mHandler = ThreadUtils.getBackgroundHandler(); michael@0: mPendingUris = new LinkedList(); michael@0: mVisitedCache = new SoftReference>(null); michael@0: mNotifierRunnable = new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: Set visitedSet = mVisitedCache.get(); michael@0: if (visitedSet == null) { michael@0: // the cache was wiped away, repopulate it michael@0: Log.w(LOGTAG, "Rebuilding visited link set..."); michael@0: visitedSet = new HashSet(); michael@0: Cursor c = null; michael@0: try { michael@0: c = BrowserDB.getAllVisitedHistory(GeckoAppShell.getContext().getContentResolver()); michael@0: if (c == null) { michael@0: return; michael@0: } michael@0: michael@0: if (c.moveToFirst()) { michael@0: do { michael@0: visitedSet.add(c.getString(0)); michael@0: } while (c.moveToNext()); michael@0: } michael@0: mVisitedCache = new SoftReference>(visitedSet); michael@0: } finally { michael@0: if (c != null) michael@0: c.close(); michael@0: } michael@0: } michael@0: michael@0: // this runs on the same handler thread as the checkUriVisited code, michael@0: // so no synchronization needed michael@0: while (true) { michael@0: String uri = mPendingUris.poll(); michael@0: if (uri == null) { michael@0: break; michael@0: } michael@0: if (visitedSet.contains(uri)) { michael@0: GeckoAppShell.notifyUriVisited(uri); michael@0: } michael@0: } michael@0: mProcessing = false; michael@0: } michael@0: }; michael@0: } michael@0: michael@0: public void addToGeckoOnly(String uri) { michael@0: Set visitedSet = mVisitedCache.get(); michael@0: if (visitedSet != null) { michael@0: visitedSet.add(uri); michael@0: } michael@0: GeckoAppShell.notifyUriVisited(uri); michael@0: } michael@0: michael@0: public void add(String uri) { michael@0: BrowserDB.updateVisitedHistory(GeckoAppShell.getContext().getContentResolver(), uri); michael@0: addToGeckoOnly(uri); michael@0: } michael@0: michael@0: public void update(String uri, String title) { michael@0: BrowserDB.updateHistoryTitle(GeckoAppShell.getContext().getContentResolver(), uri, title); michael@0: } michael@0: michael@0: public void checkUriVisited(final String uri) { michael@0: mHandler.post(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: // this runs on the same handler thread as the processing loop, michael@0: // so no synchronization needed michael@0: mPendingUris.add(uri); michael@0: if (mProcessing) { michael@0: // there's already a runnable queued up or working away, so michael@0: // no need to post another michael@0: return; michael@0: } michael@0: mProcessing = true; michael@0: mHandler.postDelayed(mNotifierRunnable, BATCHING_DELAY_MS); michael@0: } michael@0: }); michael@0: } michael@0: }