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.

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

mercurial