--- a/configure.in
+++ b/configure.in
@@ -3795,17 +3795,16 @@ if test -n "$MOZ_RTSP"; then
fi
USE_ARM_KUSER=
BUILD_CTYPES=1
MOZ_USE_NATIVE_POPUP_WINDOWS=
MOZ_ANDROID_HISTORY=
MOZ_WEBSMS_BACKEND=
MOZ_ANDROID_BEAM=
MOZ_LOCALE_SWITCHER=
-MOZ_ANDROID_READING_LIST_SERVICE=
MOZ_ANDROID_SEARCH_ACTIVITY=
MOZ_ANDROID_DOWNLOADS_INTEGRATION=
MOZ_ANDROID_GCM=
MOZ_ANDROID_MLS_STUMBLER=
MOZ_EXCLUDE_HYPHENATION_DICTIONARIES=
MOZ_INSTALL_TRACKING=
MOZ_SWITCHBOARD=
ACCESSIBILITY=1
@@ -4874,23 +4873,16 @@ fi
dnl ========================================================
dnl = Include Search Activity on Android
dnl ========================================================
if test -n "$MOZ_ANDROID_SEARCH_ACTIVITY"; then
AC_DEFINE(MOZ_ANDROID_SEARCH_ACTIVITY)
fi
dnl ========================================================
-dnl = Include Reading List service on Android
-dnl ========================================================
-if test -n "$MOZ_ANDROID_READING_LIST_SERVICE"; then
- AC_DEFINE(MOZ_ANDROID_READING_LIST_SERVICE)
-fi
-
-dnl ========================================================
dnl = Include Mozilla Location Service Stumbler on Android
dnl ========================================================
if test -n "$MOZ_ANDROID_MLS_STUMBLER"; then
AC_DEFINE(MOZ_ANDROID_MLS_STUMBLER)
fi
dnl =========================================================
dnl = Whether to exclude hyphenations files in the build
@@ -8537,17 +8529,16 @@ AC_SUBST(MOZ_D3DCOMPILER_XP_CAB)
AC_SUBST(MOZ_ANDROID_HISTORY)
AC_SUBST(MOZ_WEBSMS_BACKEND)
AC_SUBST(MOZ_ANDROID_BEAM)
AC_SUBST(MOZ_LOCALE_SWITCHER)
AC_SUBST(MOZ_DISABLE_GECKOVIEW)
AC_SUBST(MOZ_ANDROID_GCM)
AC_SUBST(MOZ_ANDROID_GECKOLIBS_AAR)
-AC_SUBST(MOZ_ANDROID_READING_LIST_SERVICE)
AC_SUBST(MOZ_ANDROID_SEARCH_ACTIVITY)
AC_SUBST(MOZ_ANDROID_MLS_STUMBLER)
AC_SUBST(MOZ_ANDROID_DOWNLOADS_INTEGRATION)
AC_SUBST(MOZ_ANDROID_APPLICATION_CLASS)
AC_SUBST(MOZ_ANDROID_BROWSER_INTENT_CLASS)
AC_SUBST(MOZ_ANDROID_SEARCH_INTENT_CLASS)
AC_SUBST(MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE)
AC_SUBST(MOZ_EXCLUDE_HYPHENATION_DICTIONARIES)
--- a/mobile/android/base/AppConstants.java.in
+++ b/mobile/android/base/AppConstants.java.in
@@ -189,23 +189,16 @@ public class AppConstants {
public static final boolean MOZ_SERVICES_HEALTHREPORT =
//#ifdef MOZ_SERVICES_HEALTHREPORT
true;
//#else
false;
//#endif
- public static final boolean MOZ_ANDROID_READING_LIST_SERVICE =
-//#ifdef MOZ_ANDROID_READING_LIST_SERVICE
- true;
-//#else
- false;
-//#endif
-
public static final boolean MOZ_TELEMETRY_ON_BY_DEFAULT =
//#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
true;
//#else
false;
//#endif
public static final String TELEMETRY_PREF_NAME =
--- a/mobile/android/base/android-services.mozbuild
+++ b/mobile/android/base/android-services.mozbuild
@@ -1060,34 +1060,8 @@ sync_java_files = [TOPSRCDIR + '/mobile/
'sync/UnexpectedJSONException.java',
'sync/UnknownSynchronizerConfigurationVersionException.java',
'sync/Utils.java',
'tokenserver/TokenServerClient.java',
'tokenserver/TokenServerClientDelegate.java',
'tokenserver/TokenServerException.java',
'tokenserver/TokenServerToken.java',
]]
-reading_list_service_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozilla/gecko/' + x for x in [
- 'reading/ClientMetadata.java',
- 'reading/ClientReadingListRecord.java',
- 'reading/FetchSpec.java',
- 'reading/LocalReadingListStorage.java',
- 'reading/ReadingListBackoffObserver.java',
- 'reading/ReadingListChangeAccumulator.java',
- 'reading/ReadingListClient.java',
- 'reading/ReadingListClientContentValuesFactory.java',
- 'reading/ReadingListClientRecordFactory.java',
- 'reading/ReadingListDeleteDelegate.java',
- 'reading/ReadingListInvalidAuthenticationException.java',
- 'reading/ReadingListRecord.java',
- 'reading/ReadingListRecordDelegate.java',
- 'reading/ReadingListRecordResponse.java',
- 'reading/ReadingListRecordUploadDelegate.java',
- 'reading/ReadingListResponse.java',
- 'reading/ReadingListStorage.java',
- 'reading/ReadingListStorageResponse.java',
- 'reading/ReadingListSyncAdapter.java',
- 'reading/ReadingListSynchronizer.java',
- 'reading/ReadingListSynchronizerDelegate.java',
- 'reading/ReadingListSyncService.java',
- 'reading/ReadingListWipeDelegate.java',
- 'reading/ServerReadingListRecord.java',
-]]
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -662,20 +662,16 @@ else:
if max_sdk_version >= 11:
gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
'tabs/TabStrip.java',
'tabs/TabStripAdapter.java',
'tabs/TabStripItemView.java',
'tabs/TabStripView.java'
]]
-# Selectively include reading list service code.
-if CONFIG['MOZ_ANDROID_READING_LIST_SERVICE']:
- gbjar.sources += reading_list_service_java_files
-
gbjar.extra_jars += [
OBJDIR + '/../javaaddons/javaaddons-1.0.jar',
'gecko-R.jar',
'gecko-mozglue.jar',
'gecko-thirdparty.jar',
'gecko-util.jar',
'sync-thirdparty.jar',
'services.jar',
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AndroidFxAccount.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AndroidFxAccount.java
@@ -82,19 +82,16 @@ public class AndroidFxAccount {
// Services may request OAuth tokens from the Firefox Account dynamically.
// Each such token is prefixed with "oauth::" and a service-dependent scope.
// Such tokens should be destroyed when the account is removed from the device.
// This list collects all the known "oauth::" token types in order to delete them when necessary.
private static final List<String> KNOWN_OAUTH_TOKEN_TYPES;
static {
final List<String> list = new ArrayList<>();
- if (AppConstants.MOZ_ANDROID_READING_LIST_SERVICE) {
- list.add(ReadingListConstants.AUTH_TOKEN_TYPE);
- }
list.add(PROFILE_OAUTH_TOKEN_TYPE);
KNOWN_OAUTH_TOKEN_TYPES = Collections.unmodifiableList(list);
}
public static final Map<String, Boolean> DEFAULT_AUTHORITIES_TO_SYNC_AUTOMATICALLY_MAP;
static {
final HashMap<String, Boolean> m = new HashMap<String, Boolean>();
// By default, Firefox Sync is enabled.
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ClientMetadata.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/* 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.reading;
-
-public class ClientMetadata {
- public final long id; // A client numeric ID. We don't always have a GUID.
- public final long lastModified; // A client timestamp.
- public final boolean isDeleted;
- public final boolean isArchived;
-
- public ClientMetadata(final long id, final long lastModified, final boolean isDeleted, final boolean isArchived) {
- this.id = id;
- this.lastModified = lastModified;
- this.isDeleted = isDeleted;
- this.isArchived = isArchived;
- }
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ClientReadingListRecord.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/* 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.reading;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-public class ClientReadingListRecord extends ReadingListRecord {
- final ExtendedJSONObject fields;
- public ClientMetadata clientMetadata;
-
- private String getDefaultAddedBy() {
- return "Test Device"; // TODO
- }
-
- /**
- * Provided `fields` are *not copied*.
- */
- public ClientReadingListRecord(final ServerMetadata serverMetadata, final ClientMetadata clientMetadata, final ExtendedJSONObject fields) {
- super(serverMetadata);
- this.clientMetadata = clientMetadata == null ? new ClientMetadata(-1L, -1L, false, false) : clientMetadata;
- this.fields = fields;
- }
-
- public ClientReadingListRecord(String url, String title, String addedBy) {
- this(url, title, addedBy, System.currentTimeMillis(), false, false);
- }
-
- public ClientReadingListRecord(String url, String title, String addedBy, long lastModified, boolean isDeleted, boolean isArchived) {
- super(null);
-
- // Required.
- if (url == null) {
- throw new IllegalArgumentException("url must be provided.");
- }
-
- final ExtendedJSONObject f = new ExtendedJSONObject();
- f.put("url", url);
- f.put("title", title == null ? "" : title);
- f.put("added_by", addedBy == null ? getDefaultAddedBy() : addedBy);
-
- this.fields = f;
- this.clientMetadata = new ClientMetadata(-1L, lastModified, isDeleted, isArchived);
- }
-
- public ExtendedJSONObject toJSON() {
- final ExtendedJSONObject object = this.fields.deepCopy();
- final String guid = getGUID();
-
- if (guid != null) {
- object.put("id", guid);
- }
- return object;
- }
-
- @Override
- public String getAddedBy() {
- return this.fields.getString("added_by");
- }
-
- @Override
- public String getURL() {
- return this.fields.getString("url"); // TODO: resolved_url
- }
-
- @Override
- public String getTitle() {
- return this.fields.getString("title"); // TODO: resolved_title
- }
-
- /**
- * Produce a record just like the server record, but with the
- * appropriate additional metadata, such as the local numeric ID.
- */
- public ClientReadingListRecord givenServerRecord(ServerReadingListRecord down) {
- return new ClientReadingListRecord(down.serverMetadata, this.clientMetadata, down.fields);
- }
-}
\ No newline at end of file
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/FetchSpec.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/* 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.reading;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-
-import ch.boye.httpclientandroidlib.client.utils.URIBuilder;
-
-/**
- * Defines the parameters that can be added to a reading list fetch URI.
- */
-public class FetchSpec {
- private final String queryString;
-
- private FetchSpec(final String q) {
- this.queryString = q;
- }
-
- public URI getURI(final URI serviceURI) throws URISyntaxException {
- return new URIBuilder(serviceURI).setCustomQuery(queryString).build();
- }
-
- public URI getURI(final URI serviceURI, final String path) throws URISyntaxException {
- final String currentPath = serviceURI.getPath();
- final String newPath = (currentPath == null ? "" : currentPath) + path;
- return new URIBuilder(serviceURI).setPath(newPath)
- .setCustomQuery(queryString)
- .build();
- }
-
- public static class Builder {
- final StringBuilder b = new StringBuilder();
- boolean first = true;
-
- public FetchSpec build() {
- return new FetchSpec(b.toString());
- }
-
- private void ampersand() {
- if (first) {
- first = false;
- return;
- }
- b.append('&');
- }
-
- public Builder setUnread(boolean unread) {
- ampersand();
- b.append("unread=");
- b.append(unread);
- return this;
- }
-
- private void qualifyAttribute(String qual, String attr) {
- ampersand();
- b.append(qual);
- b.append(attr);
- b.append('=');
- }
-
- public Builder setMinAttribute(String attr, int val) {
- qualifyAttribute("min_", attr);
- b.append(val);
- return this;
- }
-
- public Builder setMaxAttribute(String attr, int val) {
- qualifyAttribute("max_", attr);
- b.append(val);
- return this;
- }
-
- public Builder setNotAttribute(String attr, String val) {
- qualifyAttribute("not_", attr);
- b.append(val);
- return this;
- }
-
- public Builder setSince(long since) {
- if (since == -1L) {
- return this;
- }
-
- ampersand();
- b.append("_since=");
- b.append(since);
- return this;
- }
-
- public Builder setExcludeDeleted() {
- ampersand();
- b.append("not_deleted=true");
- return this;
- }
- }
-}
\ No newline at end of file
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/LocalReadingListStorage.java
+++ /dev/null
@@ -1,436 +0,0 @@
-/* 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.reading;
-
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_CHANGE_FAVORITE_CHANGED;
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_CHANGE_FLAGS;
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_CHANGE_UNREAD_CHANGED;
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_STATUS;
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_STATUS_MODIFIED;
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_STATUS_NEW;
-
-import java.util.ArrayList;
-import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
-import org.mozilla.gecko.sync.repositories.android.RepoUtils;
-
-import android.content.ContentProviderClient;
-import android.content.ContentProviderOperation;
-import android.content.ContentProviderResult;
-import android.content.ContentValues;
-import android.content.OperationApplicationException;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.RemoteException;
-
-public class LocalReadingListStorage implements ReadingListStorage {
-
- private static final String WHERE_STATUS_NEW = "(" + SYNC_STATUS + " = " + SYNC_STATUS_NEW + ")";
-
- final class LocalReadingListChangeAccumulator implements ReadingListChangeAccumulator {
- private static final String LOG_TAG = "RLChanges";
-
- /**
- * These are changes that result from uploading new or changed records to the server.
- * They always correspond to local records.
- */
- private final Queue<ClientReadingListRecord> changes;
-
- /**
- * These are deletions that result from uploading new or changed records to the server.
- * They should always correspond to local records.
- * These are not common: they should only occur if a conflict occurs.
- */
- private final Queue<ClientReadingListRecord> deletions;
- private final Queue<String> deletedGUIDs;
-
- /**
- * These are additions or changes fetched from the server.
- * At the point of collection we don't know if they're records
- * that exist locally.
- *
- * Batching these here, rather than in the client or the synchronizer,
- * puts the storage implementation in control of when batches are flushed,
- * or if batches are used at all.
- */
- private final Queue<ServerReadingListRecord> additionsOrChanges;
-
- LocalReadingListChangeAccumulator() {
- this.changes = new ConcurrentLinkedQueue<>();
- this.deletions = new ConcurrentLinkedQueue<>();
- this.deletedGUIDs = new ConcurrentLinkedQueue<>();
- this.additionsOrChanges = new ConcurrentLinkedQueue<>();
- }
-
- public boolean flushDeletions() throws RemoteException {
- if (deletions.isEmpty() && deletedGUIDs.isEmpty()) {
- return true;
- }
-
- long[] ids = new long[deletions.size()];
- String[] guids = new String[deletions.size() + deletedGUIDs.size()];
- int iID = 0;
- int iGUID = 0;
- for (ClientReadingListRecord record : deletions) {
- if (record.clientMetadata.id > -1L) {
- ids[iID++] = record.clientMetadata.id;
- } else {
- final String guid = record.getGUID();
- if (guid == null) {
- continue;
- }
- guids[iGUID++] = guid;
- }
- }
- for (String guid : deletedGUIDs) {
- guids[iGUID++] = guid;
- }
-
- if (iID > 0) {
- client.delete(URI_WITH_DELETED, RepoUtils.computeSQLLongInClause(ids, ReadingListItems._ID), null);
- }
-
- if (iGUID > 0) {
- client.delete(URI_WITH_DELETED, RepoUtils.computeSQLInClause(iGUID, ReadingListItems.GUID), guids);
- }
-
- deletions.clear();
- deletedGUIDs.clear();
- return true;
- }
-
- public boolean flushRecordChanges() throws RemoteException {
- if (changes.isEmpty() && additionsOrChanges.isEmpty()) {
- return true;
- }
-
- // For each returned record, apply it to the local store and clear all sync flags.
- // We can do this because the server always returns the entire record.
- //
- // <https://github.com/mozilla-services/readinglist/issues/138> tracks not doing so
- // for certain patches, which allows us to optimize here.
- ArrayList<ContentProviderOperation> operations = new ArrayList<>(changes.size() + additionsOrChanges.size());
- for (ClientReadingListRecord rec : changes) {
- operations.add(makeUpdateOp(rec));
- }
-
- for (ServerReadingListRecord rec : additionsOrChanges) {
- // TODO: skip records for which the local copy of the server timestamp
- // matches the timestamp in the incoming record.
- // TODO: we can do this by maintaining a lookup table, rather
- // than hitting the DB. When we do an insert after an upload, say, we
- // can make a note of it so the next download flush doesn't apply it twice.
- operations.add(makeUpdateOrInsertOp(rec));
- }
-
- // TODO: tell delegate of success or failure.
- try {
- Logger.debug(LOG_TAG, "Applying " + operations.size() + " operations.");
- ContentProviderResult[] results = client.applyBatch(operations);
- } catch (OperationApplicationException e) {
- // Oops.
- Logger.warn(LOG_TAG, "Applying operations failed.", e);
- return false;
- }
- return true;
- }
-
- private ContentProviderOperation makeUpdateOrInsertOp(ServerReadingListRecord rec) throws RemoteException {
- final ClientReadingListRecord clientRec = new ClientReadingListRecord(rec.serverMetadata, null, rec.fields);
-
- // TODO: use UPDATE OR INSERT equivalent, rather than querying here.
- if (hasGUID(rec.serverMetadata.guid)) {
- return makeUpdateOp(clientRec);
- }
-
- final ContentValues values = ReadingListClientContentValuesFactory.fromClientRecord(clientRec);
- return ContentProviderOperation.newInsert(URI_WITHOUT_DELETED)
- .withValues(values)
- .build();
- }
-
- private ContentProviderOperation makeUpdateOp(ClientReadingListRecord rec) {
- // We can't use SQLiteQueryBuilder, because it can't do UPDATE,
- // nor can it give us a WHERE clause.
- final StringBuilder selection = new StringBuilder();
- final String[] selectionArgs;
-
- // We don't apply changes that we've already applied.
- // We know they've already been applied because our local copy of the
- // server's version code/timestamp matches the value in the incoming record.
- long serverLastModified = rec.getServerLastModified();
- if (serverLastModified != -1L) {
- // This should always be the case here.
- selection.append("(" + ReadingListItems.SERVER_LAST_MODIFIED + " IS NOT ");
- selection.append(serverLastModified);
- selection.append(") AND ");
- }
-
- if (rec.clientMetadata.id > -1L) {
- selection.append("(");
- selection.append(ReadingListItems._ID + " = ");
- selection.append(rec.clientMetadata.id);
- selection.append(")");
- selectionArgs = null;
- } else if (rec.serverMetadata.guid != null) {
- selection.append("(" + ReadingListItems.GUID + " = ?)");
- selectionArgs = new String[] { rec.serverMetadata.guid };
- } else {
- final String url = rec.fields.getString("url");
- final String resolvedURL = rec.fields.getString("resolved_url");
-
- if (url == null && resolvedURL == null) {
- // We're outta luck.
- return null;
- }
-
- selection.append("((" + ReadingListItems.URL + " = ?) OR (" + ReadingListItems.RESOLVED_URL + " = ?))");
- if (url != null && resolvedURL != null) {
- selectionArgs = new String[] { url, resolvedURL };
- } else {
- final String arg = url == null ? resolvedURL : url;
- selectionArgs = new String[] { arg, arg };
- }
- }
-
- final ContentValues values = ReadingListClientContentValuesFactory.fromClientRecord(rec);
- return ContentProviderOperation.newUpdate(URI_WITHOUT_DELETED)
- .withSelection(selection.toString(), selectionArgs)
- .withValues(values)
- .build();
- }
-
- @Override
- public void finish() throws Exception {
- flushDeletions();
- flushRecordChanges();
- }
-
- @Override
- public void addDeletion(ClientReadingListRecord record) {
- deletions.add(record);
- }
-
- @Override
- public void addDeletion(String guid) {
- deletedGUIDs.add(guid);
- }
-
- @Override
- public void addChangedRecord(ClientReadingListRecord record) {
- changes.add(record);
- }
-
- @Override
- public void addDownloadedRecord(ServerReadingListRecord down) {
- final Boolean deleted = down.fields.getBoolean("deleted");
- if (deleted != null && deleted.booleanValue()) {
- addDeletion(down.getGUID());
- } else {
- additionsOrChanges.add(down);
- }
- }
- }
-
- private final ContentProviderClient client;
- private final Uri URI_WITHOUT_DELETED = BrowserContract.READING_LIST_AUTHORITY_URI
- .buildUpon()
- .appendPath("items")
- .appendQueryParameter(BrowserContract.PARAM_IS_SYNC, "1")
- .appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "0")
- .build();
-
- private final Uri URI_WITH_DELETED = BrowserContract.READING_LIST_AUTHORITY_URI
- .buildUpon()
- .appendPath("items")
- .appendQueryParameter(BrowserContract.PARAM_IS_SYNC, "1")
- .appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "1")
- .build();
-
- public LocalReadingListStorage(final ContentProviderClient client) {
- this.client = client;
- }
-
- public boolean hasGUID(String guid) throws RemoteException {
- final String[] projection = new String[] { ReadingListItems.GUID };
- final String selection = ReadingListItems.GUID + " = ?";
- final String[] selectionArgs = new String[] { guid };
- final Cursor cursor = this.client.query(URI_WITHOUT_DELETED, projection, selection, selectionArgs, null);
- try {
- return cursor.moveToFirst();
- } finally {
- cursor.close();
- }
- }
-
- public Cursor getModifiedWithSelection(final String selection) {
- final String[] projection = new String[] {
- ReadingListItems.GUID,
- ReadingListItems.IS_FAVORITE,
- ReadingListItems.RESOLVED_TITLE,
- ReadingListItems.RESOLVED_URL,
- ReadingListItems.EXCERPT,
- // TODO: ReadingListItems.IS_ARTICLE,
- // TODO: ReadingListItems.WORD_COUNT,
- };
-
- try {
- return client.query(URI_WITHOUT_DELETED, projection, selection, null, null);
- } catch (RemoteException e) {
- throw new IllegalStateException(e);
- }
- }
-
- @Override
- public Cursor getModified() {
- final String selection = ReadingListItems.SYNC_STATUS + " = " + ReadingListItems.SYNC_STATUS_MODIFIED;
- return getModifiedWithSelection(selection);
- }
-
- // Return changed items that aren't just status changes.
- // This isn't necessary because we insist on processing status changes before modified items.
- // Currently we only need this for tests...
- public Cursor getNonStatusModified() {
- final String selection = ReadingListItems.SYNC_STATUS + " = " + ReadingListItems.SYNC_STATUS_MODIFIED +
- " AND ((" + ReadingListItems.SYNC_CHANGE_FLAGS + " & " + ReadingListItems.SYNC_CHANGE_RESOLVED + ") > 0)";
-
- return getModifiedWithSelection(selection);
- }
-
- // These will never conflict (in the case of unread status changes), or
- // we don't care if they overwrite the server value (in the case of favorite changes).
- // N.B., don't actually send each field if the appropriate change flag isn't set!
- @Override
- public Cursor getStatusChanges() {
- final String[] projection = new String[] {
- ReadingListItems.GUID,
- ReadingListItems.IS_FAVORITE,
- ReadingListItems.IS_UNREAD,
- ReadingListItems.MARKED_READ_BY,
- ReadingListItems.MARKED_READ_ON,
- ReadingListItems.SYNC_CHANGE_FLAGS,
- };
-
- final String selection =
- SYNC_STATUS + " = " + SYNC_STATUS_MODIFIED + " AND " +
- "((" + SYNC_CHANGE_FLAGS + " & (" + SYNC_CHANGE_UNREAD_CHANGED + " | " + SYNC_CHANGE_FAVORITE_CHANGED + ")) > 0)";
-
- try {
- return client.query(URI_WITHOUT_DELETED, projection, selection, null, null);
- } catch (RemoteException e) {
- throw new IllegalStateException(e);
- }
- }
-
- @Override
- public Cursor getDeletedItems() {
- final String[] projection = new String[] {
- ReadingListItems.GUID,
- };
-
- final String selection = "(" + ReadingListItems.IS_DELETED + " = 1) AND (" + ReadingListItems.GUID + " IS NOT NULL)";
- try {
- return client.query(URI_WITH_DELETED, projection, selection, null, null);
- } catch (RemoteException e) {
- throw new IllegalStateException(e);
- }
- }
-
- @Override
- public Cursor getNew() {
- // N.B., query for items that have no GUID, regardless of status.
- // They should all be marked as NEW, but belt and braces.
- final String selection = WHERE_STATUS_NEW + " OR (" + ReadingListItems.GUID + " IS NULL)";
-
- try {
- return client.query(URI_WITHOUT_DELETED, null, selection, null, null);
- } catch (RemoteException e) {
- throw new IllegalStateException(e);
- }
- }
-
- @Override
- public Cursor getAll() {
- try {
- return client.query(URI_WITHOUT_DELETED, null, null, null, null);
- } catch (RemoteException e) {
- throw new IllegalStateException(e);
- }
- }
-
- private ContentProviderOperation updateAddedByNames(final String local) {
- String[] selectionArgs = new String[] {"$local"};
- String selection = WHERE_STATUS_NEW + " AND (" + ReadingListItems.ADDED_BY + " = ?)";
- return ContentProviderOperation.newUpdate(URI_WITHOUT_DELETED)
- .withValue(ReadingListItems.ADDED_BY, local)
- .withSelection(selection, selectionArgs)
- .build();
- }
-
- private ContentProviderOperation updateMarkedReadByNames(final String local) {
- String[] selectionArgs = new String[] {"$local"};
- String selection = ReadingListItems.MARKED_READ_BY + " = ?";
- return ContentProviderOperation.newUpdate(URI_WITHOUT_DELETED)
- .withValue(ReadingListItems.MARKED_READ_BY, local)
- .withSelection(selection, selectionArgs)
- .build();
- }
-
- /**
- * Consumers of the reading list provider don't know the device name.
- * Rather than smearing that logic into callers, or requiring the database
- * to be able to figure out the name of the device, we have the SyncAdapter
- * do it.
- *
- * After all, the SyncAdapter knows everything -- prefs, channels, profiles,
- * Firefox Account details, etc.
- *
- * To allow this, the CP writes the magic string "$local" wherever a device
- * name is needed. Here in storage, we run a quick UPDATE pass prior to
- * synchronizing, so the device name is 'calcified' at the time of the first
- * sync of that record. The SyncAdapter calls this prior to invoking the
- * synchronizer.
- */
- public void updateLocalNames(final String local) {
- ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(2);
- ops.add(updateAddedByNames(local));
- ops.add(updateMarkedReadByNames(local));
-
- try {
- client.applyBatch(ops);
- } catch (RemoteException e) {
- return;
- } catch (OperationApplicationException e) {
- return;
- }
- }
-
- @Override
- public ReadingListChangeAccumulator getChangeAccumulator() {
- return new LocalReadingListChangeAccumulator();
- }
-
- /**
- * Unused: we implicitly do this when we apply the server record.
- */
- /*
- public void markStatusChangedItemsAsSynced(Collection<String> uploaded) {
- ContentValues values = new ContentValues();
- values.put(ReadingListItems.SYNC_CHANGE_FLAGS, ReadingListItems.SYNC_CHANGE_NONE);
- values.put(ReadingListItems.SYNC_STATUS, ReadingListItems.SYNC_STATUS_SYNCED);
- final String where = RepoUtils.computeSQLInClause(uploaded.size(), ReadingListItems.GUID);
- final String[] args = uploaded.toArray(new String[uploaded.size()]);
- try {
- client.update(URI_WITHOUT_DELETED, values, where, args);
- } catch (RemoteException e) {
- // Nothing we can do.
- }
- }
- */
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListBackoffObserver.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/* 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.reading;
-
-import java.util.concurrent.atomic.AtomicLong;
-
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.net.HttpResponseObserver;
-import org.mozilla.gecko.sync.net.MozResponse;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
-
-public class ReadingListBackoffObserver implements HttpResponseObserver {
- protected final String host;
- protected final AtomicLong largestBackoffObservedInSeconds = new AtomicLong(-1);
-
- public ReadingListBackoffObserver(String host) {
- this.host = host;
- Utils.throwIfNull(host);
- }
-
- @Override
- public void observeHttpResponse(HttpUriRequest request, HttpResponse response) {
- // Ignore non-Reading List storage requests.
- if (!host.equals(request.getURI().getHost())) {
- return;
- }
-
- final MozResponse res = new MozResponse(response);
- long backoffInSeconds = -1;
- try {
- backoffInSeconds = Math.max(res.backoffInSeconds(), res.retryAfterInSeconds());
- } catch (NumberFormatException e) {
- // Ignore.
- }
-
- if (backoffInSeconds <= 0) {
- return;
- }
-
- while (true) {
- long existingBackoff = largestBackoffObservedInSeconds.get();
- if (existingBackoff >= backoffInSeconds) {
- return;
- }
- if (largestBackoffObservedInSeconds.compareAndSet(existingBackoff, backoffInSeconds)) {
- return;
- }
- }
- }
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListChangeAccumulator.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/* 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.reading;
-
-/**
- * Grab one of these, then you can add records to it by parsing
- * server responses. Finishing it will flush those changes (e.g.,
- * via UPDATE) to the DB.
- */
-public interface ReadingListChangeAccumulator {
- void addDeletion(String guid);
- void addDeletion(ClientReadingListRecord record);
-
- // addChangedRecord is also used to apply the server's reconciliation results after upload.
- void addChangedRecord(ClientReadingListRecord record);
- void addDownloadedRecord(ServerReadingListRecord down);
- void finish() throws Exception;
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListClient.java
+++ /dev/null
@@ -1,689 +0,0 @@
-/* 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.reading;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.security.GeneralSecurityException;
-import java.util.Queue;
-import java.util.concurrent.Executor;
-
-import org.mozilla.gecko.background.ReadingListConstants;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.reading.ReadingListResponse.ResponseFactory;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.BaseResource;
-import org.mozilla.gecko.sync.net.BaseResourceDelegate;
-import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
-import org.mozilla.gecko.sync.net.MozResponse;
-import org.mozilla.gecko.sync.net.Resource;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.client.ClientProtocolException;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-
-/**
- * This client exposes an API for the reading list service, documented at
- * https://github.com/mozilla-services/readinglist/
- */
-public class ReadingListClient {
- static final String LOG_TAG = ReadingListClient.class.getSimpleName();
- private final AuthHeaderProvider auth;
-
- private final URI articlesURI; // .../articles
- private final URI articlesBaseURI; // .../articles/
-
- /**
- * Use a {@link BasicAuthHeaderProvider} for testing, and an FxA OAuth provider for the real service.
- */
- public ReadingListClient(final URI serviceURI, final AuthHeaderProvider auth) {
- this.articlesURI = serviceURI.resolve("articles");
- this.articlesBaseURI = serviceURI.resolve("articles/");
- this.auth = auth;
- }
-
- private BaseResource getRelativeArticleResource(final String rel) {
- return new BaseResource(this.articlesBaseURI.resolve(rel));
- }
-
- private static final class DelegatingUploadResourceDelegate extends UploadResourceDelegate<ReadingListRecordResponse> {
- private final ClientReadingListRecord up;
- private final ReadingListRecordUploadDelegate uploadDelegate;
-
- DelegatingUploadResourceDelegate(Resource resource,
- AuthHeaderProvider auth,
- ResponseFactory<ReadingListRecordResponse> factory,
- ClientReadingListRecord up,
- ReadingListRecordUploadDelegate uploadDelegate) {
- super(resource, auth, factory);
- this.up = up;
- this.uploadDelegate = uploadDelegate;
- }
-
- @Override
- void onFailure(MozResponse response) {
- Logger.warn(LOG_TAG, "Upload got failure response " + response.httpResponse().getStatusLine());
- response.logResponseBody(LOG_TAG);
- if (response.getStatusCode() == 400) {
- // Error response.
- uploadDelegate.onBadRequest(up, response);
- } else {
- uploadDelegate.onFailure(up, response);
- }
- }
-
- @Override
- void onFailure(Exception ex) {
- Logger.warn(LOG_TAG, "Upload failed.", ex);
- uploadDelegate.onFailure(up, ex);
- }
-
- @Override
- void onSuccess(ReadingListRecordResponse response) {
- Logger.debug(LOG_TAG, "Upload: onSuccess: " + response.httpResponse().getStatusLine());
- final ServerReadingListRecord down;
- try {
- down = response.getRecord();
- Logger.debug(LOG_TAG, "Upload succeeded. Got GUID " + down.getGUID());
- } catch (Exception e) {
- uploadDelegate.onFailure(up, e);
- return;
- }
-
- uploadDelegate.onSuccess(up, response, down);
- }
-
- @Override
- void onSeeOther(ReadingListRecordResponse response) {
- uploadDelegate.onConflict(up, response);
- }
- }
-
- private static abstract class ReadingListResourceDelegate<T extends ReadingListResponse> extends BaseResourceDelegate {
- private final ReadingListResponse.ResponseFactory<T> factory;
- private final AuthHeaderProvider auth;
-
- public ReadingListResourceDelegate(Resource resource, AuthHeaderProvider auth, ReadingListResponse.ResponseFactory<T> factory) {
- super(resource);
- this.auth = auth;
- this.factory = factory;
- }
-
- abstract void onSuccess(T response);
- abstract void onNonSuccess(T response);
- abstract void onFailure(MozResponse response);
- abstract void onFailure(Exception ex);
-
- @Override
- public void handleHttpResponse(HttpResponse response) {
- final T resp = factory.getResponse(response);
- if (resp.wasSuccessful()) {
- onSuccess(resp);
- } else {
- onNonSuccess(resp);
- }
- }
-
- @Override
- public void handleTransportException(GeneralSecurityException e) {
- onFailure(e);
- }
-
- @Override
- public void handleHttpProtocolException(ClientProtocolException e) {
- onFailure(e);
- }
-
- @Override
- public void handleHttpIOException(IOException e) {
- onFailure(e);
- }
-
- @Override
- public String getUserAgent() {
- return ReadingListConstants.USER_AGENT;
- }
-
- @Override
- public AuthHeaderProvider getAuthHeaderProvider() {
- return auth;
- }
-
- @Override
- public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
- }
- }
-
- /**
- * An intermediate delegate class that handles all of the shared storage behavior,
- * such as handling If-Modified-Since.
- */
- private static abstract class StorageResourceDelegate<T extends ReadingListResponse> extends ReadingListResourceDelegate<T> {
- private final long ifModifiedSince;
-
- public StorageResourceDelegate(Resource resource,
- AuthHeaderProvider auth,
- ReadingListResponse.ResponseFactory<T> factory,
- long ifModifiedSince) {
- super(resource, auth, factory);
- this.ifModifiedSince = ifModifiedSince;
- }
-
- @Override
- public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
- if (ifModifiedSince != -1L) {
- // TODO: format?
- request.addHeader("If-Modified-Since", "" + ifModifiedSince);
- }
- super.addHeaders(request, client);
- }
- }
-
- /**
- * Wraps the @{link ReadingListRecordDelegate} interface to yield a {@link StorageResourceDelegate}.
- */
- private static abstract class RecordResourceDelegate<T extends ReadingListResponse> extends StorageResourceDelegate<T> {
- protected final ReadingListRecordDelegate recordDelegate;
-
- public RecordResourceDelegate(Resource resource,
- AuthHeaderProvider auth,
- ReadingListRecordDelegate recordDelegate,
- ReadingListResponse.ResponseFactory<T> factory,
- long ifModifiedSince) {
- super(resource, auth, factory, ifModifiedSince);
- this.recordDelegate = recordDelegate;
- }
-
- abstract void onNotFound(ReadingListResponse resp);
-
- @Override
- void onNonSuccess(T resp) {
- Logger.debug(LOG_TAG, "Got non-success record response " + resp.getStatusCode());
- resp.logResponseBody(LOG_TAG);
-
- switch (resp.getStatusCode()) {
- case 304:
- onNotModified(resp);
- break;
- case 404:
- onNotFound(resp);
- break;
- default:
- onFailure(resp);
- }
- }
-
- @Override
- void onFailure(MozResponse response) {
- recordDelegate.onFailure(response);
- }
-
- @Override
- void onFailure(Exception ex) {
- recordDelegate.onFailure(ex);
- }
-
- void onNotModified(T resp) {
- recordDelegate.onComplete(resp);
- }
- }
-
- private static final class SingleRecordResourceDelegate extends RecordResourceDelegate<ReadingListRecordResponse> {
- private final String guid;
-
- SingleRecordResourceDelegate(Resource resource,
- AuthHeaderProvider auth,
- ReadingListRecordDelegate recordDelegate,
- ResponseFactory<ReadingListRecordResponse> factory,
- long ifModifiedSince, String guid) {
- super(resource, auth, recordDelegate, factory, ifModifiedSince);
- this.guid = guid;
- }
-
- @Override
- void onSuccess(ReadingListRecordResponse response) {
- final ServerReadingListRecord record;
- try {
- record = response.getRecord();
- } catch (Exception e) {
- recordDelegate.onFailure(e);
- return;
- }
-
- recordDelegate.onRecordReceived(record);
- recordDelegate.onComplete(response);
- }
-
- @Override
- void onNotFound(ReadingListResponse resp) {
- recordDelegate.onRecordMissingOrDeleted(guid, resp);
- }
- }
-
- private static final class MultipleRecordResourceDelegate extends RecordResourceDelegate<ReadingListStorageResponse> {
- MultipleRecordResourceDelegate(Resource resource,
- AuthHeaderProvider auth,
- ReadingListRecordDelegate recordDelegate,
- ResponseFactory<ReadingListStorageResponse> factory,
- long ifModifiedSince) {
- super(resource, auth, recordDelegate, factory, ifModifiedSince);
- }
-
- @Override
- void onSuccess(ReadingListStorageResponse response) {
- try {
- final Iterable<ServerReadingListRecord> records = response.getRecords();
- for (ServerReadingListRecord readingListRecord : records) {
- recordDelegate.onRecordReceived(readingListRecord);
- }
- } catch (Exception e) {
- recordDelegate.onFailure(e);
- return;
- }
-
- recordDelegate.onComplete(response);
- }
-
- @Override
- void onNotFound(ReadingListResponse resp) {
- // Should not occur against articlesURI root.
- recordDelegate.onFailure(resp);
- }
- }
-
- private static abstract class UploadResourceDelegate<T extends ReadingListResponse> extends StorageResourceDelegate<T> {
- public UploadResourceDelegate(Resource resource,
- AuthHeaderProvider auth,
- ReadingListResponse.ResponseFactory<T> factory,
- long ifModifiedSince) {
- super(resource, auth, factory, ifModifiedSince);
- }
-
- public UploadResourceDelegate(Resource resource,
- AuthHeaderProvider auth,
- ReadingListResponse.ResponseFactory<T> factory) {
- this(resource, auth, factory, -1L);
- }
-
- @Override
- void onNonSuccess(T resp) {
- if (resp.getStatusCode() == 303) {
- onSeeOther(resp);
- return;
- }
- onFailure(resp);
- }
-
- abstract void onSeeOther(T resp);
- }
-
-
- /**
- * Recursively calls `patch` with items from the queue, delivering callbacks
- * to the provided delegate. Calls `onBatchDone` when the queue is exhausted.
- *
- * Uses the provided executor to flatten the recursive call stack.
- */
- private abstract class BatchingUploadDelegate implements ReadingListRecordUploadDelegate {
- private final Queue<ClientReadingListRecord> queue;
- private final ReadingListRecordUploadDelegate batchUploadDelegate;
- private final Executor executor;
-
- BatchingUploadDelegate(Queue<ClientReadingListRecord> queue,
- ReadingListRecordUploadDelegate batchUploadDelegate,
- Executor executor) {
- this.queue = queue;
- this.batchUploadDelegate = batchUploadDelegate;
- this.executor = executor;
- }
-
- abstract void again(ClientReadingListRecord record);
-
- void next() {
- final ClientReadingListRecord record = queue.poll();
- executor.execute(new Runnable() {
- @Override
- public void run() {
- if (record == null) {
- batchUploadDelegate.onBatchDone();
- return;
- }
-
- again(record);
- }
- });
- }
-
- @Override
- public void onSuccess(ClientReadingListRecord up,
- ReadingListRecordResponse response,
- ServerReadingListRecord down) {
- batchUploadDelegate.onSuccess(up, response, down);
- next();
- }
-
- @Override
- public void onInvalidUpload(ClientReadingListRecord up,
- ReadingListResponse response) {
- batchUploadDelegate.onInvalidUpload(up, response);
- next();
- }
-
- @Override
- public void onFailure(ClientReadingListRecord up, MozResponse response) {
- batchUploadDelegate.onFailure(up, response);
- next();
- }
-
- @Override
- public void onFailure(ClientReadingListRecord up, Exception ex) {
- batchUploadDelegate.onFailure(up, ex);
- next();
- }
-
- @Override
- public void onConflict(ClientReadingListRecord up,
- ReadingListResponse response) {
- batchUploadDelegate.onConflict(up, response);
- next();
- }
-
- @Override
- public void onBadRequest(ClientReadingListRecord up, MozResponse response) {
- batchUploadDelegate.onBadRequest(up, response);
- next();
- }
-
- @Override
- public void onBatchDone() {
- // This should never occur, but if it does, pass through.
- batchUploadDelegate.onBatchDone();
- }
- }
-
- private class PostBatchingUploadDelegate extends BatchingUploadDelegate {
- PostBatchingUploadDelegate(Queue<ClientReadingListRecord> queue,
- ReadingListRecordUploadDelegate batchUploadDelegate,
- Executor executor) {
- super(queue, batchUploadDelegate, executor);
- }
-
- @Override
- void again(ClientReadingListRecord record) {
- add(record, PostBatchingUploadDelegate.this);
- }
- }
-
- private class PatchBatchingUploadDelegate extends BatchingUploadDelegate {
- PatchBatchingUploadDelegate(Queue<ClientReadingListRecord> queue,
- ReadingListRecordUploadDelegate batchUploadDelegate,
- Executor executor) {
- super(queue, batchUploadDelegate, executor);
- }
-
- @Override
- void again(ClientReadingListRecord record) {
- patch(record, PatchBatchingUploadDelegate.this);
- }
- }
-
- private class DeleteBatchingDelegate implements ReadingListDeleteDelegate {
- private final Queue<String> queue;
- private final ReadingListDeleteDelegate batchDeleteDelegate;
- private final Executor executor;
-
- DeleteBatchingDelegate(Queue<String> guids,
- ReadingListDeleteDelegate batchDeleteDelegate,
- Executor executor) {
- this.queue = guids;
- this.batchDeleteDelegate = batchDeleteDelegate;
- this.executor = executor;
- }
-
- void next() {
- final String guid = queue.poll();
- executor.execute(new Runnable() {
- @Override
- public void run() {
- if (guid == null) {
- batchDeleteDelegate.onBatchDone();
- return;
- }
-
- again(guid);
- }
- });
- }
-
- void again(String guid) {
- delete(guid, DeleteBatchingDelegate.this, -1L);
- }
-
- @Override
- public void onSuccess(ReadingListRecordResponse response,
- ReadingListRecord record) {
- batchDeleteDelegate.onSuccess(response, record);
- next();
- }
-
- @Override
- public void onPreconditionFailed(String guid, MozResponse response) {
- batchDeleteDelegate.onPreconditionFailed(guid, response);
- next();
- }
-
- @Override
- public void onRecordMissingOrDeleted(String guid, MozResponse response) {
- batchDeleteDelegate.onRecordMissingOrDeleted(guid, response);
- next();
- }
-
- @Override
- public void onFailure(Exception e) {
- batchDeleteDelegate.onFailure(e);
- next();
- }
-
- @Override
- public void onFailure(MozResponse response) {
- batchDeleteDelegate.onFailure(response);
- next();
- }
-
- @Override
- public void onBatchDone() {
- // This should never occur, but if it does, pass through.
- batchDeleteDelegate.onBatchDone();
- }
- }
-
- // Deliberately declare `delegate` non-final so we can't capture it below. We prefer
- // to use `recordDelegate` explicitly.
- public void getOne(final String guid, ReadingListRecordDelegate delegate, final long ifModifiedSince) {
- final BaseResource r = getRelativeArticleResource(guid);
- r.delegate = new SingleRecordResourceDelegate(r, auth, delegate, ReadingListRecordResponse.FACTORY, ifModifiedSince, guid);
- if (ReadingListConstants.DEBUG) {
- Logger.info(LOG_TAG, "Getting record " + guid);
- }
- r.get();
- }
-
- // Deliberately declare `delegate` non-final so we can't capture it below. We prefer
- // to use `recordDelegate` explicitly.
- public void getAll(final FetchSpec spec, ReadingListRecordDelegate delegate, final long ifModifiedSince) throws URISyntaxException {
- final BaseResource r = new BaseResource(spec.getURI(this.articlesURI));
- r.delegate = new MultipleRecordResourceDelegate(r, auth, delegate, ReadingListStorageResponse.FACTORY, ifModifiedSince);
- if (ReadingListConstants.DEBUG) {
- Logger.info(LOG_TAG, "Getting all records from " + r.getURIString());
- }
- r.get();
- }
-
- /**
- * Mutates the provided queue.
- */
- public void patch(final Queue<ClientReadingListRecord> queue, final Executor executor, final ReadingListRecordUploadDelegate batchUploadDelegate) {
- if (queue.isEmpty()) {
- batchUploadDelegate.onBatchDone();
- return;
- }
-
- final ReadingListRecordUploadDelegate uploadDelegate = new PatchBatchingUploadDelegate(queue, batchUploadDelegate, executor);
-
- patch(queue.poll(), uploadDelegate);
- }
-
- public void patch(final ClientReadingListRecord up, final ReadingListRecordUploadDelegate uploadDelegate) {
- final String guid = up.getGUID();
- if (guid == null) {
- uploadDelegate.onFailure(up, new IllegalArgumentException("Supplied record must have a GUID."));
- return;
- }
-
- final BaseResource r = getRelativeArticleResource(guid);
- r.delegate = new DelegatingUploadResourceDelegate(r, auth, ReadingListRecordResponse.FACTORY, up,
- uploadDelegate);
-
- final ExtendedJSONObject body = up.toJSON();
- if (ReadingListConstants.DEBUG) {
- Logger.info(LOG_TAG, "Patching record " + guid + ": " + body.toJSONString());
- }
- r.patch(body);
- }
-
- /**
- * Mutates the provided queue.
- */
- public void add(final Queue<ClientReadingListRecord> queue, final Executor executor, final ReadingListRecordUploadDelegate batchUploadDelegate) {
- if (queue.isEmpty()) {
- batchUploadDelegate.onBatchDone();
- return;
- }
-
- final ReadingListRecordUploadDelegate uploadDelegate = new PostBatchingUploadDelegate(queue, batchUploadDelegate, executor);
-
- add(queue.poll(), uploadDelegate);
- }
-
- public void add(final ClientReadingListRecord up, final ReadingListRecordUploadDelegate uploadDelegate) {
- final BaseResource r = new BaseResource(this.articlesURI);
- r.delegate = new DelegatingUploadResourceDelegate(r, auth, ReadingListRecordResponse.FACTORY, up,
- uploadDelegate);
-
- final ExtendedJSONObject body = up.toJSON();
- if (ReadingListConstants.DEBUG) {
- Logger.info(LOG_TAG, "Uploading new record: " + body.toJSONString());
- }
- r.post(body);
- }
-
- public void delete(final Queue<String> guids, final Executor executor, final ReadingListDeleteDelegate batchDeleteDelegate) {
- if (guids.isEmpty()) {
- batchDeleteDelegate.onBatchDone();
- return;
- }
-
- final ReadingListDeleteDelegate deleteDelegate = new DeleteBatchingDelegate(guids, batchDeleteDelegate, executor);
-
- delete(guids.poll(), deleteDelegate, -1L);
- }
-
- public void delete(final String guid, final ReadingListDeleteDelegate delegate, final long ifUnmodifiedSince) {
- final BaseResource r = getRelativeArticleResource(guid);
-
- // If If-Unmodified-Since is provided, and the record has been modified,
- // we'll receive a 412 Precondition Failed.
- // If the record is missing or already deleted, a 404 will be returned.
- // Otherwise, the response will be the deleted record.
- r.delegate = new ReadingListResourceDelegate<ReadingListRecordResponse>(r, auth, ReadingListRecordResponse.FACTORY) {
- @Override
- public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
- if (ifUnmodifiedSince != -1) {
- request.addHeader("If-Unmodified-Since", "" + ifUnmodifiedSince);
- }
- super.addHeaders(request, client);
- }
-
- @Override
- void onFailure(MozResponse response) {
- switch (response.getStatusCode()) {
- case 412:
- delegate.onPreconditionFailed(guid, response);
- return;
- }
- delegate.onFailure(response);
- }
-
- @Override
- void onSuccess(ReadingListRecordResponse response) {
- final ReadingListRecord record;
- try {
- record = response.getRecord();
- } catch (Exception e) {
- delegate.onFailure(e);
- return;
- }
-
- delegate.onSuccess(response, record);
- }
-
- @Override
- void onFailure(Exception ex) {
- delegate.onFailure(ex);
- }
-
- @Override
- void onNonSuccess(ReadingListRecordResponse response) {
- if (response.getStatusCode() == 404) {
- // Already deleted!
- delegate.onRecordMissingOrDeleted(guid, response);
- }
- }
- };
-
- if (ReadingListConstants.DEBUG) {
- Logger.debug(LOG_TAG, "Deleting " + r.getURIString());
- }
- r.delete();
- }
-
- // TODO: modified times etc.
- public void wipe(final ReadingListWipeDelegate delegate) {
- Logger.info(LOG_TAG, "Wiping server.");
- final BaseResource r = new BaseResource(this.articlesURI);
-
- r.delegate = new ReadingListResourceDelegate<ReadingListStorageResponse>(r, auth, ReadingListStorageResponse.FACTORY) {
-
- @Override
- void onSuccess(ReadingListStorageResponse response) {
- Logger.info(LOG_TAG, "Wipe succeded.");
- delegate.onSuccess(response);
- }
-
- @Override
- void onNonSuccess(ReadingListStorageResponse response) {
- Logger.warn(LOG_TAG, "Wipe failed: " + response.getStatusCode());
- onFailure(response);
- }
-
- @Override
- void onFailure(MozResponse response) {
- Logger.warn(LOG_TAG, "Wipe failed: " + response.getStatusCode());
- delegate.onFailure(response);
- }
-
- @Override
- void onFailure(Exception ex) {
- Logger.warn(LOG_TAG, "Wipe failed.", ex);
- delegate.onFailure(ex);
- }
- };
-
- r.delete();
- }
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListClientContentValuesFactory.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/* 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.reading;
-
-import java.util.Map.Entry;
-import java.util.Set;
-
-import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
-import org.mozilla.gecko.reading.ReadingListRecord.ServerMetadata;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-import android.content.ContentValues;
-
-public class ReadingListClientContentValuesFactory {
- public static ContentValues fromClientRecord(ClientReadingListRecord record) {
- // Do each of these.
- ExtendedJSONObject fields = record.fields;
- ServerMetadata sm = record.serverMetadata;
-
- final ContentValues values = new ContentValues();
-
- if (sm.guid != null) {
- values.put(ReadingListItems.GUID, sm.guid);
- }
-
- if (sm.lastModified > -1L) {
- values.put(ReadingListItems.SERVER_LAST_MODIFIED, sm.lastModified);
- }
-
- final Set<Entry<String, Object>> entries = fields.entrySet();
-
- for (Entry<String,Object> entry : entries) {
- final String key = entry.getKey();
- final String field = mapField(key);
- if (field == null) {
- continue;
- }
-
- final Object v = entry.getValue();
- if (v == null) {
- values.putNull(field);
- } else if (v instanceof Boolean) {
- values.put(field, ((Boolean) v) ? 1 : 0);
- } else if (v instanceof Long) {
- values.put(field, (Long) v);
- } else if (v instanceof Integer) {
- values.put(field, (Integer) v);
- } else if (v instanceof String) {
- values.put(field, (String) v);
- } else if (v instanceof Double) {
- values.put(field, (Double) v);
- } else {
- throw new IllegalArgumentException("Unknown value " + v + " of type " + v.getClass().getSimpleName());
- }
- }
-
- // Clear the sync flags.
- values.put(ReadingListItems.SYNC_STATUS, ReadingListItems.SYNC_STATUS_SYNCED);
- values.put(ReadingListItems.SYNC_CHANGE_FLAGS, ReadingListItems.SYNC_CHANGE_NONE);
-
- return values;
- }
-
- /**
- * Only returns valid columns.
- */
- private static String mapField(String key) {
- if (key == null) {
- return null;
- }
-
- switch (key) {
- case "unread":
- return "is_unread";
- case "favorite":
- return "is_favorite";
- case "archived":
- return "is_archived";
- case "deleted":
- return "is_deleted";
- }
-
- // Validation.
- for (int i = 0; i < ReadingListItems.ALL_FIELDS.length; ++i) {
- if (key.equals(ReadingListItems.ALL_FIELDS[i])) {
- return key;
- }
- }
-
- return null;
- }
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListClientRecordFactory.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/* 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.reading;
-
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
-import org.mozilla.gecko.reading.ReadingListRecord.ServerMetadata;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-import android.annotation.TargetApi;
-import android.database.AbstractWindowedCursor;
-import android.database.Cursor;
-import android.database.CursorWindow;
-import android.os.Build;
-
-/**
- * This class converts database rows into {@link ClientReadingListRecord}s.
- *
- * In doing so it has to:
- *
- * * Translate column names.
- * * Convert INTEGER columns into booleans.
- * * Eliminate fields that aren't present in the wire format.
- * * Extract fields that are part of {@link ClientMetadata} instances.
- *
- * The caller is responsible for closing the cursor.
- */
-public class ReadingListClientRecordFactory {
- public static final int MAX_SERVER_STRING_CHARS = 1024;
-
- private final Cursor cursor;
-
- private final String[] fields;
- private final int[] columns;
-
- public ReadingListClientRecordFactory(final Cursor cursor, final String[] fields) throws IllegalArgumentException {
- this.cursor = cursor;
-
- // Does this cursor have an _ID?
- final int idIndex = cursor.getColumnIndex(ReadingListItems._ID);
- final int extra = (idIndex != -1) ? 1 : 0;
- final int cols = fields.length + extra;
-
- this.fields = new String[cols];
- this.columns = new int[cols];
-
- for (int i = 0; i < fields.length; ++i) {
- final int index = cursor.getColumnIndex(fields[i]);
- if (index == -1) {
- continue;
- }
- this.fields[i] = mapColumn(fields[i]);
- this.columns[i] = index;
- }
-
- if (idIndex != -1) {
- this.fields[fields.length] = "_id";
- this.columns[fields.length] = idIndex;
- }
- }
-
- public ReadingListClientRecordFactory(final Cursor cursor) {
- this(cursor, ReadingListItems.ALL_FIELDS);
- }
-
- private void putNull(ExtendedJSONObject o, String field) {
- o.put(field, null);
- }
-
- /**
- * Map column names to protocol field names.
- */
- private static String mapColumn(final String column) {
- switch (column) {
- case "is_unread":
- return "unread";
- case "is_favorite":
- return "favorite";
- case "is_archived":
- return "archived";
- }
- return column;
- }
-
- private void put(ExtendedJSONObject o, String field, String value) {
- // All server strings are a max of 1024 characters.
- o.put(field, value.length() > MAX_SERVER_STRING_CHARS ? value.substring(0, MAX_SERVER_STRING_CHARS - 1) + "…" : value);
- }
-
- private void put(ExtendedJSONObject o, String field, long value) {
- // Convert to boolean.
- switch (field) {
- case "unread":
- case "favorite":
- case "archived":
- case "is_article":
- o.put(field, value == 1);
- return;
- }
- o.put(field, value);
- }
-
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
- private final void fillHoneycomb(ExtendedJSONObject o, Cursor c, String f, int i) {
- if (f == null) {
- return;
- }
- switch (c.getType(i)) {
- case Cursor.FIELD_TYPE_NULL:
- putNull(o, f);
- return;
- case Cursor.FIELD_TYPE_STRING:
- put(o, f, c.getString(i));
- return;
- case Cursor.FIELD_TYPE_INTEGER:
- put(o, f, c.getLong(i));
- return;
- case Cursor.FIELD_TYPE_FLOAT:
- o.put(f, c.getDouble(i));
- return;
- case Cursor.FIELD_TYPE_BLOB:
- // TODO: this probably doesn't serialize correctly.
- o.put(f, c.getBlob(i));
- return;
- default:
- // Do nothing.
- return;
- }
- }
-
- @SuppressWarnings("deprecation")
- private final void fillGingerbread(ExtendedJSONObject o, Cursor c, String f, int i) {
- if (!(c instanceof AbstractWindowedCursor)) {
- throw new IllegalStateException("Unable to handle cursors that don't have a CursorWindow!");
- }
-
- final AbstractWindowedCursor sqc = (AbstractWindowedCursor) c;
- final CursorWindow w = sqc.getWindow();
- final int pos = c.getPosition();
- if (w.isNull(pos, i)) {
- putNull(o, f);
- } else if (w.isString(pos, i)) {
- put(o, f, c.getString(i));
- } else if (w.isLong(pos, i)) {
- put(o, f, c.getLong(i));
- } else if (w.isFloat(pos, i)) {
- o.put(f, c.getDouble(i));
- } else if (w.isBlob(pos, i)) {
- // TODO: this probably doesn't serialize correctly.
- o.put(f, c.getBlob(i));
- }
- }
-
- /**
- * TODO: optionally produce a partial record by examining SYNC_CHANGE_FLAGS/SYNC_STATUS.
- */
- public ClientReadingListRecord fromCursorRow() {
- final ExtendedJSONObject object = new ExtendedJSONObject();
- for (int i = 0; i < this.fields.length; ++i) {
- final String field = fields[i];
- if (field == null) {
- continue;
- }
- final int column = this.columns[i];
- if (Versions.feature11Plus) {
- fillHoneycomb(object, this.cursor, field, column);
- } else {
- fillGingerbread(object, this.cursor, field, column);
- }
- }
-
- // Apply cross-field constraints.
- if (object.containsKey("unread") && object.getBoolean("unread")) {
- object.remove("marked_read_by");
- object.remove("marked_read_on");
- }
-
- // Construct server metadata and client metadata from the object.
- final long serverLastModified = object.getLong("last_modified", -1L);
- final String guid = object.containsKey("guid") ? object.getString("guid") : null;
- final ServerMetadata sm = new ServerMetadata(guid, serverLastModified);
-
- final long clientLastModified = object.getLong("client_last_modified", -1L);
-
- // This has already been translated...
- final boolean isArchived = object.getBoolean("archived");
-
- // ... but this is a client-only field, so it needs to be converted.
- final boolean isDeleted = object.getLong("is_deleted", 0L) == 1L;
- final long localID = object.getLong("_id", -1L);
- final ClientMetadata cm = new ClientMetadata(localID, clientLastModified, isDeleted, isArchived);
-
- // Remove things that aren't part of the spec.
- object.remove("last_modified");
- object.remove("guid");
- object.remove("client_last_modified");
- object.remove("is_deleted");
-
- // We never want to upload stored_on; for new items it'll be null (and cause Bug 1153358),
- // and for existing items it should never change.
- object.remove("stored_on");
-
- object.remove(ReadingListItems.CONTENT_STATUS);
- object.remove(ReadingListItems.SYNC_STATUS);
- object.remove(ReadingListItems.SYNC_CHANGE_FLAGS);
- object.remove(ReadingListItems.CLIENT_LAST_MODIFIED);
-
- return new ClientReadingListRecord(sm, cm, object);
- }
-
- /**
- * Return a record from a cursor.
- * Make sure that the columns you specify in the constructor are a subset
- * of the columns in the cursor, or you'll have a bad time.
- */
- public ClientReadingListRecord getNext() {
- if (!cursor.moveToNext()) {
- return null;
- }
-
- return fromCursorRow();
- }
-}
\ No newline at end of file
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListDeleteDelegate.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/* 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.reading;
-
-import org.mozilla.gecko.sync.net.MozResponse;
-
-/**
- * Response delegate for a server DELETE.
- * Only one of these methods will be called, and it will be called precisely once,
- * unless batching is used.
- */
-public interface ReadingListDeleteDelegate {
- void onSuccess(ReadingListRecordResponse response, ReadingListRecord record);
- void onPreconditionFailed(String guid, MozResponse response);
- void onRecordMissingOrDeleted(String guid, MozResponse response);
- void onFailure(Exception e);
- void onFailure(MozResponse response);
- void onBatchDone();
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListInvalidAuthenticationException.java
+++ /dev/null
@@ -1,18 +0,0 @@
-/* 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.reading;
-
-import org.mozilla.gecko.sync.net.MozResponse;
-
-public class ReadingListInvalidAuthenticationException extends Exception {
- private static final long serialVersionUID = 7112459541558266597L;
-
- public final MozResponse response;
-
- public ReadingListInvalidAuthenticationException(MozResponse response) {
- super();
- this.response = response;
- }
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListRecord.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/* 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.reading;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-/**
- * This models the wire protocol format, not database contents.
- */
-public abstract class ReadingListRecord {
- public static class ServerMetadata {
- public final String guid; // Null if not yet uploaded successfully.
- public final long lastModified; // A server timestamp.
-
- public ServerMetadata(String guid, long lastModified) {
- this.guid = guid;
- this.lastModified = lastModified;
- }
-
- /**
- * From server record.
- */
- public ServerMetadata(ExtendedJSONObject obj) {
- this(obj.getString("id"), obj.containsKey("last_modified") ? obj.getLong("last_modified") : -1L);
- }
- }
-
- public final ServerMetadata serverMetadata;
-
- public String getGUID() {
- if (serverMetadata == null) {
- return null;
- }
-
- return serverMetadata.guid;
- }
-
- public long getServerLastModified() {
- if (serverMetadata == null) {
- return -1L;
- }
-
- return serverMetadata.lastModified;
- }
-
- protected ReadingListRecord(final ServerMetadata serverMetadata) {
- this.serverMetadata = serverMetadata;
- }
-
- public abstract String getURL();
- public abstract String getTitle();
- public abstract String getAddedBy();
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListRecordDelegate.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/* 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.reading;
-
-import org.mozilla.gecko.sync.net.MozResponse;
-
-/**
- * Delegate for downloading records.
- *
- * onRecordReceived will be called at most once per record.
- * onComplete will be called at the end of a successful download.
- *
- * Otherwise, one of the failure methods will be called.
- *
- * onRecordMissingOrDeleted will only be called when fetching a single
- * record by ID.
- */
-public interface ReadingListRecordDelegate {
- void onRecordReceived(ServerReadingListRecord record);
- void onComplete(ReadingListResponse response);
- void onFailure(MozResponse response);
- void onFailure(Exception error);
- void onRecordMissingOrDeleted(String guid, ReadingListResponse resp);
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListRecordResponse.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/* 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.reading;
-
-import java.io.IOException;
-
-import org.json.simple.parser.ParseException;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-
-/**
- * A storage response that contains a single record.
- */
-public class ReadingListRecordResponse extends ReadingListResponse {
- @Override
- public boolean wasSuccessful() {
- final int code = getStatusCode();
- if (code == 200 || code == 201 || code == 204) {
- return true;
- }
- return super.wasSuccessful();
- }
-
- public static final ReadingListResponse.ResponseFactory<ReadingListRecordResponse> FACTORY = new ReadingListResponse.ResponseFactory<ReadingListRecordResponse>() {
- @Override
- public ReadingListRecordResponse getResponse(HttpResponse r) {
- return new ReadingListRecordResponse(r);
- }
- };
-
- public ReadingListRecordResponse(HttpResponse res) {
- super(res);
- }
-
- public ServerReadingListRecord getRecord() throws IllegalStateException, NonObjectJSONException, IOException, ParseException {
- return new ServerReadingListRecord(jsonObjectBody());
- }
-}
\ No newline at end of file
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListRecordUploadDelegate.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/* 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.reading;
-
-import org.mozilla.gecko.sync.net.MozResponse;
-
-public interface ReadingListRecordUploadDelegate {
- // Called once per batch.
- public void onBatchDone();
-
- // One of these is called once per record.
- public void onSuccess(ClientReadingListRecord up, ReadingListRecordResponse response, ServerReadingListRecord down);
- public void onConflict(ClientReadingListRecord up, ReadingListResponse response);
- public void onInvalidUpload(ClientReadingListRecord up, ReadingListResponse response);
- public void onBadRequest(ClientReadingListRecord up, MozResponse response);
- public void onFailure(ClientReadingListRecord up, Exception ex);
- public void onFailure(ClientReadingListRecord up, MozResponse response);
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListResponse.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/* 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.reading;
-
-import org.mozilla.gecko.sync.net.MozResponse;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-
-/**
- * A MozResponse that knows about all of the general RL-related headers, like Last-Modified.
- */
-public abstract class ReadingListResponse extends MozResponse {
- static interface ResponseFactory<T extends ReadingListResponse> {
- public T getResponse(HttpResponse r);
- }
-
- public ReadingListResponse(HttpResponse res) {
- super(res);
- }
-
- public long getLastModified() {
- return getLongHeader("Last-Modified");
- }
-}
\ No newline at end of file
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListStorage.java
+++ /dev/null
@@ -1,16 +0,0 @@
-/* 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.reading;
-
-import android.database.Cursor;
-
-public interface ReadingListStorage {
- Cursor getModified();
- Cursor getDeletedItems();
- Cursor getStatusChanges();
- Cursor getNew();
- Cursor getAll();
- ReadingListChangeAccumulator getChangeAccumulator();
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListStorageResponse.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/* 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.reading;
-
-import java.io.IOException;
-import java.util.Iterator;
-
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.json.simple.parser.ParseException;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.UnexpectedJSONException;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-
-/**
- * A storage response that contains multiple records.
- */
-public class ReadingListStorageResponse extends ReadingListResponse {
- public static final ReadingListResponse.ResponseFactory<ReadingListStorageResponse> FACTORY = new ReadingListResponse.ResponseFactory<ReadingListStorageResponse>() {
- @Override
- public ReadingListStorageResponse getResponse(HttpResponse r) {
- return new ReadingListStorageResponse(r);
- }
- };
-
- private static final String LOG_TAG = "StorageResponse";
-
- public ReadingListStorageResponse(HttpResponse res) {
- super(res);
- }
-
- public Iterable<ServerReadingListRecord> getRecords() throws IOException, ParseException, UnexpectedJSONException {
- final ExtendedJSONObject body = jsonObjectBody();
- final JSONArray items = body.getArray("items");
-
- final int expected = getTotalRecords();
- final int actual = items.size();
- if (actual < expected) {
- Logger.warn(LOG_TAG, "Unexpected number of records. Got " + actual + ", expected " + expected);
- }
-
- return new Iterable<ServerReadingListRecord>() {
- @Override
- public Iterator<ServerReadingListRecord> iterator() {
- return new Iterator<ServerReadingListRecord>() {
- int position = 0;
-
- @Override
- public boolean hasNext() {
- return position < actual;
- }
-
- @Override
- public ServerReadingListRecord next() {
- final Object o = items.get(position++);
- return new ServerReadingListRecord(new ExtendedJSONObject((JSONObject) o));
- }
-
- @Override
- public void remove() {
- throw new RuntimeException("Cannot remove from iterator.");
- }
- };
- }
- };
- }
-
- public int getTotalRecords() {
- return getIntegerHeader("Total-Records");
- }
-}
\ No newline at end of file
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListSyncAdapter.java
+++ /dev/null
@@ -1,320 +0,0 @@
-/* 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.reading;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-import org.mozilla.gecko.background.ReadingListConstants;
-import org.mozilla.gecko.background.common.PrefsBranch;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.fxa.FxAccountUtils;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
-import org.mozilla.gecko.fxa.FirefoxAccounts;
-import org.mozilla.gecko.fxa.FirefoxAccounts.SyncHint;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.fxa.sync.FxAccountSyncDelegate;
-import org.mozilla.gecko.fxa.sync.FxAccountSyncDelegate.Result;
-import org.mozilla.gecko.sync.BackoffHandler;
-import org.mozilla.gecko.sync.PrefsBackoffHandler;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.BaseResource;
-import org.mozilla.gecko.sync.net.BearerAuthHeaderProvider;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.content.AbstractThreadedSyncAdapter;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.SyncResult;
-import android.os.Bundle;
-
-public class ReadingListSyncAdapter extends AbstractThreadedSyncAdapter {
- public static final String PREF_LOCAL_NAME = "device.localname";
-
- private static final String LOG_TAG = ReadingListSyncAdapter.class.getSimpleName();
- private static final long TIMEOUT_SECONDS = 60;
- protected final ExecutorService executor;
-
- // Don't sync again if we successfully synced within this duration.
- private static final int AFTER_SUCCESS_SYNC_DELAY_SECONDS = 5 * 60; // 5 minutes.
- // Don't sync again if we unsuccessfully synced within this duration.
- private static final int AFTER_ERROR_SYNC_DELAY_SECONDS = 15 * 60; // 15 minutes.
-
- public ReadingListSyncAdapter(Context context, boolean autoInitialize) {
- super(context, autoInitialize);
- this.executor = Executors.newSingleThreadExecutor();
- }
-
- protected static abstract class SyncAdapterSynchronizerDelegate implements ReadingListSynchronizerDelegate {
- private final FxAccountSyncDelegate syncDelegate;
- private final ContentProviderClient cpc;
- private final SyncResult result;
-
- SyncAdapterSynchronizerDelegate(FxAccountSyncDelegate syncDelegate,
- ContentProviderClient cpc,
- SyncResult result) {
- this.syncDelegate = syncDelegate;
- this.cpc = cpc;
- this.result = result;
- }
-
- abstract public void onInvalidAuthentication();
-
- @Override
- public void onUnableToSync(Exception e) {
- Logger.warn(LOG_TAG, "Unable to sync.", e);
- if (e instanceof ReadingListInvalidAuthenticationException) {
- onInvalidAuthentication();
- }
- cpc.release();
- syncDelegate.handleError(e);
- }
-
- @Override
- public void onDeletionsUploadComplete() {
- Logger.debug(LOG_TAG, "Step: onDeletionsUploadComplete");
- this.result.stats.numEntries += 1; // TODO: Bug 1140809.
- }
-
- @Override
- public void onStatusUploadComplete(Collection<String> uploaded,
- Collection<String> failed) {
- Logger.debug(LOG_TAG, "Step: onStatusUploadComplete");
- this.result.stats.numEntries += 1; // TODO: Bug 1140809.
- }
-
- @Override
- public void onNewItemUploadComplete(Collection<String> uploaded,
- Collection<String> failed) {
- Logger.debug(LOG_TAG, "Step: onNewItemUploadComplete");
- this.result.stats.numEntries += 1; // TODO: Bug 1140809.
- }
-
- @Override
- public void onModifiedUploadComplete() {
- Logger.debug(LOG_TAG, "Step: onModifiedUploadComplete");
- this.result.stats.numEntries += 1; // TODO: Bug 1140809.
- }
-
- @Override
- public void onDownloadComplete() {
- Logger.debug(LOG_TAG, "Step: onDownloadComplete");
- this.result.stats.numInserts += 1; // TODO: Bug 1140809.
- }
-
- @Override
- public void onComplete() {
- Logger.info(LOG_TAG, "Reading list synchronization complete.");
- cpc.release();
- syncDelegate.handleSuccess();
- }
- }
-
- private void syncWithAuthorization(final Context context,
- final URI endpoint,
- final SyncResult syncResult,
- final FxAccountSyncDelegate syncDelegate,
- final String authToken,
- final SharedPreferences sharedPrefs,
- final Bundle extras) {
- final AuthHeaderProvider auth = new BearerAuthHeaderProvider(authToken);
-
- final PrefsBranch branch = new PrefsBranch(sharedPrefs, "readinglist.");
- final ReadingListClient remote = new ReadingListClient(endpoint, auth);
- final ContentProviderClient cpc = getContentProviderClient(context); // Released by the inner SyncAdapterSynchronizerDelegate.
-
- final LocalReadingListStorage local = new LocalReadingListStorage(cpc);
- String localName = branch.getString(PREF_LOCAL_NAME, null);
- if (localName == null) {
- localName = FxAccountUtils.defaultClientName(context);
- }
-
- // Make sure DB rows don't refer to placeholder values.
- local.updateLocalNames(localName);
-
- final ReadingListSynchronizer synchronizer = new ReadingListSynchronizer(branch, remote, local);
-
- synchronizer.syncAll(new SyncAdapterSynchronizerDelegate(syncDelegate, cpc, syncResult) {
- @Override
- public void onInvalidAuthentication() {
- // The reading list server rejected our oauth token! Invalidate it. Next
- // time through, we'll request a new one, which will drive the login
- // state machine, produce a new assertion, and eventually a fresh token.
- Logger.info(LOG_TAG, "Invalidating oauth token after 401!");
- AccountManager.get(context).invalidateAuthToken(FxAccountConstants.ACCOUNT_TYPE, authToken);
- }
- });
- // TODO: backoffs, and everything else handled by a SessionCallback.
- }
-
- @Override
- public void onPerformSync(final Account account, final Bundle extras, final String authority, final ContentProviderClient provider, final SyncResult syncResult) {
- Logger.setThreadLogTag(ReadingListConstants.GLOBAL_LOG_TAG);
- Logger.resetLogging();
-
- final EnumSet<SyncHint> syncHints = FirefoxAccounts.getHintsToSyncFromBundle(extras);
- FirefoxAccounts.logSyncHints(syncHints);
-
- final Context context = getContext();
- final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
-
- // Don't sync Reading List if we're in a non-default configuration, but allow testing against stage.
- final String accountServerURI = fxAccount.getAccountServerURI();
- final boolean usingDefaultAuthServer = FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT.equals(accountServerURI);
- final boolean usingStageAuthServer = FxAccountConstants.STAGE_AUTH_SERVER_ENDPOINT.equals(accountServerURI);
- if (!usingDefaultAuthServer && !usingStageAuthServer) {
- Logger.error(LOG_TAG, "Skipping Reading List sync because Firefox Account is not using prod or stage auth server.");
- // Stop syncing the Reading List entirely.
- ContentResolver.setIsSyncable(account, BrowserContract.READING_LIST_AUTHORITY, 0);
- return;
- }
- final String tokenServerURI = fxAccount.getTokenServerURI();
- final boolean usingDefaultSyncServer = FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT.equals(tokenServerURI);
- final boolean usingStageSyncServer = FxAccountConstants.STAGE_TOKEN_SERVER_ENDPOINT.equals(tokenServerURI);
- if (!usingDefaultSyncServer && !usingStageSyncServer) {
- Logger.error(LOG_TAG, "Skipping Reading List sync because Sync is not using the prod or stage Sync (token) server.");
- Logger.debug(LOG_TAG, "If the user has chosen to not store Sync data with Mozilla, we shouldn't store Reading List data with Mozilla .");
- // Stop syncing the Reading List entirely.
- ContentResolver.setIsSyncable(account, BrowserContract.READING_LIST_AUTHORITY, 0);
- return;
- }
-
- Result result = Result.Error;
- final BlockingQueue<Result> latch = new LinkedBlockingQueue<Result>(1);
- final FxAccountSyncDelegate syncDelegate = new FxAccountSyncDelegate(latch, syncResult);
-
- // Allow testing against stage.
- final String endpointString;
- if (usingStageAuthServer) {
- endpointString = ReadingListConstants.DEFAULT_DEV_ENDPOINT;
- } else {
- endpointString = ReadingListConstants.DEFAULT_PROD_ENDPOINT;
- }
-
- Logger.info(LOG_TAG, "Syncing reading list against endpoint: " + endpointString);
- final URI endpointURI;
- try {
- endpointURI = new URI(endpointString);
- } catch (URISyntaxException e) {
- // Should never happen.
- Logger.error(LOG_TAG, "Unexpected malformed URI for reading list service: " + endpointString);
- syncDelegate.handleError(e);
- return;
- }
-
- final AccountManager accountManager = AccountManager.get(context);
- // If we have an auth failure that requires user intervention, FxA will show system
- // notifications prompting the user to re-connect as it advances the internal account state.
- // true causes the auth token fetch to return null on failure immediately, rather than doing
- // Mysterious Internal Work to try to get the token.
- final boolean notifyAuthFailure = true;
- try {
- final SharedPreferences sharedPrefs = fxAccount.getReadingListPrefs();
-
- final BackoffHandler storageBackoffHandler = new PrefsBackoffHandler(sharedPrefs, "storage");
- final long storageBackoffDelayMilliseconds = storageBackoffHandler.delayMilliseconds();
- if (!syncHints.contains(SyncHint.SCHEDULE_NOW) && !syncHints.contains(SyncHint.IGNORE_REMOTE_SERVER_BACKOFF) && storageBackoffDelayMilliseconds > 0) {
- Logger.warn(LOG_TAG, "Not syncing: storage requested additional backoff: " + storageBackoffDelayMilliseconds + " milliseconds.");
- syncDelegate.rejectSync();
- return;
- }
-
- final BackoffHandler rateLimitBackoffHandler = new PrefsBackoffHandler(sharedPrefs, "rate");
- final long rateLimitBackoffDelayMilliseconds = rateLimitBackoffHandler.delayMilliseconds();
- if (!syncHints.contains(SyncHint.SCHEDULE_NOW) && !syncHints.contains(SyncHint.IGNORE_LOCAL_RATE_LIMIT) && rateLimitBackoffDelayMilliseconds > 0) {
- Logger.warn(LOG_TAG, "Not syncing: local rate limiting for another: " + rateLimitBackoffDelayMilliseconds + " milliseconds.");
- syncDelegate.rejectSync();
- return;
- }
-
- final String authToken = accountManager.blockingGetAuthToken(account, ReadingListConstants.AUTH_TOKEN_TYPE, notifyAuthFailure);
- if (authToken == null) {
- throw new RuntimeException("Couldn't get oauth token! Aborting sync.");
- }
-
- final ReadingListBackoffObserver observer = new ReadingListBackoffObserver(endpointURI.getHost());
- BaseResource.addHttpResponseObserver(observer);
- try {
- syncWithAuthorization(context, endpointURI, syncResult, syncDelegate, authToken, sharedPrefs, extras);
- result = latch.poll(TIMEOUT_SECONDS, TimeUnit.SECONDS);
- } finally {
- BaseResource.removeHttpResponseObserver(observer);
- long backoffInSeconds = observer.largestBackoffObservedInSeconds.get();
- if (backoffInSeconds > 0) {
- Logger.warn(LOG_TAG, "Observed " + backoffInSeconds + "-second backoff request.");
- storageBackoffHandler.extendEarliestNextRequest(System.currentTimeMillis() + 1000 * backoffInSeconds);
- }
- }
-
- if (result == null) {
- // The poll timed out. Let's call this an error.
- result = Result.Error;
- }
-
- switch (result) {
- case Success:
- requestPeriodicSync(account, ReadingListSyncAdapter.AFTER_SUCCESS_SYNC_DELAY_SECONDS);
- break;
- case Error:
- requestPeriodicSync(account, ReadingListSyncAdapter.AFTER_ERROR_SYNC_DELAY_SECONDS);
- break;
- case Postponed:
- break;
- case Rejected:
- break;
- }
-
- Logger.info(LOG_TAG, "Reading list sync done.");
- } catch (Exception e) {
- // We can get lots of exceptions here; handle them uniformly.
- Logger.error(LOG_TAG, "Got error syncing.", e);
- syncDelegate.handleError(e);
- }
-
- /*
- * TODO:
- * * Account error notifications. How do we avoid these overlapping with Sync?
- * * Pickling. How do we avoid pickling twice if you use both Sync and RL?
- */
-
- /*
- * TODO:
- * * Auth.
- * * Server URI lookup.
- * * Syncing.
- * * Error handling.
- * * Forcing syncs/interactive use.
- */
- }
-
- private ContentProviderClient getContentProviderClient(Context context) {
- final ContentResolver contentResolver = context.getContentResolver();
- final ContentProviderClient client = contentResolver.acquireContentProviderClient(ReadingListItems.CONTENT_URI);
- return client;
- }
-
- /**
- * Updates the existing system periodic sync interval to the specified duration.
- *
- * @param intervalSeconds the requested period, which Android will vary by up to 4%.
- */
- protected void requestPeriodicSync(final Account account, final long intervalSeconds) {
- final String authority = BrowserContract.AUTHORITY;
- Logger.info(LOG_TAG, "Scheduling periodic sync for " + intervalSeconds + ".");
- ContentResolver.addPeriodicSync(account, authority, Bundle.EMPTY, intervalSeconds);
- }
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListSyncService.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/* 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.reading;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-
-public class ReadingListSyncService extends Service {
- private static final Object syncAdapterLock = new Object();
- private static ReadingListSyncAdapter syncAdapter;
-
- @Override
- public void onCreate() {
- synchronized (syncAdapterLock) {
- if (syncAdapter == null) {
- syncAdapter = new ReadingListSyncAdapter(getApplicationContext(), true);
- }
- }
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return syncAdapter.getSyncAdapterBinder();
- }
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListSynchronizer.java
+++ /dev/null
@@ -1,985 +0,0 @@
-/* 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.reading;
-
-import java.io.IOException;
-import java.net.URISyntaxException;
-import java.util.LinkedList;
-import java.util.Queue;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-import org.json.simple.parser.ParseException;
-import org.mozilla.gecko.background.common.PrefsBranch;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
-import org.mozilla.gecko.reading.ReadingListRecord.ServerMetadata;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-import org.mozilla.gecko.sync.net.MozResponse;
-
-import android.database.Cursor;
-import android.text.TextUtils;
-
-/**
- * This class implements the multi-phase synchronizing approach described
- * at <https://github.com/mozilla-services/readinglist/wiki/Client-phases>.
- *
- * This is also where delegate-based control flow comes to die.
- */
-public class ReadingListSynchronizer {
- public static final String LOG_TAG = ReadingListSynchronizer.class.getSimpleName();
-
- public static final String PREF_LAST_MODIFIED = "download.serverlastmodified";
-
- private final PrefsBranch prefs;
- private final ReadingListClient remote;
- private final ReadingListStorage local;
- private final Executor executor;
-
- private interface StageDelegate {
- void next();
- void fail();
- void fail(Exception e);
- }
-
- private abstract static class NextDelegate implements StageDelegate {
- private final Executor executor;
- NextDelegate(final Executor executor) {
- this.executor = executor;
- }
-
- abstract void doNext();
- abstract void doFail(Exception e);
-
- @Override
- public void next() {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- doNext();
- }
- });
- }
-
- @Override
- public void fail() {
- fail(null);
- }
-
- @Override
- public void fail(final Exception e) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- doFail(e);
- }
- });
- }
- }
-
- public ReadingListSynchronizer(final PrefsBranch prefs, final ReadingListClient remote, final ReadingListStorage local) {
- this(prefs, remote, local, Executors.newSingleThreadExecutor());
- }
-
- public ReadingListSynchronizer(final PrefsBranch prefs, final ReadingListClient remote, final ReadingListStorage local, Executor executor) {
- this.prefs = prefs;
- this.remote = remote;
- this.local = local;
- this.executor = executor;
- }
-
- private static final class NewItemUploadDelegate implements ReadingListRecordUploadDelegate {
- public volatile int failures = 0;
- private final ReadingListChangeAccumulator acc;
- private final StageDelegate next;
-
- NewItemUploadDelegate(ReadingListChangeAccumulator acc, StageDelegate next) {
- this.acc = acc;
- this.next = next;
- }
-
- @Override
- public void onSuccess(ClientReadingListRecord up,
- ReadingListRecordResponse response,
- ServerReadingListRecord down) {
- // Apply the resulting record. The server will have populated some fields.
- acc.addChangedRecord(up.givenServerRecord(down));
- }
-
- @Override
- public void onConflict(ClientReadingListRecord up, ReadingListResponse response) {
- ExtendedJSONObject body;
- try {
- body = response.jsonObjectBody();
- String conflicting = body.getString("id");
- Logger.warn(LOG_TAG, "Conflict detected: remote ID is " + conflicting);
-
- // TODO: When an operation implies that a server record is a replacement
- // of what we uploaded, we should ensure that we have a local copy of
- // that server record!
- } catch (IllegalStateException | NonObjectJSONException | IOException |
- ParseException e) {
- // Oops.
- // But our workaround is the same either way.
- }
-
- // Either the record exists locally, in which case we need to merge,
- // or it doesn't, and we'll download it shortly.
- // The simplest thing to do in both cases is to simply delete the local
- // record we tried to upload. Yes, we might lose some annotations, but
- // we can leave doing better to a follow-up.
- // Issues here are so unlikely that we don't do anything sophisticated
- // (like moving the record to a holding area) -- just delete it ASAP.
- acc.addDeletion(up);
- }
-
- @Override
- public void onInvalidUpload(ClientReadingListRecord up, ReadingListResponse response) {
- recordFailed(up);
- }
-
- @Override
- public void onFailure(ClientReadingListRecord up, MozResponse response) {
- recordFailed(up);
- }
-
- @Override
- public void onFailure(ClientReadingListRecord up, Exception ex) {
- recordFailed(up);
- }
-
- @Override
- public void onBadRequest(ClientReadingListRecord up, MozResponse response) {
- recordFailed(up);
- }
-
- private void recordFailed(ClientReadingListRecord up) {
- ++failures;
- }
-
- @Override
- public void onBatchDone() {
- // We mark uploaded records as synced when we apply the server record with the
- // GUID -- we don't know the GUID yet!
- if (failures == 0) {
- try {
- next.next();
- } catch (Exception e) {
- next.fail(e);
- }
- return;
- }
- next.fail();
- }
- }
-
- private static class DeletionUploadDelegate implements ReadingListDeleteDelegate {
- private final ReadingListChangeAccumulator acc;
- private final StageDelegate next;
-
- DeletionUploadDelegate(ReadingListChangeAccumulator acc, StageDelegate next) {
- this.acc = acc;
- this.next = next;
- }
-
- @Override
- public void onBatchDone() {
- try {
- acc.finish();
- } catch (Exception e) {
- next.fail(e);
- return;
- }
-
- next.next();
- }
-
- @Override
- public void onSuccess(ReadingListRecordResponse response,
- ReadingListRecord record) {
- Logger.debug(LOG_TAG, "Tracking uploaded deletion " + record.getGUID());
- acc.addDeletion(record.getGUID());
- }
-
- @Override
- public void onPreconditionFailed(String guid, MozResponse response) {
- // Should never happen.
- }
-
- @Override
- public void onRecordMissingOrDeleted(String guid, MozResponse response) {
- // Great!
- Logger.debug(LOG_TAG, "Tracking redundant deletion " + guid);
- acc.addDeletion(guid);
- }
-
- @Override
- public void onFailure(Exception e) {
- // Ignore.
- }
-
- @Override
- public void onFailure(MozResponse response) {
- // Ignore.
- }
- }
-
-
- private Queue<String> collectDeletedIDsFromCursor(Cursor cursor) {
- try {
- final Queue<String> toDelete = new LinkedList<>();
-
- final int columnGUID = cursor.getColumnIndexOrThrow(ReadingListItems.GUID);
-
- while (cursor.moveToNext()) {
- final String guid = cursor.getString(columnGUID);
- if (guid == null) {
- // Nothing we can do here.
- continue;
- }
-
- toDelete.add(guid);
- }
-
- return toDelete;
- } finally {
- cursor.close();
- }
- }
-
- private static class StatusUploadDelegate implements ReadingListRecordUploadDelegate {
- private final ReadingListChangeAccumulator acc;
-
- public volatile int failures = 0;
- private final StageDelegate next;
-
- StatusUploadDelegate(ReadingListChangeAccumulator acc, StageDelegate next) {
- this.acc = acc;
- this.next = next;
- }
-
- @Override
- public void onInvalidUpload(ClientReadingListRecord up,
- ReadingListResponse response) {
- recordFailed(up);
- }
-
- @Override
- public void onConflict(ClientReadingListRecord up,
- ReadingListResponse response) {
- // This should never happen for a status-only change.
- // TODO: mark this record as requiring a full upload or download.
- failures++;
- }
-
- @Override
- public void onSuccess(ClientReadingListRecord up,
- ReadingListRecordResponse response,
- ServerReadingListRecord down) {
- if (!TextUtils.equals(up.getGUID(), down.getGUID())) {
- // Uh oh!
- // This should never occur. We should get an onConflict instead,
- // so this would imply a server bug, or something like a truncated
- // over-long GUID string.
- //
- // Should we wish to recover from this case, probably the right approach
- // is to ensure that the GUID is overwritten locally (given that we know
- // the numeric ID).
- }
-
- acc.addChangedRecord(up.givenServerRecord(down));
- }
-
- @Override
- public void onBadRequest(ClientReadingListRecord up, MozResponse response) {
- recordFailed(up);
- }
-
- @Override
- public void onFailure(ClientReadingListRecord up, Exception ex) {
- recordFailed(up);
- }
-
- @Override
- public void onFailure(ClientReadingListRecord up, MozResponse response) {
- recordFailed(up);
- }
-
- private void recordFailed(ClientReadingListRecord up) {
- ++failures;
- }
-
- @Override
- public void onBatchDone() {
- try {
- acc.finish();
- } catch (Exception e) {
- next.fail(e);
- return;
- }
-
- if (failures == 0) {
- next.next();
- return;
- }
-
- next.fail();
- }
- }
-
- private Queue<ClientReadingListRecord> collectStatusChangesFromCursor(final Cursor cursor) {
- try {
- final Queue<ClientReadingListRecord> toUpload = new LinkedList<>();
-
- // The columns should come in this order, FWIW.
- final int columnGUID = cursor.getColumnIndexOrThrow(ReadingListItems.GUID);
- final int columnIsUnread = cursor.getColumnIndexOrThrow(ReadingListItems.IS_UNREAD);
- final int columnIsFavorite = cursor.getColumnIndexOrThrow(ReadingListItems.IS_FAVORITE);
- final int columnMarkedReadBy = cursor.getColumnIndexOrThrow(ReadingListItems.MARKED_READ_BY);
- final int columnMarkedReadOn = cursor.getColumnIndexOrThrow(ReadingListItems.MARKED_READ_ON);
- final int columnChangeFlags = cursor.getColumnIndexOrThrow(ReadingListItems.SYNC_CHANGE_FLAGS);
-
- while (cursor.moveToNext()) {
- final String guid = cursor.getString(columnGUID);
- if (guid == null) {
- // Nothing we can do here.
- continue;
- }
-
- final ExtendedJSONObject o = new ExtendedJSONObject();
- o.put("id", guid);
-
- final int changeFlags = cursor.getInt(columnChangeFlags);
- if ((changeFlags & ReadingListItems.SYNC_CHANGE_FAVORITE_CHANGED) > 0) {
- o.put("favorite", cursor.getInt(columnIsFavorite) == 1);
- }
-
- if ((changeFlags & ReadingListItems.SYNC_CHANGE_UNREAD_CHANGED) > 0) {
- final boolean isUnread = cursor.getInt(columnIsUnread) == 1;
- o.put("unread", isUnread);
- if (!isUnread) {
- o.put("marked_read_by", cursor.getString(columnMarkedReadBy));
- o.put("marked_read_on", cursor.getLong(columnMarkedReadOn));
- }
- }
-
- final ClientMetadata cm = null;
- final ServerMetadata sm = new ServerMetadata(guid, -1L);
- final ClientReadingListRecord record = new ClientReadingListRecord(sm, cm, o);
- toUpload.add(record);
- }
-
- return toUpload;
- } finally {
- cursor.close();
- }
- }
-
- private static class ModifiedUploadDelegate implements ReadingListRecordUploadDelegate {
- private final ReadingListChangeAccumulator acc;
-
- public volatile int failures = 0;
- private final StageDelegate next;
-
- ModifiedUploadDelegate(ReadingListChangeAccumulator acc, StageDelegate next) {
- this.acc = acc;
- this.next = next;
- }
-
- @Override
- public void onInvalidUpload(ClientReadingListRecord up,
- ReadingListResponse response) {
- recordFailed(up);
- }
-
- @Override
- public void onConflict(ClientReadingListRecord up,
- ReadingListResponse response) {
- // This can happen for a material change.
- failures++;
- }
-
- @Override
- public void onSuccess(ClientReadingListRecord up,
- ReadingListRecordResponse response,
- ServerReadingListRecord down) {
- if (!TextUtils.equals(up.getGUID(), down.getGUID())) {
- // Uh oh!
- // This should never occur. We should get an onConflict instead,
- // so this would imply a server bug, or something like a truncated
- // over-long GUID string.
- //
- // Should we wish to recover from this case, probably the right approach
- // is to ensure that the GUID is overwritten locally (given that we know
- // the numeric ID).
- }
-
- // We could upload our material changes but get back additional status
- // changes from the server. Apply them.
- acc.addChangedRecord(up.givenServerRecord(down));
- }
-
- @Override
- public void onBadRequest(ClientReadingListRecord up, MozResponse response) {
- recordFailed(up);
- }
-
- @Override
- public void onFailure(ClientReadingListRecord up, Exception ex) {
- recordFailed(up);
- }
-
- @Override
- public void onFailure(ClientReadingListRecord up, MozResponse response) {
- // Since we download and apply remote changes before uploading local changes, the conflict
- // window is very small. We should essentially never see true conflicts here.
- if (response.getStatusCode() == 404) {
- // We shouldn't see a 404; we should see a record with deleted=true when
- // we fetch remote changes.
- Logger.warn(LOG_TAG, "Ignoring 404 response patching record with guid: " + up.getGUID());
- } else if (response.getStatusCode() == 409) {
- // A 409 indicates that resolved_url has collided with an existing
- // record. Not much to be done here.
- Logger.info(LOG_TAG, "409 response seen; deleting record with guid: " + up.getGUID());
- acc.addDeletion(up);
- } else {
- // We should never see a 412 since we race to upload our changes (and
- // accept whatever the server gives us back).
- recordFailed(up);
- }
- }
-
- private void recordFailed(ClientReadingListRecord up) {
- ++failures;
- }
-
- @Override
- public void onBatchDone() {
- try {
- acc.finish();
- } catch (Exception e) {
- next.fail(e);
- return;
- }
-
- if (failures == 0) {
- next.next();
- return;
- }
-
- next.fail();
- }
- }
-
- private Queue<ClientReadingListRecord> collectModifiedFromCursor(final Cursor cursor) {
- try {
- final Queue<ClientReadingListRecord> toUpload = new LinkedList<>();
-
- final int columnGUID = cursor.getColumnIndexOrThrow(ReadingListItems.GUID);
- final int columnExcerpt = cursor.getColumnIndexOrThrow(ReadingListItems.EXCERPT);
- final int columnResolvedURL = cursor.getColumnIndexOrThrow(ReadingListItems.RESOLVED_URL);
- final int columnResolvedTitle = cursor.getColumnIndexOrThrow(ReadingListItems.RESOLVED_TITLE);
- // TODO: final int columnIsArticle = cursor.getColumnIndexOrThrow(ReadingListItems.IS_ARTICLE);
- // TODO: final int columnWordCount = cursor.getColumnIndexOrThrow(ReadingListItems.WORD_COUNT);
-
- while (cursor.moveToNext()) {
- final String guid = cursor.getString(columnGUID);
- if (guid == null) {
- // Nothing we can do here, but this should never happen: we should
- // have uploaded this record as new before trying to upload a
- // material modification!
- continue;
- }
-
- final ExtendedJSONObject o = new ExtendedJSONObject();
- o.put("id", guid);
- final String excerpt = cursor.getString(columnExcerpt); // Can be NULL.
- final String resolvedURL = cursor.getString(columnResolvedURL); // Can be NULL.
- final String resolvedTitle = cursor.getString(columnResolvedTitle); // Can be NULL.
- if (excerpt == null && resolvedURL == null && resolvedTitle == null) {
- // Nothing material to upload, so skip this record.
- continue;
- }
- o.put("excerpt", excerpt);
- o.put("resolved_url", resolvedURL);
- o.put("resolved_title", resolvedTitle);
- // TODO: o.put("is_article", cursor.getInt(columnIsArticle) == 1);
- // TODO: o.put("word_count", cursor.getInt(columnWordCount));
-
- final ClientMetadata cm = null;
- final ServerMetadata sm = new ServerMetadata(guid, -1L);
- final ClientReadingListRecord record = new ClientReadingListRecord(sm, cm, o);
- toUpload.add(record);
- }
-
- return toUpload;
- } finally {
- cursor.close();
- }
- }
-
- private Queue<ClientReadingListRecord> accumulateNewItems(Cursor cursor) {
- try {
- final Queue<ClientReadingListRecord> toUpload = new LinkedList<>();
- final ReadingListClientRecordFactory factory = new ReadingListClientRecordFactory(cursor);
-
- ClientReadingListRecord record;
- while ((record = factory.getNext()) != null) {
- toUpload.add(record);
- }
- return toUpload;
- } finally {
- cursor.close();
- }
- }
-
- protected void uploadDeletions(final StageDelegate delegate) {
- try {
- final Cursor cursor = local.getDeletedItems();
-
- if (cursor == null) {
- delegate.fail(new RuntimeException("Unable to get unread item cursor."));
- return;
- }
-
- final Queue<String> toDelete = collectDeletedIDsFromCursor(cursor);
-
- // Nothing to do.
- if (toDelete.isEmpty()) {
- Logger.debug(LOG_TAG, "No new deletions to upload. Skipping.");
- delegate.next();
- return;
- } else {
- Logger.debug(LOG_TAG, "Deleting " + toDelete.size() + " records from the server.");
- }
-
- final ReadingListChangeAccumulator acc = this.local.getChangeAccumulator();
- final DeletionUploadDelegate deleteDelegate = new DeletionUploadDelegate(acc, delegate);
-
- // Don't send I-U-S; we're happy for the client to win, because this is a one-way state change.
- this.remote.delete(toDelete, executor, deleteDelegate);
- } catch (Exception e) {
- delegate.fail(e);
- }
- }
-
- // N.B., status changes for items that haven't been uploaded yet are dealt with in
- // uploadNewItems.
- protected void uploadUnreadChanges(final StageDelegate delegate) {
- try {
- final Cursor cursor = local.getStatusChanges();
-
- if (cursor == null) {
- delegate.fail(new RuntimeException("Unable to get unread item cursor."));
- return;
- }
-
- final Queue<ClientReadingListRecord> toUpload = collectStatusChangesFromCursor(cursor);
-
- // Nothing to do.
- if (toUpload.isEmpty()) {
- Logger.debug(LOG_TAG, "No new unread changes to upload. Skipping.");
- delegate.next();
- return;
- } else {
- Logger.debug(LOG_TAG, "Uploading " + toUpload.size() + " new unread changes.");
- }
-
- // Upload each record. This looks like batching, but it's really chained serial requests.
- final ReadingListChangeAccumulator acc = this.local.getChangeAccumulator();
- final StatusUploadDelegate uploadDelegate = new StatusUploadDelegate(acc, delegate);
-
- // Don't send I-U-S; in the case of favorites we're
- // happy to overwrite the server value, and in the case of unread status
- // the server will reconcile for us.
- this.remote.patch(toUpload, executor, uploadDelegate);
- } catch (Exception e) {
- delegate.fail(e);
- }
- }
-
- protected void uploadNewItems(final StageDelegate delegate) {
- try {
- final Cursor cursor = this.local.getNew();
-
- if (cursor == null) {
- delegate.fail(new RuntimeException("Unable to get new item cursor."));
- return;
- }
-
- Queue<ClientReadingListRecord> toUpload = accumulateNewItems(cursor);
-
- // Nothing to do.
- if (toUpload.isEmpty()) {
- Logger.debug(LOG_TAG, "No new items to upload. Skipping.");
- delegate.next();
- return;
- } else {
- Logger.debug(LOG_TAG, "Uploading " + toUpload.size() + " new items.");
- }
-
- final ReadingListChangeAccumulator acc = this.local.getChangeAccumulator();
- final NewItemUploadDelegate uploadDelegate = new NewItemUploadDelegate(acc, new StageDelegate() {
- private boolean tryFlushChanges() {
- Logger.debug(LOG_TAG, "Flushing post-upload changes.");
- try {
- acc.finish();
- return true;
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Flushing changes failed! This sync went wrong.", e);
- delegate.fail(e);
- return false;
- }
- }
-
- @Override
- public void next() {
- Logger.debug(LOG_TAG, "New items uploaded successfully.");
-
- if (tryFlushChanges()) {
- delegate.next();
- }
- }
-
- @Override
- public void fail() {
- Logger.warn(LOG_TAG, "Couldn't upload new items.");
- if (tryFlushChanges()) {
- delegate.fail();
- }
- }
-
- @Override
- public void fail(Exception e) {
- Logger.warn(LOG_TAG, "Couldn't upload new items.", e);
- if (tryFlushChanges()) {
- delegate.fail(e);
- }
- }
- });
-
- // Handle 201 for success, 400 for invalid, 303 for redirect.
- // TODO: 200 == "was already on the server, we didn't touch it, here it is."
- // ... we need to apply it locally.
- this.remote.add(toUpload, executor, uploadDelegate);
- } catch (Exception e) {
- delegate.fail(e);
- return;
- }
- }
-
- protected void uploadModified(final StageDelegate delegate) {
- try {
- // This looks strange because modified includes material changes and
- // status changes, but this is called after status changes have been
- // uploaded and removed from local storage. So what's left should be
- // material changes. Even so, it should be safe to upload status changes
- // here.
- final Cursor cursor = this.local.getModified();
-
- if (cursor == null) {
- delegate.fail(new RuntimeException("Unable to get modified item cursor."));
- return;
- }
-
- final Queue<ClientReadingListRecord> toUpload = collectModifiedFromCursor(cursor);
-
- // Nothing to do.
- if (toUpload.isEmpty()) {
- Logger.debug(LOG_TAG, "No modified items to upload. Skipping.");
- delegate.next();
- return;
- } else {
- Logger.debug(LOG_TAG, "Uploading " + toUpload.size() + " modified items.");
- }
-
- final ReadingListChangeAccumulator acc = this.local.getChangeAccumulator();
- final ModifiedUploadDelegate uploadDelegate = new ModifiedUploadDelegate(acc, new StageDelegate() {
- private boolean tryFlushChanges() {
- Logger.debug(LOG_TAG, "Flushing post-upload changes.");
- try {
- acc.finish();
- return true;
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Flushing changes failed! This sync went wrong.", e);
- delegate.fail(e);
- return false;
- }
- }
-
- @Override
- public void next() {
- Logger.debug(LOG_TAG, "Modified items uploaded successfully.");
-
- if (tryFlushChanges()) {
- delegate.next();
- }
- }
-
- @Override
- public void fail() {
- Logger.warn(LOG_TAG, "Couldn't upload modified items.");
- if (tryFlushChanges()) {
- delegate.fail();
- }
- }
-
- @Override
- public void fail(Exception e) {
- Logger.warn(LOG_TAG, "Couldn't upload modified items.", e);
- if (tryFlushChanges()) {
- delegate.fail(e);
- }
- }
- });
-
- // Handle 201 for success, 400 for invalid, 303 for redirect.
- // TODO: 200 == "was already on the server, we didn't touch it, here it is."
- // ... we need to apply it locally.
- this.remote.patch(toUpload, executor, uploadDelegate);
- } catch (Exception e) {
- delegate.fail(e);
- return;
- }
- }
-
- private void downloadIncoming(final long since, final StageDelegate delegate) {
- final ReadingListChangeAccumulator postDownload = this.local.getChangeAccumulator();
-
- final FetchSpec spec = new FetchSpec.Builder().setSince(since).build();
-
- // TODO: should we flush the accumulator if we get a failure?
- ReadingListRecordDelegate recordDelegate = new ReadingListRecordDelegate() {
- @Override
- public void onRecordReceived(ServerReadingListRecord record) {
- postDownload.addDownloadedRecord(record);
- }
-
- @Override
- public void onRecordMissingOrDeleted(String guid, ReadingListResponse resp) {
- // Should never occur. Deleted records will be processed by onRecordReceived.
- }
-
- @Override
- public void onFailure(Exception error) {
- Logger.error(LOG_TAG, "Download failed. since = " + since + ".", error);
- delegate.fail(error);
- }
-
- @Override
- public void onFailure(MozResponse response) {
- final int statusCode = response.getStatusCode();
- Logger.error(LOG_TAG, "Download failed. since = " + since + ". Response: " + statusCode);
- response.logResponseBody(LOG_TAG);
- if (response.isInvalidAuthentication()) {
- delegate.fail(new ReadingListInvalidAuthenticationException(response));
- } else {
- delegate.fail();
- }
- }
-
- @Override
- public void onComplete(ReadingListResponse response) {
- long lastModified = response.getLastModified();
- Logger.info(LOG_TAG, "Server last modified: " + lastModified);
- try {
- postDownload.finish();
-
- // Yay. We do this here so that if writing changes fails, we don't advance.
- advanceLastModified(lastModified);
- delegate.next();
- } catch (Exception e) {
- delegate.fail(e);
- }
- }
- };
-
- try {
- remote.getAll(spec, recordDelegate, since);
- } catch (URISyntaxException e) {
- delegate.fail(e);
- }
- }
-
- /**
- * Upload deletions and unread changes, then upload new items, then call `done`.
- * Substantially modified records are uploaded last.
- *
- * @param syncDelegate only used for status callbacks.
- */
- private void syncUp(final ReadingListSynchronizerDelegate syncDelegate, final StageDelegate done) {
- // Third.
- final StageDelegate onNewItemsUploaded = new NextDelegate(executor) {
- @Override
- public void doNext() {
- syncDelegate.onNewItemUploadComplete(null, null);
- done.next();
- }
-
- @Override
- public void doFail(Exception e) {
- done.fail(e);
- }
- };
-
- // Second.
- final StageDelegate onUnreadChangesUploaded = new NextDelegate(executor) {
- @Override
- public void doNext() {
- syncDelegate.onStatusUploadComplete(null, null);
- uploadNewItems(onNewItemsUploaded);
- }
-
- @Override
- public void doFail(Exception e) {
- Logger.warn(LOG_TAG, "Uploading unread changes failed.", e);
- done.fail(e);
- }
- };
-
- // First.
- final StageDelegate onDeletionsUploaded = new NextDelegate(executor) {
- @Override
- public void doNext() {
- syncDelegate.onDeletionsUploadComplete();
- uploadUnreadChanges(onUnreadChangesUploaded);
- }
-
- @Override
- public void doFail(Exception e) {
- Logger.warn(LOG_TAG, "Uploading deletions failed.", e);
- done.fail(e);
- }
- };
-
- try {
- uploadDeletions(onDeletionsUploaded);
- } catch (Exception ee) {
- done.fail(ee);
- }
- }
-
-
- /**
- * Do an upload-only sync.
- * By "upload-only" we mean status-only changes and new items.
- * To upload modifications, use syncAll.
- */
- /*
- // Not yet used
- public void syncUp(final ReadingListSynchronizerDelegate syncDelegate) {
- final StageDelegate onUploadCompleted = new StageDelegate() {
- @Override
- public void next() {
- // TODO
- syncDelegate.onNewItemUploadComplete(null, null);
- }
-
- @Override
- public void fail(Exception e) {
- syncDelegate.onUnableToSync(e);
- }
- };
-
- executor.execute(new Runnable() {
- @Override
- public void run() {
- try {
- syncUp(onUploadCompleted);
- } catch (Exception e) {
- syncDelegate.onUnableToSync(e);
- return;
- }
- }
- });
- }
-*/
-
- /**
- * Do a bidirectional sync.
- */
- public void syncAll(final ReadingListSynchronizerDelegate syncDelegate) {
- syncAll(getLastModified(), syncDelegate);
- }
-
- public void syncAll(final long since, final ReadingListSynchronizerDelegate syncDelegate) {
- // Fourth: call back to the synchronizer delegate.
- final StageDelegate onModifiedUploadComplete = new NextDelegate(executor) {
- @Override
- public void doNext() {
- syncDelegate.onModifiedUploadComplete();
- syncDelegate.onComplete();
- }
-
- @Override
- public void doFail(Exception e) {
- syncDelegate.onUnableToSync(e);
- }
- };
-
- // Third: upload modified records.
- final StageDelegate onDownloadCompleted = new NextDelegate(executor) { // TODO: since.
- @Override
- public void doNext() {
- // TODO: save prefs.
- syncDelegate.onDownloadComplete();
- uploadModified(onModifiedUploadComplete);
- }
-
- @Override
- public void doFail(Exception e) {
- Logger.warn(LOG_TAG, "Download failed.", e);
- syncDelegate.onUnableToSync(e);
- }
- };
-
- // Second: download incoming changes.
- final StageDelegate onUploadCompleted = new NextDelegate(executor) {
- @Override
- public void doNext() {
- // N.B., we apply the downloaded versions of all uploaded records.
- // That means the DB server timestamp matches the server's current
- // timestamp when we do a fetch; we skip records in this way.
- // We can also optimize by keeping the (guid, server timestamp) pair
- // in memory, but of course this runs into invalidation issues if
- // concurrent writes are occurring.
- downloadIncoming(since, onDownloadCompleted);
- }
-
- @Override
- public void doFail(Exception e) {
- Logger.warn(LOG_TAG, "Upload failed.", e);
- syncDelegate.onUnableToSync(e);
- }
- };
-
- // First: upload changes and new items.
- executor.execute(new Runnable() {
- @Override
- public void run() {
- try {
- syncUp(syncDelegate, onUploadCompleted);
- } catch (Exception e) {
- syncDelegate.onUnableToSync(e);
- return;
- }
- }
- });
-
- // TODO: ensure that records we identified as conflicts have been downloaded.
- }
-
- protected long getLastModified() {
- return prefs.getLong(PREF_LAST_MODIFIED, -1L);
- }
-
- protected void advanceLastModified(final long lastModified) {
- if (getLastModified() > lastModified) {
- return;
- }
- prefs.edit().putLong(PREF_LAST_MODIFIED, lastModified).apply();
- }
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListSynchronizerDelegate.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/* 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.reading;
-
-import java.util.Collection;
-
-public interface ReadingListSynchronizerDelegate {
- // Called on failure.
- void onUnableToSync(Exception e);
-
- // These are called sequentially, or not at all
- // if a failure occurs.
- void onDeletionsUploadComplete();
- void onStatusUploadComplete(Collection<String> uploaded, Collection<String> failed);
- void onNewItemUploadComplete(Collection<String> uploaded, Collection<String> failed);
- void onDownloadComplete();
- void onModifiedUploadComplete();
-
- // If no failure occurred, called at the end.
- void onComplete();
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListWipeDelegate.java
+++ /dev/null
@@ -1,14 +0,0 @@
-/* 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.reading;
-
-import org.mozilla.gecko.sync.net.MozResponse;
-
-public interface ReadingListWipeDelegate {
- void onSuccess(ReadingListStorageResponse response);
- void onPreconditionFailed(MozResponse response);
- void onFailure(Exception e);
- void onFailure(MozResponse response);
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ServerReadingListRecord.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/* 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.reading;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-public class ServerReadingListRecord extends ReadingListRecord {
- final ExtendedJSONObject fields;
-
- public ServerReadingListRecord(ExtendedJSONObject obj) {
- super(new ServerMetadata(obj));
- this.fields = obj.deepCopy();
- }
-
- @Override
- public String getURL() {
- return this.fields.getString("url"); // TODO: resolved_url
- }
-
- @Override
- public String getTitle() {
- return this.fields.getString("title"); // TODO: resolved_title
- }
-
- @Override
- public String getAddedBy() {
- return this.fields.getString("added_by");
- }
-
- public String getExcerpt() {
- return this.fields.getString("excerpt");
- }
-}