mobile/android/base/background/healthreport/upload/ObsoleteDocumentTracker.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 package org.mozilla.gecko.background.healthreport.upload;
michael@0 6
michael@0 7 import java.util.ArrayList;
michael@0 8 import java.util.Collection;
michael@0 9 import java.util.Collections;
michael@0 10 import java.util.Comparator;
michael@0 11 import java.util.HashSet;
michael@0 12 import java.util.List;
michael@0 13 import java.util.Map.Entry;
michael@0 14 import java.util.Set;
michael@0 15
michael@0 16 import org.mozilla.gecko.background.common.log.Logger;
michael@0 17 import org.mozilla.gecko.background.healthreport.HealthReportConstants;
michael@0 18 import org.mozilla.gecko.sync.ExtendedJSONObject;
michael@0 19
michael@0 20 import android.content.SharedPreferences;
michael@0 21
michael@0 22 public class ObsoleteDocumentTracker {
michael@0 23 public static final String LOG_TAG = ObsoleteDocumentTracker.class.getSimpleName();
michael@0 24
michael@0 25 protected final SharedPreferences sharedPrefs;
michael@0 26
michael@0 27 public ObsoleteDocumentTracker(SharedPreferences sharedPrefs) {
michael@0 28 this.sharedPrefs = sharedPrefs;
michael@0 29 }
michael@0 30
michael@0 31 protected ExtendedJSONObject getObsoleteIds() {
michael@0 32 String s = sharedPrefs.getString(HealthReportConstants.PREF_OBSOLETE_DOCUMENT_IDS_TO_DELETION_ATTEMPTS_REMAINING, null);
michael@0 33 if (s == null) {
michael@0 34 // It's possible we're migrating an old profile forward.
michael@0 35 String lastId = sharedPrefs.getString(HealthReportConstants.PREF_LAST_UPLOAD_DOCUMENT_ID, null);
michael@0 36 if (lastId == null) {
michael@0 37 return new ExtendedJSONObject();
michael@0 38 }
michael@0 39 ExtendedJSONObject ids = new ExtendedJSONObject();
michael@0 40 ids.put(lastId, HealthReportConstants.DELETION_ATTEMPTS_PER_OBSOLETE_DOCUMENT_ID);
michael@0 41 setObsoleteIds(ids);
michael@0 42 return ids;
michael@0 43 }
michael@0 44 try {
michael@0 45 return ExtendedJSONObject.parseJSONObject(s);
michael@0 46 } catch (Exception e) {
michael@0 47 Logger.warn(LOG_TAG, "Got exception getting obsolete ids.", e);
michael@0 48 return new ExtendedJSONObject();
michael@0 49 }
michael@0 50 }
michael@0 51
michael@0 52 /**
michael@0 53 * Write obsolete ids to disk.
michael@0 54 *
michael@0 55 * @param ids to write.
michael@0 56 */
michael@0 57 protected void setObsoleteIds(ExtendedJSONObject ids) {
michael@0 58 sharedPrefs
michael@0 59 .edit()
michael@0 60 .putString(HealthReportConstants.PREF_OBSOLETE_DOCUMENT_IDS_TO_DELETION_ATTEMPTS_REMAINING, ids.toString())
michael@0 61 .commit();
michael@0 62 }
michael@0 63
michael@0 64 /**
michael@0 65 * Remove id from set of obsolete document ids tracked for deletion.
michael@0 66 *
michael@0 67 * Public for testing.
michael@0 68 *
michael@0 69 * @param id to stop tracking.
michael@0 70 */
michael@0 71 public void removeObsoleteId(String id) {
michael@0 72 ExtendedJSONObject ids = getObsoleteIds();
michael@0 73 ids.remove(id);
michael@0 74 setObsoleteIds(ids);
michael@0 75 }
michael@0 76
michael@0 77 protected void decrementObsoleteId(ExtendedJSONObject ids, String id) {
michael@0 78 if (!ids.containsKey(id)) {
michael@0 79 return;
michael@0 80 }
michael@0 81 try {
michael@0 82 Long attempts = ids.getLong(id);
michael@0 83 if (attempts == null || --attempts < 1) {
michael@0 84 ids.remove(id);
michael@0 85 } else {
michael@0 86 ids.put(id, attempts);
michael@0 87 }
michael@0 88 } catch (ClassCastException e) {
michael@0 89 ids.remove(id);
michael@0 90 Logger.info(LOG_TAG, "Got exception decrementing obsolete ids counter.", e);
michael@0 91 }
michael@0 92 }
michael@0 93
michael@0 94 /**
michael@0 95 * Decrement attempts remaining for id in set of obsolete document ids tracked
michael@0 96 * for deletion.
michael@0 97 *
michael@0 98 * Public for testing.
michael@0 99 *
michael@0 100 * @param id to decrement attempts.
michael@0 101 */
michael@0 102 public void decrementObsoleteIdAttempts(String id) {
michael@0 103 ExtendedJSONObject ids = getObsoleteIds();
michael@0 104 decrementObsoleteId(ids, id);
michael@0 105 setObsoleteIds(ids);
michael@0 106 }
michael@0 107
michael@0 108 public void purgeObsoleteIds(Collection<String> oldIds) {
michael@0 109 ExtendedJSONObject ids = getObsoleteIds();
michael@0 110 for (String oldId : oldIds) {
michael@0 111 ids.remove(oldId);
michael@0 112 }
michael@0 113 setObsoleteIds(ids);
michael@0 114 }
michael@0 115
michael@0 116 public void decrementObsoleteIdAttempts(Collection<String> oldIds) {
michael@0 117 ExtendedJSONObject ids = getObsoleteIds();
michael@0 118 for (String oldId : oldIds) {
michael@0 119 decrementObsoleteId(ids, oldId);
michael@0 120 }
michael@0 121 setObsoleteIds(ids);
michael@0 122 }
michael@0 123
michael@0 124 /**
michael@0 125 * Sort Longs in decreasing order, moving null and non-Longs to the front.
michael@0 126 *
michael@0 127 * Public for testing only.
michael@0 128 */
michael@0 129 public static class PairComparator implements Comparator<Entry<String, Object>> {
michael@0 130 @Override
michael@0 131 public int compare(Entry<String, Object> lhs, Entry<String, Object> rhs) {
michael@0 132 Object l = lhs.getValue();
michael@0 133 Object r = rhs.getValue();
michael@0 134 if (l == null || !(l instanceof Long)) {
michael@0 135 if (r == null || !(r instanceof Long)) {
michael@0 136 return 0;
michael@0 137 }
michael@0 138 return -1;
michael@0 139 }
michael@0 140 if (r == null || !(r instanceof Long)) {
michael@0 141 return 1;
michael@0 142 }
michael@0 143 return ((Long) r).compareTo((Long) l);
michael@0 144 }
michael@0 145 }
michael@0 146
michael@0 147 /**
michael@0 148 * Return a batch of obsolete document IDs that should be deleted next.
michael@0 149 *
michael@0 150 * Document IDs are long and sending too many in a single request might
michael@0 151 * increase the likelihood of POST failures, so we delete a (deterministic)
michael@0 152 * subset here.
michael@0 153 *
michael@0 154 * @return a non-null collection.
michael@0 155 */
michael@0 156 public Collection<String> getBatchOfObsoleteIds() {
michael@0 157 ExtendedJSONObject ids = getObsoleteIds();
michael@0 158 // Sort by increasing order of key values.
michael@0 159 List<Entry<String, Object>> pairs = new ArrayList<Entry<String,Object>>(ids.entrySet());
michael@0 160 Collections.sort(pairs, new PairComparator());
michael@0 161 List<String> batch = new ArrayList<String>(HealthReportConstants.MAXIMUM_DELETIONS_PER_POST);
michael@0 162 int i = 0;
michael@0 163 while (batch.size() < HealthReportConstants.MAXIMUM_DELETIONS_PER_POST && i < pairs.size()) {
michael@0 164 batch.add(pairs.get(i++).getKey());
michael@0 165 }
michael@0 166 return batch;
michael@0 167 }
michael@0 168
michael@0 169 /**
michael@0 170 * Track the given document ID for eventual obsolescence and deletion.
michael@0 171 * Obsolete IDs are not known to have been uploaded to the server, so we just
michael@0 172 * give a best effort attempt at deleting them
michael@0 173 *
michael@0 174 * @param id to eventually delete.
michael@0 175 */
michael@0 176 public void addObsoleteId(String id) {
michael@0 177 ExtendedJSONObject ids = getObsoleteIds();
michael@0 178 if (ids.size() >= HealthReportConstants.MAXIMUM_STORED_OBSOLETE_DOCUMENT_IDS) {
michael@0 179 // Remove the one that's been tried the most and is least likely to be
michael@0 180 // known to be on the server. Since the comparator orders in decreasing
michael@0 181 // order, we take the max.
michael@0 182 ids.remove(Collections.max(ids.entrySet(), new PairComparator()).getKey());
michael@0 183 }
michael@0 184 ids.put(id, HealthReportConstants.DELETION_ATTEMPTS_PER_OBSOLETE_DOCUMENT_ID);
michael@0 185 setObsoleteIds(ids);
michael@0 186 }
michael@0 187
michael@0 188 /**
michael@0 189 * Track the given document ID for eventual obsolescence and deletion, and
michael@0 190 * give it priority since we know this ID has made it to the server, and we
michael@0 191 * definitely don't want to orphan it.
michael@0 192 *
michael@0 193 * @param id to eventually delete.
michael@0 194 */
michael@0 195 public void markIdAsUploaded(String id) {
michael@0 196 ExtendedJSONObject ids = getObsoleteIds();
michael@0 197 ids.put(id, HealthReportConstants.DELETION_ATTEMPTS_PER_KNOWN_TO_BE_ON_SERVER_DOCUMENT_ID);
michael@0 198 setObsoleteIds(ids);
michael@0 199 }
michael@0 200
michael@0 201 public boolean hasObsoleteIds() {
michael@0 202 return getObsoleteIds().size() > 0;
michael@0 203 }
michael@0 204
michael@0 205 public int numberOfObsoleteIds() {
michael@0 206 return getObsoleteIds().size();
michael@0 207 }
michael@0 208
michael@0 209 public String getNextObsoleteId() {
michael@0 210 ExtendedJSONObject ids = getObsoleteIds();
michael@0 211 if (ids.size() < 1) {
michael@0 212 return null;
michael@0 213 }
michael@0 214 try {
michael@0 215 // Delete the one that's most likely to be known to be on the server, and
michael@0 216 // that's not been tried as much. Since the comparator orders in
michael@0 217 // decreasing order, we take the min.
michael@0 218 return Collections.min(ids.entrySet(), new PairComparator()).getKey();
michael@0 219 } catch (Exception e) {
michael@0 220 Logger.warn(LOG_TAG, "Got exception picking obsolete id to delete.", e);
michael@0 221 return null;
michael@0 222 }
michael@0 223 }
michael@0 224
michael@0 225 /**
michael@0 226 * We want cleaning up documents on the server to be best effort. Purge badly
michael@0 227 * formed IDs and cap the number of times we try to delete so that the queue
michael@0 228 * doesn't take too long.
michael@0 229 */
michael@0 230 public void limitObsoleteIds() {
michael@0 231 ExtendedJSONObject ids = getObsoleteIds();
michael@0 232
michael@0 233 Set<String> keys = new HashSet<String>(ids.keySet()); // Avoid invalidating an iterator.
michael@0 234 for (String key : keys) {
michael@0 235 Object o = ids.get(key);
michael@0 236 if (!(o instanceof Long)) {
michael@0 237 continue;
michael@0 238 }
michael@0 239 if (((Long) o).longValue() > HealthReportConstants.DELETION_ATTEMPTS_PER_OBSOLETE_DOCUMENT_ID) {
michael@0 240 ids.put(key, HealthReportConstants.DELETION_ATTEMPTS_PER_OBSOLETE_DOCUMENT_ID);
michael@0 241 }
michael@0 242 }
michael@0 243 setObsoleteIds(ids);
michael@0 244 }
michael@0 245 }

mercurial