Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
michael@0 | 1 | /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- |
michael@0 | 2 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | package org.mozilla.gecko; |
michael@0 | 7 | |
michael@0 | 8 | import org.mozilla.gecko.db.BrowserDB; |
michael@0 | 9 | import org.mozilla.gecko.util.ThreadUtils; |
michael@0 | 10 | |
michael@0 | 11 | import android.database.Cursor; |
michael@0 | 12 | import android.net.Uri; |
michael@0 | 13 | import android.os.Handler; |
michael@0 | 14 | import android.util.Log; |
michael@0 | 15 | |
michael@0 | 16 | import java.lang.ref.SoftReference; |
michael@0 | 17 | import java.util.HashSet; |
michael@0 | 18 | import java.util.LinkedList; |
michael@0 | 19 | import java.util.Queue; |
michael@0 | 20 | import java.util.Set; |
michael@0 | 21 | |
michael@0 | 22 | class GlobalHistory { |
michael@0 | 23 | private static final String LOGTAG = "GeckoGlobalHistory"; |
michael@0 | 24 | |
michael@0 | 25 | private static GlobalHistory sInstance = new GlobalHistory(); |
michael@0 | 26 | |
michael@0 | 27 | static GlobalHistory getInstance() { |
michael@0 | 28 | return sInstance; |
michael@0 | 29 | } |
michael@0 | 30 | |
michael@0 | 31 | // this is the delay between receiving a URI check request and processing it. |
michael@0 | 32 | // this allows batching together multiple requests and processing them together, |
michael@0 | 33 | // which is more efficient. |
michael@0 | 34 | private static final long BATCHING_DELAY_MS = 100; |
michael@0 | 35 | |
michael@0 | 36 | private final Handler mHandler; // a background thread on which we can process requests |
michael@0 | 37 | private final Queue<String> mPendingUris; // URIs that need to be checked |
michael@0 | 38 | private SoftReference<Set<String>> mVisitedCache; // cache of the visited URI list |
michael@0 | 39 | private final Runnable mNotifierRunnable; // off-thread runnable used to check URIs |
michael@0 | 40 | private boolean mProcessing; // = false // whether or not the runnable is queued/working |
michael@0 | 41 | |
michael@0 | 42 | private GlobalHistory() { |
michael@0 | 43 | mHandler = ThreadUtils.getBackgroundHandler(); |
michael@0 | 44 | mPendingUris = new LinkedList<String>(); |
michael@0 | 45 | mVisitedCache = new SoftReference<Set<String>>(null); |
michael@0 | 46 | mNotifierRunnable = new Runnable() { |
michael@0 | 47 | @Override |
michael@0 | 48 | public void run() { |
michael@0 | 49 | Set<String> visitedSet = mVisitedCache.get(); |
michael@0 | 50 | if (visitedSet == null) { |
michael@0 | 51 | // the cache was wiped away, repopulate it |
michael@0 | 52 | Log.w(LOGTAG, "Rebuilding visited link set..."); |
michael@0 | 53 | visitedSet = new HashSet<String>(); |
michael@0 | 54 | Cursor c = null; |
michael@0 | 55 | try { |
michael@0 | 56 | c = BrowserDB.getAllVisitedHistory(GeckoAppShell.getContext().getContentResolver()); |
michael@0 | 57 | if (c == null) { |
michael@0 | 58 | return; |
michael@0 | 59 | } |
michael@0 | 60 | |
michael@0 | 61 | if (c.moveToFirst()) { |
michael@0 | 62 | do { |
michael@0 | 63 | visitedSet.add(c.getString(0)); |
michael@0 | 64 | } while (c.moveToNext()); |
michael@0 | 65 | } |
michael@0 | 66 | mVisitedCache = new SoftReference<Set<String>>(visitedSet); |
michael@0 | 67 | } finally { |
michael@0 | 68 | if (c != null) |
michael@0 | 69 | c.close(); |
michael@0 | 70 | } |
michael@0 | 71 | } |
michael@0 | 72 | |
michael@0 | 73 | // this runs on the same handler thread as the checkUriVisited code, |
michael@0 | 74 | // so no synchronization needed |
michael@0 | 75 | while (true) { |
michael@0 | 76 | String uri = mPendingUris.poll(); |
michael@0 | 77 | if (uri == null) { |
michael@0 | 78 | break; |
michael@0 | 79 | } |
michael@0 | 80 | if (visitedSet.contains(uri)) { |
michael@0 | 81 | GeckoAppShell.notifyUriVisited(uri); |
michael@0 | 82 | } |
michael@0 | 83 | } |
michael@0 | 84 | mProcessing = false; |
michael@0 | 85 | } |
michael@0 | 86 | }; |
michael@0 | 87 | } |
michael@0 | 88 | |
michael@0 | 89 | public void addToGeckoOnly(String uri) { |
michael@0 | 90 | Set<String> visitedSet = mVisitedCache.get(); |
michael@0 | 91 | if (visitedSet != null) { |
michael@0 | 92 | visitedSet.add(uri); |
michael@0 | 93 | } |
michael@0 | 94 | GeckoAppShell.notifyUriVisited(uri); |
michael@0 | 95 | } |
michael@0 | 96 | |
michael@0 | 97 | public void add(String uri) { |
michael@0 | 98 | BrowserDB.updateVisitedHistory(GeckoAppShell.getContext().getContentResolver(), uri); |
michael@0 | 99 | addToGeckoOnly(uri); |
michael@0 | 100 | } |
michael@0 | 101 | |
michael@0 | 102 | public void update(String uri, String title) { |
michael@0 | 103 | BrowserDB.updateHistoryTitle(GeckoAppShell.getContext().getContentResolver(), uri, title); |
michael@0 | 104 | } |
michael@0 | 105 | |
michael@0 | 106 | public void checkUriVisited(final String uri) { |
michael@0 | 107 | mHandler.post(new Runnable() { |
michael@0 | 108 | @Override |
michael@0 | 109 | public void run() { |
michael@0 | 110 | // this runs on the same handler thread as the processing loop, |
michael@0 | 111 | // so no synchronization needed |
michael@0 | 112 | mPendingUris.add(uri); |
michael@0 | 113 | if (mProcessing) { |
michael@0 | 114 | // there's already a runnable queued up or working away, so |
michael@0 | 115 | // no need to post another |
michael@0 | 116 | return; |
michael@0 | 117 | } |
michael@0 | 118 | mProcessing = true; |
michael@0 | 119 | mHandler.postDelayed(mNotifierRunnable, BATCHING_DELAY_MS); |
michael@0 | 120 | } |
michael@0 | 121 | }); |
michael@0 | 122 | } |
michael@0 | 123 | } |